この項では、次に示すフォーカス・イベントに関連する問題のトラブルシューティングについて説明します。
フォーカスの問題をトラブルシューティングするには、フォーカス・イベントをトレースできます。例10-5に示すように、フォーカス・リスナーをツールキットに追加することでこれを行うことができます。
例10-5 フォーカス・イベントのトレース
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener( public void eventDispatched(AWTEvent e) { System.err.println(e); } ), FocusEvent.FOCUS_EVENT_MASK | WindowEvent.WINDOW_FOCUS_EVENT_MASK | WindowEvent.WINDOW_EVENT_MASK);
ここでSystem.err
ストリームが使用されているのは、出力のバッファリングが行われないからです。
なお、フォーカス・イベントの正しい順序は次のとおりです。
フォーカスを失うコンポーネントのFOCUS_LOST
フォーカスを失うトップ・レベルのWINDOW_LOST_FOCUS
アクティブ状態を失うトップ・レベルのWINDOW_DEACTIVATED
アクティブ・ウィンドウになるトップ・レベルのWINDOW_ACTIVATED
フォーカスされたウィンドウになるトップ・レベルのWINDOW_GAINED_FOCUS
フォーカスを得るコンポーネントのFOCUS_GAINED
フォーカスされたウィンドウ内のコンポーネント間でフォーカスが移動する場合は、FOCUS_LOST
イベントとFOCUS_GAINED
イベントのみが生成されるはずです。同じ所有者が所有するウィンドウ間や、所有されるウィンドウとその所有者の間でフォーカスが移動する場合は、次のイベントが生成されるはずです。
FOCUS_LOST
WINDOW_LOST_FOCUS
WINDOW_GAINED_FOCUS
FOCUS_GAINED
注意: フォーカスまたはアクティベーションを失うイベントが最初に来る必要があります。
時折、ネイティブ・プラット・フォームが原因で問題が発生することがあります。これをチェックするには、フォーカスに関係するネイティブ・イベントを調査します。フォーカス対象のウィンドウがアクティブ化され、フォーカス対象のコンポーネントがネイティブ・フォーカス・イベントを受け取ることを確認してください。
Windowsプラットフォームのネイティブ・フォーカス・イベントは、次のとおりです。
WM_ACTIVATE
(トップ・レベル用)。WPARAM
は、アクティブ化の際はWA_ACTIVE
、非アクティブ化の際はWA_INACTIVE
になります。
WM_SETFOCUS
およびWM_KILLFOCUS
(コンポーネント用)。
Windowsプラットフォームでは、合成フォーカスという概念が実装されています。つまり、フォーカス所有者コンポーネントはそのフォーカス可能状態をエミュレートするだけですが、実際のネイティブ・フォーカスはフォーカス・プロキシ・コンポーネントに設定されます。このコンポーネントは、キーとインプット・メソッドのネイティブ・メッセージを受け取り、それらをフォーカス所有者にディスパッチします。JDK7より前は、フォーカス・プロキシ・コンポーネントはフレーム/ダイアログ内の専用の非表示の子コンポーネントでした。最新のJDKリリースでは、フレーム/ダイアログ自体がフォーカス・プロキシとして機能します。現在は、所有されるウィンドウ内のコンポーネントだけでなく、すべての子コンポーネントのフォーカス・プロキシとしても機能します。単純なウィンドウは、ネイティブ・フォーカスを受け取ることは決してなく、その所有者のフォーカス・プロキシに依存します。ユーザーがこのメカニズムを意識することはありませんが、デバッグ時には考慮に入れるべきです。
Oracle SolarisおよびLinuxオペレーティング・システム上のXToolkitでは、AWT自体がフォーカスを管理できるフォーカス・モデルを使用しています。このモデルでは、ウィンドウ・マネージャが直接トップレベル・ウィンドウに入力フォーカスを設定することはなく、代わりにWM_TAKE_FOCUS
クライアント・メッセージのみを送信することで、フォーカスを設定すべきであることを示します。その後、トップレベル・ウィンドウへのフォーカス設定が可能な場合は、その設定がAWTによって明示的に行われます。
注意: ただし、Xサーバーおよび一部のウィンドウ・マネージャは、ウィンドウにフォーカス・イベントを送信することがあります。ただし、そのようなイベントはすべてAWTによって破棄されます。
トップ・レベル内のコンポーネントがフォーカスを得ても、AWTはフォーカス・イベントの階層チェーンを生成しません。さらに、コンポーネント自体にマップされたネイティブ・ウィンドウがネイティブ・フォーカス・イベントを取得することはありません。Oracle SolarisおよびLinuxプラットフォームではWindowsプラットフォームと同じく、AWTはフォーカス・プロキシ・メカニズムを使用します。したがって、コンポーネントのフォーカスはフォーカス・イベントの合成によって設定され、不可視のフォーカス・プロキシがネイティブ・フォーカスを保持します。
Window
オブジェクト(Frame
オブジェクトでもDialog
オブジェクトでもない)にマップされたネイティブ・ウィンドウには、override-redirect
フラグが設定されます。したがって、ウィンドウ・マネージャはそのウィンドウに対し、フォーカス移動に関して通知しません。このウィンドウでフォーカスが要求されるのは、マウス・クリックへの応答としてだけです。このウィンドウはネイティブ・フォーカス・イベントを一切受け取りません。したがって、トレース可能なイベントは、フレームまたはダイアログ上のFocusIn
またはFocusOut
イベントだけです。XToolkitでは、フォーカスの主な処理がJavaレベルで発生するため、フォーカスのデバッグがWToolkitの場合よりも単純になります。
アプレットは、EmbeddedFrame
の子(ただし直接の子ではない)としてブラウザ内に埋め込まれます。これは、プラグインとの通信機能を備えた特殊なFrame
です。アプレットからは、EmbeddedFrame
はトップ・レベルの完全なFrame
に見えます。
EmbeddedFrame
のフォーカスを管理するには、特殊な追加アクションが必要になります。アプレットが最初に起動する際に、EmbeddedFrame
はネイティブ・システムによってデフォルトでアクティブ化されません。アクティブ化は、EmbeddedFrame
に用意された特殊なAPIをプラグインがトリガーすることによって実行されます。フォーカスがアプレットを離れる際のEmbeddedFrame
の非アクティブ化も、合成された方法で行われます。
Xウィンドウ・マネージャでサポートされているフォーカス・モデルは、次のとおりです。
click-to-focusは一般的に使用されているフォーカス・モデルです。(たとえば、Microsoft Windowsではこのモデルが使用されています。)
focus-follows-mouseは、マウスの下にあるウィンドウにフォーカスが移動するフォーカス・モデルです。
Java SE 7のXAWTではfocus-follows-mouseモデルは検出されませんが、これにより、単純なウィンドウ(java.awt.Window
クラスのオブジェクト)で問題が発生します。そのようなウィンドウにはoverride-redirect
プロパティが設定されているため、ウィンドウにフォーカスを移動できるのはマウス・ボタンを押した場合のみであり、ウィンドウの上にマウスを移動してもフォーカスは移動しません。回避方法としては、ウィンドウにMouseListener
を設定し、マウスがウィンドウのボーダーを横切ったときにウィンドウへのフォーカスを要求します。
このセクションでは、AWTのフォーカスに関連して発生する可能性のあるいくつかの問題を説明し、その解決方法を提示します。
問題1 - KDEを実行するLinux上のXToolkitでは、フレームのタイトルをクリックしても、2つのフレーム間で切り替えることができない。
フレームの内側にあるコンポーネントをクリックすると、フォーカスが変更されます。
解決方法: ウィンドウ・マネージャのバージョンをチェックし、3.0 以上にアップグレードします。
問題2 - [Tab]/[Shift]+[Tab]でフォーカスを移動するためにKeyListener
を使ってフォーカスを管理しても、キー・イベントが表示されない。
解決方法: トラバーサル・キー・イベントをキャッチするには、Component.setFocusTraversalKeysEnabled(true)
を呼び出してそれらのイベントを有効にする必要があります。
問題3 - Window.setModalExclusionType(ModalExclusionType)
でウィンドウがモーダル除外に設定される。
その所有者であるフレームはモーダル・ブロックされています。この場合、ウィンドウもモーダル・ブロックされたままになります。
解決方法: ウィンドウは、その所有者がフォーカスの取得を許可されていない場合はフォーカスされたウィンドウになれません。解決方法は、所有者をモーダリティから除外することです。
問題4 - Windowsでコンポーネントがフォーカスを要求し、同時にそのコンテナから削除される。
java.lang.NullPointerException: null pData
がスローされる場合があります。
解決方法: 例外のスローを避けるもっとも簡単な方法は、削除とフォーカス要求をEDT上で行うことです。もう1つのより複雑なアプローチは、フォーカス要求と削除を同期することです(これらのアクションをそれぞれ異なるスレッド上で実行する必要がある場合)。
問題5 - コンポーネントにフォーカスが要求され、フォーカスの所有者がすぐに削除された場合、フォーカスは削除されたコンポーネントの後のコンポーネントに移動する。
たとえば、コンポーネントAがフォーカス所有者とします。コンポーネントBがフォーカスを要求し、その直後にコンポーネントAがそのコンテナから削除されます。フォーカスは最終的に、コンテナ内でコンポーネントAのあとに配置されたコンポーネントCに移動し、コンポーネントBには移動しません。
解決方法: この場合、フォーカス要求がコンポーネントAの削除前ではなく削除後に実行されるようにしてください。
問題6 - Windowsで、非アクティブなフレーム内のウィンドウをalwaysOnTop
に設定すると、そのウィンドウはキー・イベントを受け取れない。
たとえば、フレームが1つと、そのフレームが所有するウィンドウが1つ表示されているとします。フレームがアクティブでないため、ウィンドウはフォーカスされません。その後、ウィンドウをalwaysOnTop
に設定します。ウィンドウはフォーカスを取得しますが、その所有者は非アクティブなままです。したがって、ウィンドウはキー・イベントを受け取れません。
解決方法: ウィンドウをalwaysOnTop
に設定する前に、フレームを前面に移動します(Frame.toFront()
メソッド)。
問題7 - スプラッシュ画面が表示され、そのスプラッシュ画面のウィンドウが閉じた後でフレームが表示されると、そのフレームはアクティブ化されない。
解決方法: フレームを表示した後で(Frame.setVisible(true)
メソッド)、フレームを前面に移動します(Frame.toFront()
メソッド)。
問題8 - WindowFocusListener.windowGainedFocus(WindowEvent)
メソッドがフレームの直近のフォーカス所有者を返さない。
たとえば、あるフレームがフォーカスされたウィンドウであり、そのコンポーネントの1つがフォーカス所有者であるとします。別のウィンドウがクリックされたあと、このフレームが再度クリックされます。WINDOW_GAINED_FOCUS
がフレームに送信され、WindowFocusListener.windowGainedFocus(WindowEvent)
メソッドが呼び出されます。ただし、このコールバックの内部では、Frame.getMostRecentFocusOwner()
からnull
が返されるため、フレームの直近のフォーカス所有者を確認できません。
解決方法: WindowListener.windowActivated(WindowEvent)
コールバックの内部では、フレームの直近のフォーカス所有者を取得できます。ただし、この時点までにフレームがフォーカスされたウィンドウになっているのは、フレームが所有するウィンドウが1つも存在しない場合だけです。
注: このアプローチは、ウィンドウでは機能せず、フレームまたはダイアログでのみ機能します。 |
問題9 - アプレットが起動時にフォーカスを横取りする。
解決方法: この動作は、JDKのデフォルト動作です。ただし、アプレットが起動時にフォーカスを取得しないようにする必要がある場合もあります(アプレットが不可視であるためにフォーカスがまったく不要な場合など)。この場合は、例10-6に示すように、HTMLタグ内で特殊パラメータinitial_focus
をfalse
に設定します。
問題10 - Component.setEnabled(false)
でウィンドウを無効にしても、完全にフォーカス不可能な状態にならない。
解決方法: Component.setEnabled(false)
またはComponent.setFocusable(false)
を呼び出すことで設定された状態が、そのすべてのコンテンツでもフォーカス不可能な状態として維持されるとは仮定しないでください。代わりに、Window.setFocusableWindowState(boolean)
メソッドを使用します。