AWTフォーカス・サブシステム
Java 2 Standard Edition、JDK 1.4より前のAWTフォーカス・サブシステムは不十分でした。 設計とAPIによる大きな問題だけでなく、100を超える未解決のバグを抱えていました。 これらのバグの多くは、プラットフォームの不一致や、重量用のネイティブのフォーカス・システムと軽量用のJavaフォーカス・システムとの間に互換性がないことが原因でした。
AWTのフォーカスの実装の単一で最大の問題は、現在フォーカスのあるComponentを照会することができないことでした。 このような照会のためのAPIが存在しないだけではなく、アーキテクチャが不十分であるために、このような情報はコードによっても維持されていませんでした。
(FrameやDialogではなく) Windowの軽量な子がキーボード入力を受け取れないことも、同様に問題でした。 この問題は、WindowがWINDOW_ACTIVATED
イベントを決して受け取らないためアクティブになることがなく、フォーカスされたComponentを含むことができるのがアクティブなWindowのみであるために生じていました。
さらに、FocusEventおよびWindowEvent用のAPIは、フォーカスまたはアクティブ化の変更に関係する「反対の」Componentを決定する方法がないため不十分であることを、多くの開発者が指摘していました。 たとえば、ComponentがFOCUS_LOSTイベントを受け取った場合に、どのComponentがフォーカスを取得するのかを知る方法はありませんでした。 Microsoft Windowsはコストなしでこの機能を提供するため、Microsoft Windows C/C++またはVisual BasicからJavaに移行する開発者は、この機能がないことに不満を感じていました。
これらおよびその他の欠点に対処するため、JDK 1.4ではAWTの新しいフォーカス・モデルを設計しました。 主な設計変更は、集中処理のための新しいKeyboardFocusManagerクラスの構築、および軽量フォーカス・アーキテクチャです。 フォーカス関連のプラットフォーム依存コードは最小限に抑えられ、完全にプラガブルで拡張可能な公開APIに置き換えられました。 既存の実装との下位互換性を保つ努力をしましたが、洗練され効果的な結果に到達するために、互換性のない小規模な変更を行わざるを得ませんでした。 これらの非互換性が既存のアプリケーションに与える影響はわずかであると予測されています。
このドキュメントは、新しいAPIと、新しいモデルにも引き続き関係する既存のAPIの正式な仕様です。 開発者は、フォーカス関連のクラスおよびメソッドのJavadocと合わせてこのドキュメントを使用することにより、カスタマイズされていながらプラットフォーム間で一貫性のあるフォーカス動作を持つ堅固なAWTおよびSwingアプリケーションを作成できるはずです。 このドキュメントには次のセクションがあります。
- KeyboardFocusManagerの概要
- KeyboardFocusManagerとブラウザ・コンテキスト
- KeyEventDispatcher
- FocusEventとWindowEvent
- イベント配信
- 反対のComponentとWindow
- テンポラリFocusEvent
- フォーカス・トラバーサル
- フォーカス・トラバーサル・ポリシー
- フォーカス・トラバーサル・ポリシー・プロバイダ
- プログラムによるトラバーサル
- フォーカス可能性
- フォーカス可能なWindow
- フォーカスのリクエスト
- フォーカスとPropertyChangeListener
- フォーカスとVetoableChangeListener
- Z軸順
- DefaultKeyboardFocusManagerの置き換え
- 以前のリリースとの非互換性
KeyboardFocusManagerの概要
フォーカス・モデルは、現在のフォーカス状態の照会、フォーカス変更の開始、およびデフォルトのフォーカス・イベント・ディスパッチのカスタム・ディスパッチャへの置換をクライアント・コードが実行するためのAPIのセットを提供する、単独のKeyboardFocusManagerクラスに集中しています。 クライアントはフォーカス状態を直接照会することも、フォーカス状態に変更があった場合にPropertyChangeEventを受け取るPropertyChangeListenerを登録することもできます。
KeyboardFocusManagerでは次の主な概念と用語が導入されます。
- 「フォーカス所有者」 -- 通常キーボード入力を受け取るComponent。
- 「パーマネント・フォーカス所有者」 -- フォーカスを永続的に受け取る最後のComponent。 「フォーカス所有者」と「パーマネント・フォーカス所有者」は、テンポラリ・フォーカス変更が現在有効でないかぎり同等です。 変更されている場合、「パーマネント・フォーカス所有者」はテンポラリ・フォーカス変更が終了するとふたたび「フォーカス所有者」になります。
- 「フォーカスされたWindow」 -- 「フォーカス所有者」を含むWindow。
- 「アクティブWindow」 -- 「フォーカスされたWindow」であるFrameまたはDialogか、または「フォーカスされたWindow」の所有者である最初のFrameまたはDialog。
- 「フォーカス・トラバーサル」 -- ユーザーが、カーソルを動かさずに「フォーカス所有者」を変更できる機能。 通常、これはキーボード(TABキーを使用するなど)、またはユーザー補助機能を利用する環境における同等のデバイスを使用して行われます。 クライアント・コードでは、トラバーサルをプログラムにより開始することもできます。 通常のフォーカス・トラバーサルは、「次の」Componentへの「フォワード」、または「前の」Componentへの「バックワード」のいずれかです。
- 「フォーカス・トラバーサル・サイクル」 --「フォワード」(または「バックワード」)の通常のフォーカス・トラバーサルでフォーカス・サイクル内のすべてのComponentをトラバースし、ほかのComponentをトラバースしないような、Component階層の一部分。 このサイクルにより、サイクル内の任意のComponentから「次の」(フォワード・トラバーサル)および「前の」(バックワード・トラバーサル) Componentへのマッピングが行われます。
- 「トラバース可能なComponent」 -- フォーカス・トラバーサル・サイクル内のComponent。
- 「トラバース不可能なComponent」 -- フォーカス・トラバーサル・サイクル外のComponent。 トラバース不可能なComponentであっても、ほかの方法(直接のフォーカス・リクエストなど)によってフォーカスが設定可能です。
- 「フォーカス・サイクル・ルート」 -- 特定の「フォーカス・トラバーサル・サイクル」のComponent階層のルートであるContainer。 「フォーカス所有者」が特定のサイクル内のComponentである場合、通常のフォワードおよびバックワードのフォーカス・トラバーサルでは、「フォーカス所有者」をComponent階層のフォーカス・サイクル・ルートより上に移動することはできません。 代わりに、「アップ・サイクル」および「ダウンサイクル」という2つの追加のトラバーサル操作が定義され、キーボードおよびプログラムによってフォーカス・トラバーサル・サイクル階層を上下にナビゲートできます。
- 「フォーカス・トラバーサル・ポリシー・プロバイダ」 - 「FocusTraversalPolicyProvider」プロパティがtrueであるContainer。 このContainerは、フォーカス・トラバーサル・ポリシーを取得するために使用されます。 このContainerは新しいフォーカス・サイクルを定義するわけではなく、その子の「フォワード」および「バックワード」のトラバースの順序を変更するだけです。 フォーカス・トラバーサル・ポリシー・プロバイダは、Containerに対して
setFocusTraversalPolicyProvider
を使用して設定できます。
すべてのWindowおよびJInternalFrameは、デフォルトで「フォーカス・サイクル・ルート」です。 それが唯一のフォーカス・サイクル・ルートである場合は、フォーカス可能なすべての子孫がフォーカス・サイクルに含まれる必要があり、そのフォーカス・トラバーサル・ポリシーは、通常のフォワード(またはバックワード)のトラバーサルの間にすべての子孫に到達できることを保証することによって、すべての子孫がフォーカス・サイクルに含まれるようにする必要があります。 一方、WindowまたはJInternalFrameにフォーカス・サイクル・ルートである子孫がある場合、このような各子孫は、自身がルートであるフォーカス・サイクルと、もっとも近いフォーカス・サイクル・ルートの祖先のフォーカス・サイクルの、2つのフォーカス・サイクルのメンバーになります。 このような「下位」のフォーカス・サイクル・ルートのフォーカス・サイクルに所属するフォーカス可能なComponentにトラバースするためには、まず(フォワードまたはバックワードで)トラバースしてその下位のフォーカス・サイクル・ルートに到達し、次に「ダウンサイクル」操作を使用してその下位Componentに到達します。
次はその例です。
次のことを前提にしています。
- Aは
Window
で、これはフォーカス・サイクル・ルートである必要があることを意味します。 - BとDは、フォーカス・サイクル・ルートである
Container
です。 - Cはフォーカス・サイクル・ルートではない
Container
です。 - G、H、E、およびFはすべて
Component
です。
- Aはルートであり、A、B、C、およびFはAのサイクルのメンバーです。
- Bはルートであり、B、D、およびEはBのサイクルのメンバーです。
- Dはルートであり、D、G、およびHはDのサイクルのメンバーです。
KeyboardFocusManager
は抽象クラスです。 AWTでは、DefaultKeyboardFocusManager
クラスにデフォルトの実装が提供されます。
KeyboardFocusManagerとブラウザ・コンテキスト
一部のブラウザは、異なるコード・ベースのアプレットを別のコンテキストに分割し、これらのコンテキストの間に壁を構築します。 各スレッドおよび各Componentは、特定のコンテキストに関連付けられ、ほかのコンテキスト内のスレッドに影響を与えたり、Componentにアクセスしたりすることはできません。 このようなシナリオでは、コンテキストごとに1つのKeyboardFocusManagerがあります。 別のブラウザは、すべてのアプレットを同じコンテキストに配置します。これは、すべてのアプレットに対して単一でグローバルなKeyboardFocusManagerのみがあることを示します。 この動作は実装に依存します。 詳細はブラウザのマニュアルを参照してください。 ただし、存在するコンテキストの数にかかわらず、ClassLoaderあたり複数のフォーカスの所有者、フォーカスされたWindow、またはアクティブWindowが存在することはありません。
KeyEventDispatcherおよびKeyEventPostProcessor
ユーザーのKeyEventは通常フォーカス所有者に送られますが、これを避けるべき場合がまれにあります。 インプット・メソッドは特殊なComponentの例で、インプット・メソッドがKeyEventを受け取るべきですが、関連付けられたテキストComponentは引き続きフォーカス所有者でありフォーカス所有者であるべきです。
KeyEventDispatcherは、クライアント・コードが特定のコンテキスト内のすべてのKeyEventを事前に待機できるようにするための、軽量インタフェースです。 このインタフェースを実装し、現在のKeyboardFocusManagerに登録されたクラスのインスタンスは、フォーカス所有者にKeyEventがディスパッチされる前にそのKeyEventを受け取ります。これにより、KeyEventDispatcherはイベントのターゲット変更、消費、自身によるイベント・ディスパッチ、またはその他の変更を行うことができます。
一貫性を保つため、KeyboardFocusManager自体はKeyEventDispatcherです。 デフォルトで、現在のKeyboardFocusManagerは、登録されたKeyEventDispatchersによりディスパッチされないすべてのKeyEventsのシンクになります。 現在のKeyboardFocusManagerはKeyEventDispatcherとしての登録を完全に解除することはできません。 ただし、KeyEventDispatcherが実際にディスパッチしたかどうかにかかわらずKeyEventをディスパッチしたことを報告する場合は、KeyboardFocusManagerはKeyEventに関してそれ以上の処理は行いません (クライアント・コードは、現在のKeyboardFocusManagerをKeyEventDispatcherとして1回または複数回登録することが可能ですが、これが必要になる明確な理由はなく、お薦めできません)。
クライアント・コードは、KeyEventPostProcessorインタフェースを使用して、特定のコンテキスト内のKeyEventを事後に待機することもできます。 現在のKeyboardFocusManagerに登録されたKeyEventPostProcessorは、KeyEventがフォーカス所有者にディスパッチされ処理されたあとでそのKeyEventを受け取ります。 KeyEventPostProcessorは、アプリケーション内に現在フォーカスを所有しているComponentがないために破棄されるはずのKeyEventも受け取ります。 これによりアプリケーションは、メニュー・ショートカットなどの、グローバルKeyEventの事後処理を必要とする機能を実装できるようになります。
KeyEventDispatcherと同様に、KeyboardFocusManagerもKeyEventPostProcessorを実装し、この機能を使用する際には同様の制限が適用されます。
FocusEventとWindowEvent
AWTでは、フォーカス・モデルの中心となる次の6つのイベント・タイプが2つの異なるjava.awt.event
クラス内に定義されています。
WindowEvent.WINDOW_ACTIVATED
: このイベントは、FrameまたはDialogがアクティブWindowになったときに、そのFrameまたはDialogにディスパッチされます(FrameでもDialogでもないWindowにはディスパッチされません)。WindowEvent.WINDOW_GAINED_FOCUS
: このイベントは、WindowがフォーカスされたWindowになったときにディスパッチされます。 このイベントを受け取ることができるのは、フォーカス可能なWindowのみです。FocusEvent.FOCUS_GAINED
: このイベントは、Componentがフォーカス所有者になったときにディスパッチされます。 このイベントを受け取ることができるのは、フォーカス可能なComponentのみです。FocusEvent.FOCUS_LOST
: このイベントは、Componentがフォーカス所有者でなくなったときにディスパッチされます。WindowEvent.WINDOW_LOST_FOCUS
: このイベントは、WindowがフォーカスされたWindowでなくなったときにディスパッチされます。WindowEvent.WINDOW_DEACTIVATED
: このイベントは、FrameまたはDialogがアクティブWindowでなくなったときに、そのFrameまたはDialogにディスパッチされます(FrameでもDialogでもないWindowにはディスパッチされません)。
イベント配信
フォーカスがjavaアプリケーションにない場合に、ユーザーが非アクティブなFrame bのフォーカス可能な子Component aをクリックすると、次のイベントがディスパッチされ、順に処理されます。
- bは
WINDOW_ACTIVATED
イベントを受信します。 - 次に、bは
WINDOW_GAINED_FOCUS
イベントを受信します。 - 最後に、aは
FOCUS_GAINED
イベントを受信します。
- aは
FOCUS_LOST
イベントを受信します。 - bは
WINDOW_LOST_FOCUS
イベントを受信します。 - bは
WINDOW_DEACTIVATED
イベントを受信します。 - dは
WINDOW_ACTIVATED
イベントを受信します。 - dは
WINDOW_GAINED_FOCUS
イベントを受信します。 - cは
FOCUS_GAINED
イベントを受信します。
さらに、各イベント・タイプは反対のイベント・タイプと一対一対応でディスパッチされます。 たとえば、ComponentがFOCUS_GAINED
イベントを受け取った場合、間にFOCUS_LOST
を受け取ることなく別のFOCUS_GAINED
イベントを受け取ることは決してありません。
最後に、これらのイベントは情報目的だけで配信されることに注意してください。 たとえば、前のFOCUS_LOST
イベントの処理中に、フォーカスを失うComponentにフォーカスを戻すことをリクエストすることによって、保留中のFOCUS_GAINED
イベントが配信されることを防ぐことはできません。 クライアント・コードがこのようなリクエストを行う場合があっても、保留中のFOCUS_GAINED
は配信され、フォーカスを元のフォーカス所有者に戻すイベントがあとに続きます。
FOCUS_GAINED
イベントをどうしても抑制する必要がある場合は、クライアント・コードはフォーカスの変更を拒否するVetoableChangeListener
をインストールできます。 「フォーカスとVetoableChangeListener」を参照してください。
反対のComponentとWindow
各イベントには、フォーカスまたはアクティベーションの変更に関係する「反対の」ComponentまたはWindowの情報が含まれます。 たとえば、FOCUS_GAINED
イベントの場合、反対のComponentはフォーカスを失ったComponentです。 このフォーカスまたはアクティベーションの変更が、ネイティブ・アプリケーションや異なるVMまたはコンテキストのJavaアプリケーションで発生する場合、または別のComponentなしで行われる場合は、反対のComponentまたはWindowはnullになります。 この情報には、FocusEvent.getOppositeComponent
またはWindowEvent.getOppositeWindow
を使用してアクセスできます。
一部のプラットフォームでは、フォーカスまたはアクティベーションの変更が2つの異なる重量Component間で起きた場合に、反対のComponentまたはWindowを判断できません。 このような場合に、反対のComponentまたはWindowがnullにセットされるプラットフォームもあれば、null以外の有効な値にセットされるプラットフォームもあります。 ただし、同じ重量Containerを共有する2つの軽量Component間のフォーカスの変更では、反対のComponentは常に正しくセットされます。 したがって、純粋なSwingアプリケーションでは、トップ・レベルWindow内で発生したフォーカス変更の反対のComponentを使用する場合にはこのプラットフォームの制限を無視できます。
テンポラリFocusEvent
FOCUS_GAINED
およびFOCUS_LOST
イベントは、テンポラリまたはパーマネントとマークされます。
テンポラリFOCUS_LOST
イベントは、Componentがフォーカスを失おうとしているが、すぐにフォーカスを取得し直す場合に送信されます。 これらのイベントは、フォーカスの変更がデータ検証のトリガーとして使用される場合に便利です。 たとえば、テキストComponentが、ユーザーがほかのComponentとの対話を開始する前にそのコンテンツをコミットする場合がありますが、これはFOCUS_LOST
イベントに応答することで実行できます。 ただし、受け取ったFocusEvent
がテンポラリの場合は、すぐにテキスト・フィールドにフォーカスが戻るため、コミットするべきではありません。
パーマネント・フォーカス移動は通常、ユーザーが選択可能な重量Componentをクリックした場合や、キーボードまたは同等の入力デバイスを使用したフォーカス・トラバーサル、またはrequestFocus()
やrequestFocusInWindow()
の呼出しの結果生じます。
テンポラリ・フォーカス移動は通常、MenuまたはPopupMenuの表示、Scrollbarのクリックまたはドラッグ、タイトル・バーのドラッグによるWindowの移動、またはフォーカスされたWindowの別のWindowへの変更を行った結果生じます。 一部のプラットフォームでは、これらのアクションではFocusEventがまったく生成されない場合があることに注意してください。 ほかのプラットフォームでは、テンポラリ・フォーカス移動が発生します。
ComponentがテンポラリFOCUS_LOST
イベントを受け取ると、イベントの反対のComponent (ある場合)はテンポラリFOCUS_GAINED
イベントを受け取る場合がありますが、パーマネントFOCUS_GAINED
イベントを受け取る場合もあります。 MenuまたはPopupMenuの表示やScrollbarのクリックまたはドラッグでは、テンポラリFOCUS_GAINED
イベントが生成されるはずです。 しかし、フォーカスされたWindowの変更では、新しいフォーカス所有者に対してパーマネントFOCUS_GAINED
イベントが生成されます。
Componentクラスには、必要なテンポラリ状態をパラメータとして取るrequestFocus
およびrequestFocusInWindow
のバリエーションが含まれます。 ただし一部のネイティブ・ウィンドウ・システムでは、任意のテンポラリ状態の指定を実装できないため、このメソッドの正常な動作は軽量Componentに対してのみ保証されます。 このメソッドは一般的な用途向きではありませんが、Swingのような軽量Componentライブラリ用のフックとして用意されています。
フォーカス・トラバーサル
各Componentは、指定されたトラバーサル操作に対して、独自のフォーカス・トラバーサル・キーのセットを定義します。 Componentは、フォワードおよびバックワードのトラバーサル用に別個のキーのセットをサポートし、1つ上のフォーカス・トラバーサル・サイクルに移動するためのキーのセットもサポートします。 フォーカス・サイクル・ルートであるContainerは、1つ下のフォーカス・トラバーサル・サイクルに移動するためのキーのセットもサポートします。 Componentに対してセットが明示的に定義されていない場合、そのComponentは親から再帰的にセットを継承し、最終的には現在のKeyboardFocusManager
のコンテキスト全体のデフォルト・セットを継承します。
AWTKeyStroke
APIを使用すると、2つの特定のKeyEvent (KEY_PRESSED
とKEY_RELEASED
)のどちらでフォーカス・トラバーサルが発生するかをクライアント・コードで指定できます。 ただし、指定されるKeyEventに関係なく、関連付けられるKEY_TYPED
イベントを含む、フォーカス・トラバーサル・キーに関連するすべてのKeyEventは消費され、ほかのComponentへのディスパッチは行われません。 KEY_TYPED
イベントのフォーカス・トラバーサル操作へのマッピング、任意のComponentまたはKeyboardFocusManager
のデフォルトに対して同一イベントの複数のフォーカス・トラバーサル操作へのマッピングは実行時エラーになります。
デフォルトのフォーカス・トラバーサル・キーは実装に依存します。 Sunでは、特定のネイティブなプラットフォームに対するすべての実装で同じキーを使用することをお薦めします。 WindowsおよびUnixに対する推奨は次にリストされています。
- 次のComponentへのフォワード・トラバーサル:
TextArea:CTRL-TAB
(KEY_PRESSED
)
その他すべて:TAB
(KEY_PRESSED
)およびCTRL-TAB
(KEY_PRESSED
) - 前のComponentへのバックワード・トラバーサル:
TextArea:CTRL-SHIFT-TAB
(KEY_PRESSED
)
その他すべて:SHIFT-TAB
(KEY_PRESSED
)およびCTRL-SHIFT-TAB
(KEY_PRESSED
) - 1つ上のフォーカス・トラバーサル・サイクルにトラバース: <なし>
- 1つ下のフォーカス・トラバーサル・サイクルにトラバース: <なし>
Componentは、フォーカス・トラバーサル・キーをComponent.setFocusTraversalKeysEnabled
を使用してすべてまとめて有効または無効にできます。 フォーカス・トラバーサル・キーが無効の場合、Componentはこれらのキーに対するすべてのKeyEventを受け取ります。 フォーカス・トラバーサル・キーが有効の場合、Componentはトラバーサル・キーのKeyEventを受け取ることはなく、KeyEventはフォーカス・トラバーサル操作に自動的にマッピングされます。
通常のフォワードおよびバックワードのトラバーサルでは、AWTのフォーカスの実装は、次にどのComponentにフォーカスするかを、フォーカス所有者のフォーカス・サイクル・ルートまたはフォーカス・トラバーサル・ポリシー・プロバイダのFocusTraversalPolicy
に基づいて決定します。 フォーカス所有者がフォーカス・サイクルのルートの場合、通常のフォーカス・トラバーサルの間にどのComponentが次または前のComponentを表すかについて、あいまいになる場合があります。 したがって、現在のKeyboardFocusManager
は「現在の」フォーカス・サイクル・ルートへの参照を維持しており、これはすべてのコンテキストにまたがってグローバルです。 現在のフォーカス・サイクル・ルートは、あいまいさを解決するために使用されます。
アップサイクル・トラバーサルでは、フォーカス所有者は、現在のフォーカス所有者のフォーカス・サイクル・ルートに設定され、現在のフォーカス・サイクル・ルートは新しいフォーカス所有者のフォーカス・サイクル・ルートに設定されます。 ただし、現在のフォーカス所有者のフォーカス・サイクル・ルートがトップレベル・ウィンドウの場合、フォーカス所有者はフォーカス・サイクル・ルートのデフォルトでフォーカスするコンポーネントに設定され、現在のフォーカス・サイクル・ルートは変更されません。
ダウンサイクル・トラバーサルでは、現在のフォーカス所有者がフォーカス・サイクル・ルートである場合は、フォーカス所有者は、現在のフォーカス所有者のデフォルトでフォーカスするコンポーネントに設定され、現在のフォーカス・サイクル・ルートは現在のフォーカス所有者に設定されます。 現在のフォーカス所有者がフォーカス・サイクル・ルートではない場合、フォーカス・トラバーサル操作は行われません。
FocusTraversalPolicy
FocusTraversalPolicy
は、あるフォーカス・サイクル・ルートまたはフォーカス・トラバーサル・ポリシー・プロバイダ内のComponentのトラバース順序を定義します。 FocusTraversalPolicy
のインスタンスはContainer間で共有できるため、それらのContainerは同じトラバーサル・ポリシーを実装できます。 FocusTraversalPolicyは、フォーカス・トラバーサル・サイクル階層が変わっても再度初期化する必要はありません。
各FocusTraversalPolicy
は、次の5つのアルゴリズムを定義する必要があります。
- フォーカス・サイクル・ルートとそのサイクル内のComponent aが指定された場合の、aの次のComponent。
- フォーカス・サイクル・ルートとそのサイクル内のComponent aが指定された場合の、aの前のComponent。
- フォーカス・サイクル・ルートが指定された場合の、そのサイクルの「最初の」Component。 「最初の」Componentは、フォワード方向のトラバーサルがラップするときに、フォーカスするComponentです。
- フォーカス・サイクル・ルートが指定された場合の、そのサイクルの「最後の」Component。 「最後の」Componentは、バックワード方向のトラバーサルがラップするときに、フォーカスするComponentです。
- フォーカス・サイクル・ルートが指定された場合の、そのサイクルの「デフォルト」Component。 「デフォルト」Componentは、下にトラバースして新しいフォーカス・トラバーサル・サイクルが開始されたときに、最初にフォーカスが設定されるComponentです。 これは「最初の」Componentと同じ場合もありますが、同じである必要はありません。
FocusTraversalPolicy
は、オプションで次のアルゴリズムを提供する場合もあります。
Windowが指定された場合の、そのWindow内の「初期」Component。 初期ComponentはWindowが最初に表示されたときに最初にフォーカスを受け取ります。 デフォルトでは、「デフォルト」Componentと同じです。さらに、Swingは
FocusTraversalPolicy
のサブクラスInternalFrameFocusTraversalPolicy
を提供し、開発者はこれを使用して次のアルゴリズムを提供できます。
JInternalFrame
が指定された場合の、そのJInternalFrame
の「初期」Component。 初期ComponentはJInternalFrame
が最初に選択されたときに最初にフォーカスを受け取ります。 デフォルトでは、JInternalFrame
のデフォルトでフォーカスするComponentと同じです。
FocusTraversalPolicy
は、Container.setFocusTraversalPolicy
を使用してContainerにインストールされます。 ポリシーが明示的に設定されていない場合は、Containerはもっとも近いフォーカス・サイクル・ルートの上位Containerからポリシーを継承します。 トップ・レベルは、コンテキストのデフォルト・ポリシーを使用してフォーカス・トラバーサル・ポリシーを初期化します。 コンテキストのデフォルト・ポリシーは、KeyboardFocusManager.setDefaultFocusTraversalPolicy
を使用して確立されます。
AWTは、クライアント・コードで使用できる2つの標準FocusTraversalPolicy
実装を提供します。
ContainerOrderFocusTraversalPolicy
: フォーカス・トラバーサル・サイクル内のComponentすべてを、ComponentがContainerに追加された順で反復します。 各Componentは、accept(Component)メソッドを使用して適合性をテストされます。 デフォルトでは、Componentは可視性、表示可能性、有効性、フォーカス可能性のすべてを満たす場合にのみ適合します。- デフォルトでは、ContainerOrderFocusTraversalPolicyは暗黙にフォーカスを下のサイクルに転送します。 つまり、通常のフォワード・フォーカス・トラバーサル中は、フォーカス・サイクル・ルートがトラバース可能またはトラバース不可能のどちらのContainerであるかには関係なく、フォーカス・サイクル・ルートのあとにトラバースされたComponentが、そのフォーカス・サイクル・ルートのデフォルトでフォーカスするComponentになります(下の図1、2を参照してください)。 このような動作により、アップ・サイクルおよびダウンサイクル・トラバーサルの概念なしで設計されたアプリケーションとの、下位互換性が提供されます。
DefaultFocusTraversalPolicy
: 適合性テストを再定義するContainerOrderFocusTraversalPolicy
のサブクラス。 クライアント・コードのComponent.isFocusTraversable()
またはComponent.isFocusable()
のオーバーライド、またはComponent.setFocusable(boolean)
の呼出しによって、Componentのフォーカス可能性を明示的に設定した場合は、DefaultFocusTraversalPolicy
はContainerOrderFocusTraversalPolicy
とまったく同じように動作します。 デフォルトのフォーカス可能性を使用する場合は、DefaultFocusTraversalPolicy
はフォーカス不可能なピアを持つコンポーネントをすべて拒否します。
ピアがフォーカス可能かどうかは実装で決定されます。 Sunでは、特定のネイティブ・プラット・フォームのすべての実装に対して、フォーカス可能性が同じピアの構築をお薦めします。 WindowsおよびUnixについては、Canvas、Label、Panel、Scrollbar、ScrollPane、Window、軽量Componentに対してはフォーカス不可能なピアを、それ以外のComponentについてはフォーカス可能なピアをお薦めします。 これらの推奨はSun AWTの実装で使用されます。 Componentのピアのフォーカス可能性は、Component自体のフォーカス可能性とは異なり、また影響も与えません。
Swingは、クライアント・コードで使用する2つの追加の標準FocusTraversalPolicy実装を提供します。 各実装はInternalFrameFocusTraversalPolicyです。
- SortingFocusTraversalPolicy: 特定のComparatorに基づいてフォーカス・トラバーサル・サイクルのComponentをソートすることによってトラバーサルの順序を判定します。 各Componentは、accept(Component)メソッドを使用して適合性をテストされます。 デフォルトでは、Componentは可視性、表示可能性、有効性、フォーカス可能性のすべてを満たす場合にのみ適合します。
- デフォルトでは、SortingFocusTraversalPolicyは暗黙にフォーカスを下のサイクルに転送します。 つまり、通常のフォワード・フォーカス・トラバーサル中は、フォーカス・サイクル・ルートがトラバース可能またはトラバース不可能のどちらのContainerであるかには関係なく、フォーカス・サイクル・ルートのあとにトラバースされたComponentが、そのフォーカス・サイクル・ルートのデフォルトでフォーカスするComponentになります(下の図1、2を参照してください)。 このような動作により、アップ・サイクルおよびダウンサイクル・トラバーサルの概念なしで設計されたアプリケーションとの、下位互換性が提供されます。
- LayoutFocusTraversalPolicy: サイズ、位置、方向に基づいてComponentをソートするSortingFocusTraversalPolicyのサブクラスです。 Componentは、サイズと位置に基づいて、大まかに行と列に分類されます。 水平方向のContainerの場合、列は左から右または右から左に並べられ、行は上から下に並べられます。 垂直方向のContainerの場合、列は上から下に並べられ、行は左から右または右から左に並べられます。 行内の列がすべてトラバースされてから、次の行に進みます。
さらに、適合性テストは拡張され、空のInputMapを持つかまたは継承するJComponentを除外します。
次の図は、暗黙的なフォーカス移動を示します。
次のことを前提にしています。
- A、B、およびCはあるウィンドウ(コンテナ)のコンポーネント
- Rはウィンドウ内のコンテナで、BおよびCの親。 また、Rはフォーカス・サイクル・ルート。
- Bは、Rのフォーカス・トラバーサル・サイクルのデフォルトのコンポーネント
- 図1では、Rはトラバース可能なContainerで、図2ではトラバース不可能なContainerです。
- このような場合、フォワード・トラバーサルは次のようになります。
- 図1: A -> R -> B -> C
- 図2: A -> B -> C
標準Look&FeelまたはBasicLookAndFeelから派生したLook&Feelを使用するSwingアプリケーションまたはSwing/AWTの混合アプリケーションは、デフォルトですべてのContainerに対してLayoutFocusTraversalPolicyを使用します。
純粋なAWTアプリケーションを含むその他のすべてのアプリケーションは、デフォルトでDefaultFocusTraversalPolicy
を使用します。
フォーカス・トラバーサル・ポリシー・プロバイダ
フォーカス・サイクル・ルートでないContainerには、独自のFocusTraversalPolicyを提供するオプションがあります。 そのためには、次の呼出しで、Containerのフォーカス・トラバーサル・ポリシー・プロバイダ・プロパティをtrue
に設定する必要があります。
Container.setFocusTraversalPolicyProvider(boolean)
Containerがフォーカス・トラバーサル・ポリシー・プロバイダであるかどうかを判断するには、次のメソッドを使用するべきです。
Container.isFocusTraversalPolicyProvider()
フォーカス・トラバーサル・ポリシー・プロバイダ・プロパティがフォーカス・サイクル・ルートに設定されている場合、フォーカス・トラバーサル・ポリシー・プロバイダとはみなされず、ほかのフォーカス・サイクル・ルートと同様に動作します。
フォーカス・サイクル・ルートとフォーカス・トラバーサル・ポリシー・プロバイダの間の主な違いは、後者はほかのすべてのContainerと同様に、フォーカスの出入りを許可することです。 ただし、フォーカス・トラバーサル・ポリシー・プロバイダ内の子は、プロバイダのFocusTraversalPolicyによって決定される順序でトラバースされます。 フォーカス・トラバーサル・ポリシー・プロバイダがこの方法で動作するようにするために、FocusTraversalPolicyはこれらを次のように処理します。
- フォーカス・サイクル・ルートの代わりにフォーカス・トラバーサル・ポリシー・プロバイダをFocusTraversalPolicyメソッドに渡すことができます。
-
FocusTraversalPolicy.getComponentAfter
またはFocusTraversalPolicy.getComponentBefore
で次または前のComponentを計算するときに、- Componentがフォーカス・トラバーサル・ポリシー・プロバイダの子である場合、このComponentの次と前のComponentはこのフォーカス・トラバーサル・ポリシー・プロバイダのFocusTraversalPolicyを使用して決定されます。 ただし、フォーカスがプロバイダから離れるために、次のルールが適用されます。
- ある時点で見つかった
next
のComponentがフォーカス・トラバーサル・ポリシー・プロバイダのfirst
のComponentである場合、フォーカス・トラバーサル・ポリシー・プロバイダの後ろのComponentが返されます - ある時点で見つかった
previous
のComponentがフォーカス・トラバーサル・ポリシー・プロバイダのlast
のComponentである場合、フォーカス・トラバーサル・ポリシー・プロバイダの前のComponentが返されます
- ある時点で見つかった
-
FocusTraversalPolicy.getComponentAfter
で次のComponentを計算するときに、- 取得されたComponentがトラバース不可能なContainerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、そのプロバイダのデフォルトのComponentが返されます
-
FocusTraversalPolicy.getComponentAfter
メソッドに渡されるComponentがトラバーサル可能なContainerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、このプロバイダのデフォルトのComponentが返されます
-
FocusTraversalPolicy.getComponentBefore
で前のComponentを計算するときに、- 取得されたComponentが(トラバース可能またはトラバース不可能な) Containerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、そのプロバイダの最後のComponentが返されます
- Componentがフォーカス・トラバーサル・ポリシー・プロバイダの子である場合、このComponentの次と前のComponentはこのフォーカス・トラバーサル・ポリシー・プロバイダのFocusTraversalPolicyを使用して決定されます。 ただし、フォーカスがプロバイダから離れるために、次のルールが適用されます。
- FocusTraversalPolicy.getFirstComponentで最初のComponentを計算するときに、
- 取得されたComponentがトラバース不可能なContainerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、そのプロバイダのデフォルトのComponentが返されます
- 取得されたComponentがトラバース不可能なContainerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、そのContainer自体が返されます
- FocusTraversalPolicy.getLastComponentで最後のComponentを計算するときに、
- 取得されたComponentが(トラバース可能またはトラバース不可能な) Containerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、そのプロバイダの最後のComponentが返されます
プログラムによるトラバーサル
ユーザーが開始するフォーカス・トラバーサルに加えて、クライアント・コードでフォーカス・トラバーサル操作をプログラムにより開始することもできます。 クライアント・コードでは、プログラムによるトラバーサルはユーザーが開始するトラバーサルと区別できません。 プログラムによるトラバーサルの開始に推奨される方法は、KeyboardFocusManager
の次のメソッドのいずれかを使用することです。
KeyboardFocusManager.focusNextComponent()
KeyboardFocusManager.focusPreviousComponent()
KeyboardFocusManager.upFocusCycle()
KeyboardFocusManager.downFocusCycle()
これらの各メソッドは、現在のフォーカス所有者のトラバーサル操作を開始します。 現在フォーカス所有者が存在しない場合は、トラバーサル操作は生じません。 さらに、フォーカス所有者がフォーカス・サイクル・ルートではない場合、downFocusCycle()はトラバーサル操作を実行しません。
KeyboardFocusManager
は、これらのメソッドの次のバリエーションもサポートします。
KeyboardFocusManager.focusNextComponent(Component)
KeyboardFocusManager.focusPreviousComponent(Component)
KeyboardFocusManager.upFocusCycle(Component)
KeyboardFocusManager.downFocusCycle(Container)
代替の同等なAPIがComponentクラスおよびContainerクラス自体にも定義されています。
Component.transferFocus()
Component.transferFocusBackward()
Component.transferFocusUpCycle()
Container.transferFocusDownCycle()
KeyboardFocusManager
のバリエーションと同様に、これらの各メソッドは、そのComponentがフォーカス所有者であるかのようにトラバーサル操作を開始します(フォーカス所有者である必要はありません)。
また、フォーカス所有者を、直接または祖先を経由して間接的に非表示または無効にしたり、表示不可能またはフォーカス不可能にしたりすると、自動的にフォワード・フォーカス・トラバーサルが開始されます。 軽量または重量の祖先を非表示にすると、その子は常に間接的に非表示になりますが、上位の重量の祖先を無効にした場合のみ、その子が無効になります。 したがって、フォーカス所有者の軽量の祖先を無効にしても、フォーカス・トラバーサルは自動的には開始されません。
クライアント・コードがフォーカス・トラバーサルを開始し、ほかにフォーカスするComponentがない場合、フォーカス所有者は変更されません。 クライアント・コードがフォーカス所有者を直接または間接的に非表示にするか、またはフォーカス所有者を表示不可能またはフォーカス不可能にすることによって自動的なフォーカス・トラバーサルを開始し、ほかにフォーカスするComponentがない場合、グローバル・フォーカス所有者はクリアされます。 クライアント・コードがフォーカス所有者を直接または間接的に無効することによって自動的なフォーカス・トラバーサルを開始し、ほかにフォーカスするComponentがない場合、フォーカス所有者は変更されません。
フォーカス可能性
フォーカス可能なComponentは、フォーカス所有者になることができ(「フォーカス可能性」)、FocusTraversalPolicyを使用してキーボード・フォーカス・トラバーサルに参加します(「フォーカス・トラバーサル可能性」)。 2つの概念に区別はなく、Componentはフォーカス可能かつフォーカス・トラバーサル可能であるか、またはどちらも不可能である必要があります。 Componentは、isFocusable()メソッドを介してこの状態を表します。 デフォルトでは、すべてのComponentがこのメソッドからtrueを返します。 クライアント・コードは、Component.setFocusable(boolean)を呼び出すことでこのデフォルトを変更できます。
フォーカス可能なWindow
パレット・ウィンドウおよびインプット・メソッドをサポートするために、クライアント・コードはWindowがフォーカスされたWindowにならないようにすることができます。 移行性によって、Windowまたはその子孫がフォーカス所有者になることを防ぎます。 フォーカス不可能なWindowは、フォーカス可能なWindowを引き続き所有できます。 デフォルトでは、すべてのFrameおよびDialogはフォーカス可能です。 もっとも近くの所有するFrameまたはDialogが画面に表示されており、フォーカス・トラバーサル・サイクルに少なくともその1つのComponentが含まれる場合、FrameでもDialogでもないWindowについてもすべて、デフォルトでフォーカス可能です。 Windowをフォーカス不可能にするには、Window.setFocusableWindowState(false)を使用します。
Windowがフォーカス不可能の場合、この制約はKeyboardFocusManager
がWindowのWINDOW_GAINED_FOCUS
イベントを検知したときに実施されます。 この時点で、フォーカス変更は拒否され、フォーカスは別のWindowにリセットされます。 拒否復旧スキームは、VetoableChangeListener
がフォーカス変更を拒否した場合と同じです。 「フォーカスとVetoableChangeListener」を参照してください。
新しいフォーカスの実装では、Windowまたはその子孫のKeyEventがWindowの所有者の子を介してプロキシされる必要があり、イベントを受け取るためにこのプロキシがX11上でマップされている必要があるため、もっとも近くの所有するFrameまたはDialogが表示されていないWindowは、X11でKeyEventを決して受け取れません。 この制約をサポートするために、Windowの「ウィンドウ・フォーカス可能性」とその「ウィンドウ・フォーカス可能性状態」とを区別しました。 Windowのフォーカス可能性状態とWindowのもっとも近くの所有するFrameまたはDialogの表示状態を組み合わせて、Windowのフォーカス可能性が判断されます。 デフォルトで、すべてのWindowはtrueのフォーカス可能性状態を持っています。 Windowのフォーカス可能性状態をfalseに設定すると、もっとも近くの所有するFrameまたはDialogの表示状態にかかわらず、フォーカスされたWindowにならないことが保証されます。
Swingでは、アプリケーションはnullの所有者を持つJWindowを作成できます。 Swingでは、このようなすべてのJWindowはprivateで非表示のFrameに所有されるように構築されます。 このFrameの表示状態は常にfalseになるため、nullの所有者で構築されたJWindowは、Windowのフォーカス可能性状態がtrueであっても、フォーカスされたWindowになることは決してできません。
フォーカスされたWindowがフォーカス不可能になった場合は、AWTは、Windowの所有者の直近にフォーカスされたComponentにフォーカスしようとします。 したがって、Windowの所有者が新しくフォーカスされたWindowになります。 Windowの所有者もフォーカス不可能なWindowである場合は、フォーカス変更リクエストは所有者の階層を再帰的に上に移動します。 すべてのプラットフォームがWindowをまたがるフォーカス変更をサポートするわけではないので(「フォーカスのリクエスト」を参照してください)、このようなフォーカス変更リクエストがすべて失敗する可能性があります。 この場合、グローバル・フォーカス所有者はクリアされ、フォーカスされたWindowは変更されません。
フォーカスのリクエスト
Componentは、フォーカス所有者になることをComponent.requestFocus()
の呼出しによってリクエストできます。 これにより、Componentが表示可能、フォーカス可能、可視で、すべての上位Component (トップ・レベルWindowを除く)が可視である場合にかぎり、Componentへのパーマネント・フォーカス転送が開始されます。 これらの条件のいずれかが満たされない場合は、リクエストはただちに拒否されます。 無効なComponentがフォーカス所有者になる場合もありますが、この場合は、すべてのKeyEventは破棄されます。
Componentのトップ・レベルWindowがフォーカスされたWindowではなく、プラットフォームがWindow間でのフォーカスのリクエストをサポートしない場合にも、リクエストは拒否されます。 この理由でリクエストが拒否された場合、リクエストは記憶され、あとでそのWindowがユーザーによってフォーカスされたときに許可されます。 それ以外の場合、フォーカス変更リクエストによって、フォーカスされたWindowも変更されます。
フォーカス変更リクエストが許可されたかどうかを同期的に判断する方法はありません。 代わりに、クライアント・コードはFocusListenerをComponentにインストールし、FOCUS_GAINED
イベントの配信を監視する必要があります。 クライアント・コードでは、このイベントを受け取るまで、Componentがフォーカス所有者であるとみなしてはいけません。 イベントは、requestFocus()
が戻る前に配信されるとはかぎりません。 開発者は、いずれかの動作を想定してはいけません。
AWTは、すべてのフォーカス変更リクエストがEventDispatchThreadで行われる場合には、先打ちをサポートします。 クライアント・コードがフォーカスの変更をリクエストし、AWTがこのリクエストはネイティブ・ウィンドウ・システムによって許可されるものであると判定した場合、AWTは現在のKeyboardFocusManagerに対して、現在処理中のイベントのタイムスタンプよりもあとのタイムスタンプですべてのKeyEventをキューに入れるべきであることを通知します。 これらのKeyEventは、新しいComponentがフォーカス所有者になるまでディスパッチされません。 AWTは、フォーカス変更がネイティブ・レベルで成功しなかった場合、Componentのピアが破棄された場合、およびVetoableChangeListenerによってフォーカス変更が拒否された場合は、遅延されたディスパッチ・リクエストを取り消します。 KeyboardFocusManagerは、フォーカス変更リクエストがEventDispatchThread以外のスレッドから行われた場合、先打ちをサポートする必要はありません。
Component.requestFocus()
はプラットフォーム間で一貫した方法で実装できないため、開発者は代わりにComponent.requestFocusInWindow()
を使用することが推奨されます。 このメソッドは、すべてのプラットフォーム上でWindow間のフォーカス移動を自動的に拒否します。 フォーカス移動のプラットフォーム固有の唯一の要素を排除することで、このメソッドはプラットフォーム間で一貫した動作を実現します。
さらに、requestFocusInWindow()
はブール値を返します。 「false」が返された場合、リクエストは確実に失敗します。 「true」が返された場合、通常、リクエストは正常に処理されます。ただし、許可されない、またはComponentのピアが破棄されるなどの異常なイベントが、ネイティブのウィンドウ・システムでリクエストを許可する前に発生した場合は正常に処理されません。 「true」が返された場合、リクエストは正常に処理される可能性が高いのですが、このComponentがFOCUS_GAINED
イベントを受け取るまでは、このComponentがフォーカス所有者であることを決して想定しないでください。
クライアント・コードで、アプリケーション内のどのComponentもフォーカス所有者にしない場合は、現在のKeyboardFocusManager
のメソッドKeyboardFocusManager
. clearGlobalFocusOwner()
を呼び出すことができます。 このメソッドが呼び出されたときにフォーカス所有者が存在する場合、フォーカス所有者はパーマネントFOCUS_LOST
イベントを受け取ります。 これ以降、AWTフォーカス実装は、ユーザーまたはクライアント・コードが明示的にフォーカスをComponentに設定するまで、すべてのKeyEventを破棄します。
Componentクラスは、クライアント・コードがテンポラリ状態を指定できるrequestFocus
およびrequestFocusInWindow
のバリエーションもサポートします。 テンポラリFocusEventsを参照してください
フォーカスとPropertyChangeListener
クライアント・コードは、PropertyChangeListenerを介して、コンテキスト全体でのフォーカス状態の変更や、Component内のフォーカス関連の状態の変更を待機できます。
KeyboardFocusManager
は次のプロパティをサポートしています。
focusOwner
: フォーカス所有者focusedWindow
: フォーカスされたWindowactiveWindow
: アクティブWindowdefaultFocusTraversalPolicy
: デフォルトのフォーカス・トラバーサル・ポリシーforwardDefaultFocusTraversalKeys
: デフォルトのFORWARD_TRAVERSAL_KEYS
のセットbackwardDefaultFocusTraversalKeys
: デフォルトのBACKWARD_TRAVERSAL_KEYS
のセットupCycleDefaultFocusTraversalKeys
: デフォルトのUP_CYCLE_TRAVERSAL_KEYS
のセットdownCycleDefaultFocusTraversalKeys
: デフォルトのDOWN_CYCLE_TRAVERSAL_KEYS
のセットcurrentFocusCycleRoot
: 現在のフォーカス・サイクル・ルート
現在のKeyboardFocusManager
にインストールされたPropertyChangeListener
は、フォーカス所有者、フォーカスされたWindow、アクティブWindow、および現在のフォーカス・サイクル・ルートがすべてのコンテキストで共有されるグローバル・フォーカス状態を構成するにもかかわらず、KeyboardFocusManager
のコンテキスト内のこれらの変更のみを検知します。 これは、クライアント・コードがPropertyChangeListener
をインストールする前にセキュリティ・チェックにパスすることを義務付けるよりはわずらわしくないと考えられます。
Componentは次のフォーカス関連のプロパティをサポートしています。
focusable
: Componentのフォーカス可能性focusTraversalKeysEnabled
: Componentのフォーカス・トラバーサル・キーの(有効かどうかの)状態forwardFocusTraversalKeys
: ComponentのFORWARD_TRAVERSAL_KEYS
のセットbackwardFocusTraversalKeys
: ComponentのBACKWARD_TRAVERSAL_KEYS
のセットupCycleFocusTraversalKeys
: ComponentのUP_CYCLE_TRAVERSAL_KEYS
のセット
Containerは、Componentのプロパティに加えて次のフォーカス関連のプロパティをサポートしています。
downCycleFocusTraversalKeys
: ContainerのDOWN_CYCLE_TRAVERSAL_KEYS
のセットfocusTraversalPolicy
: Containerのフォーカス・トラバーサル・ポリシーfocusCycleRoot
: Containerのフォーカス・サイクル・ルートの状態
Windowは、Containerのプロパティに加えて次のフォーカス関連のプロパティをサポートしています。
focusableWindow
: Windowのフォーカス可能なWindow状態
WindowにインストールされたPropertyChangeListener
は、focusCycleRoot
プロパティのPropertyChangeEvent
を決して検出しません。 Windowは常にフォーカス・サイクル・ルートであり、このプロパティは変更できません。
フォーカスとVetoableChangeListener
KeyboardFocusManager
は次のプロパティに対してVetoableChangeListener
もサポートしています。
- "focusOwner": フォーカス所有者
- "focusedWindow": フォーカスされたWindow
- "activeWindow": アクティブWindow
VetoableChangeListenerは、KeyboardFocusManagerで変更が反映される前に状態変更の通知を受けます。 逆に、PropertyChangeListenerは変更が反映されたあとで通知を受け取ります。 したがって、すべてのVetoableChangeListenerはどのPropertyChangeListenerよりも前に通知を受け取ります。
VetoableChangeListenerはべき等である必要があり、特定のフォーカス変更に関して喪失と取得の両方のイベントを拒否する必要があります(FOCUS_LOST
とFOCUS_GAINED
など)。 たとえば、VetoableChangeListener
がFOCUS_LOST
イベントを拒否する場合、KeyboardFocusManager
はEventQueue
を検索して関連する保留中のFOCUS_GAINED
イベントを削除する必要はありません。 代わりに、KeyboardFocusManager
はこのイベントをディスパッチすることができ、このイベントを同様に拒否することはVetoableChangeListener
の責任です。 さらに、FOCUS_GAINED
イベントの処理中に、KeyboardFocusManager
が別のFOCUS_LOST
イベントを合成することによってグローバル・フォーカス状態を再同期しようとする場合があります。 このイベントは最初のFOCUS_LOST
イベントと同様に拒否される必要があります。
KeyboardFocusManager
は、PropertyChangeListener
に状態変更を通知する間、ロックを保持しません。 ただし、この要件はVetoableChangeListeners
に関しては緩和されます。 したがって、クライアント定義のVetoableChangeListener
は、デッドロックが発生する可能性があるので、vetoableChange(PropertyChangeEvent)
内で追加のロックを取得することを避けるべきです。 フォーカスまたはアクティブ化の変更が拒否されると、KeyboardFocusManagerは拒否回復を次のように開始します。
- フォーカスされたWindowまたはアクティブWindowの変更が拒否された場合は、フォーカスされたWindowまたはアクティブWindowは以前にフォーカスされたWindowまたはアクティブWindowであったWindowにリセットされます。 そのようなWindowが存在しない場合は、
KeyboardFocusManager
はグローバル・フォーカス所有者をクリアします。 - フォーカス所有者の変更が拒否された場合は、フォーカス所有者は以前にフォーカス所有者であったComponentにリセットされます。 それが不可能な場合は、フォーカス・トラバーサル・サイクル内の、以前のフォーカス所有者の次のComponentにリセットされます。 それも不可能な場合は、
KeyboardFocusManager
はグローバル・フォーカス所有者をクリアします。
VetoableChangeListener
は、拒否回復の結果として開始されたフォーカス変更を拒否しないように注意する必要があります。 この状況を想定しないと、拒否されたフォーカス変更と回復の試行の無限サイクルに陥ることがあります。
Z軸順
一部のネイティブ・ウィンドウ・システムでは、WindowのZ軸順がフォーカス状態またはアクティブ状態(該当する場合)に影響することがあります。 Microsoft Windowsでは、最前面のWindowは当然フォーカスされたWindowでもあります。 しかしSolarisでは、多くのウィンドウ・マネージャが、フォーカスされたWindowを決定するときに、ポイントしてフォーカスするモデルを使用し、Z軸順を無視します。 Windowをフォーカスまたはアクティブ化するとき、AWTはネイティブ・プラット・フォームのUI要件に準拠します。 したがって、Z軸順に関連する次のようなメソッドのフォーカスの動作は、
Window.toFront()
Window.toBack()
Window.show()
Window.hide()
Window.setVisible(boolean)
Window.dispose()
Frame.setState(int)
Window.toFront()
:
Microsoft Windows:可能であれば、Windowが前面に移動されます。 このWindowを、同じVM内のほかのWindowの前に移動することは常に可能ですが、Windows 98およびWindows 2000では、アプリケーションがウィンドウを前面に移動するためには、アプリケーションのウィンドウの1つがすでにフォアグラウンドにあることが必要です。 フォアグラウンドにない場合は、Windowsは代わりにタスク・バー内のWindowのアイコンを点滅させます。 Windowが前面に移動される場合は、フォーカスされ、(該当する場合)アクティブWindowになります。
Solaris: Windowが前面に移動されます。 ポイントしてフォーカスするウィンドウ・マネージャでは、カーソルの下にある最前面のWindowであればそのWindowはフォーカスされたWindowになります。 クリックしてフォーカスするウィンドウ・マネージャでは、フォーカスされたWindowは変更されないままになります。Window.toBack()
:
Microsoft Windows:Windowが背面に移動されます。 ただし、Microsoft Windowsでは、所有されるWindowはその再帰的なすべての所有者よりも常に前にあることが必要です。 そのため、この操作が完了したあと、このWindowはZ軸順で最下位のJava Windowでない可能性があります。 Windowまたはその所有者がフォーカスされたWindowであった場合、フォーカスされたWindowはVM内の最前面のWindowにリセットされます。
Solaris: Windowが背面に移動されます。 Microsoft Windowsと同様に、一部のウィンドウ・マネージャでは、所有されるWindowはその再帰的なすべての所有者よりも常に前にあることが必要です。 そのため、この操作が完了したあと、このWindowはZ軸順で最下位のJava Windowでない可能性があります。 WindowがフォーカスされたWindowであった場合は、ポイントしてフォーカスするウィンドウ・マネージャでは、カーソルの下にある最前面のWindowでなくなるとフォーカスを失います。 クリックしてフォーカスするウィンドウ・マネージャでは、フォーカスされたWindowは変更されないままになります。Window.show()/Window.setVisible(true)/Frame.setState(NORMAL)
:
Microsoft Windows:Windowが前面に移動され、フォーカスされたWindowになります。
Solaris: Windowが前面に移動されます。 ポイントしてフォーカスするウィンドウ・マネージャでは、そのWindowが現在カーソルの下にある最前面のWindowであれば、フォーカスされたWindowになります。 クリックしてフォーカスするウィンドウ・マネージャでは、そのWindowがフォーカスされたWindowになります。Window.hide()/Window.setVisible(false)/Window.dispose()/Frame.setState(ICONIFIED)
:
Microsoft Windows:WindowがフォーカスされたWindowであった場合は、フォーカスされたWindowはOSによって選択されるウィンドウにリセットされるか、どのウィンドウにもリセットされません。 このウィンドウは、ネイティブ・アプリケーション、または別のVM内のJavaアプリケーションに存在する可能性があります。
Solaris: ポイントしてフォーカスするウィンドウ・マネージャでは、WindowがフォーカスされたWindowである場合は、カーソルの下にある最前面のWindowがフォーカスされたWindowになります。 クリックしてフォーカスするウィンドウ・マネージャでは、フォーカスされたWindowはウィンドウ・マネージャによって選択されるウィンドウにリセットされます。 このウィンドウは、ネイティブ・アプリケーション、または別のVM内のJavaアプリケーションに存在する可能性があります。
DefaultKeyboardFocusManagerの置換え
KeyboardFocusManager
は、ブラウザ・コンテキスト・レベルでプラガブルです。 クライアント・コードは、KeyboardFocusManager
またはDefaultKeyboardFocusManager
をサブクラス化して、フォーカス、FocusEvent、およびKeyEventに関連するWindowEventの処理方法とディスパッチ方法を変更できます。また、グローバル・フォーカス状態を調べて変更できます。 カスタムのKeyboardFocusManager
は、FocusListenerやWindowListenerでは不可能な根本的なレベルで、フォーカス変更を拒否することもできます。
KeyboardFocusManager
を全体的に置き換えると、開発者はフォーカス・モデルを完全に制御できますが、これは、ピア・フォーカス・レイヤーを完全に理解することが必要な困難なプロセスです。 ほとんどのアプリケーションではこのレベルでの制御は不要です。 開発者は、KeyboardFocusManager
を全体的に置き換えるという手段をとる前に、このドキュメントで説明するKeyEventDispatcher、KeyEventPostProcessor、FocusTraversalPolicy、VetoableChangeListener、およびそのほかの概念を使用することが推奨されます。
制約を受けることなくほかのコンテキスト内のComponentにアクセスできるということはセキュリティ・ホールを意味するため、SecurityManagerは、クライアント・コードがKeyboardFocusManager
を任意のサブクラスのインスタンスに置き換えることを許可する前に、新しいアクセス権「replaceKeyboardFocusManager」を付与する必要があります。 セキュリティ・チェックのため、ブラウザ内のアプレットなど、SecurityManagerが存在する環境に配置されるアプリケーションでは、KeyboardFocusManager
を置き換えるというオプションはありません。
KeyboardFocusManager
インスタンスは、インストールされると、protectedの関数のセットを介してグローバル・フォーカス状態にアクセスできます。 KeyboardFocusManager
は、呼出し元スレッドのコンテキスト内にインストールされている場合にかぎってこれらの関数を呼び出すことができます。 これにより、悪意のあるコードがKeyboardFocusManager.setCurrentFocusManager
のセキュリティ・チェックを回避できなくなります。 KeyboardFocusManager
は常に、コンテキストのフォーカス状態ではなくグローバルのフォーカス状態を処理するべきです。 そうしないと、KeyboardFocusManager
が正しく動作しなくなります。
KeyboardFocusManager
の主な責任は、次のイベントをディスパッチすることです。
- すべての
KeyEvent
- すべての
FocusEvent
WindowEvent.WINDOW_GAINED_FOCUS
WindowEvent.WINDOW_LOST_FOCUS
WindowEvent.WINDOW_ACTIVATED
WindowEvent.WINDOW_DEACTIVATED
KeyboardFocusManager
に対して、WINDOW_ACTIVATED
およびWINDOW_DEACTIVATED
以外のすべての上記イベントを提供します。 KeyboardFocusManager
は、適切な場合にはWINDOW_ACTIVATED
とWINDOW_DEACTIVATED
イベントを合成し、適切なターゲットに送信する必要があります。
KeyboardFocusManager
は、ピア・レイヤーによって提供されたイベントのターゲットを、自身の概念によるフォーカス所有者またはフォーカスされたWindowに変更する必要がある場合があります。
- KeyEventのターゲットをフォーカス所有者に変更する必要があります。 ピア・レイヤーは軽量Componentを認識しないため、KeyEventは、フォーカス所有者ではなくフォーカス所有者の重量Containerに対して送信されます。
FOCUS_LOST
イベントのターゲットをフォーカス所有者に変更する必要があります。 この場合も、ピア・レイヤーが軽量Componentを認識しないためこれが必要です。WINDOW_LOST_FOCUS
イベントのターゲットを、フォーカスされたWindowに変更する必要があります。 Windowクラスの実装によっては、ネイティブのフォーカスされたWindowとJavaのフォーカスされたWindowとが異なる場合があります。
KeyboardFocusManager
は、イベントが適切な順序であること、およびイベントとその反対のイベント・タイプとが一対一対応であることを保証する必要があります。 ピア・レイヤーは、これらを一切保証しません。 たとえば、ピア・レイヤーがWINDOW_GAINED_FOCUS
イベントの前にFOCUS_GAINED
イベントを送信する可能性があります。 WINDOW_GAINED_FOCUS
イベントがFOCUS_GAINED
イベントの前にディスパッチされることを保証することは、KeyboardFocusManager
の責任です。
KeyboardFocusManager
.redispatchEvent
を介してイベントを再ディスパッチする前に、KeyboardFocusManager
はグローバル・フォーカス状態の更新を試みる必要があります。 通常、これはKeyboardFocusManager.setGlobal*
メソッドのいずれかを使用して行われますが、実装は、独自のメソッドを実装することもできます。 KeyboardFocusManager
は、更新を試みたあとで、グローバル・フォーカス状態の変更が拒否されなかったことを確認する必要があります。 対応するgetGlobal*
メソッドの呼出しが、たった今設定した値と異なる値を返したときに、拒否されたことが検知されます。 次の3つの標準的な場合に拒否が発生します。
KeyboardFocusManager
がグローバル・フォーカス所有者をフォーカス不可能なComponentに設定しようとした場合。KeyboardFocusManager
がグローバルのフォーカスされたWindowをフォーカス不可能なWindowに設定しようとした場合。- インストールされている
VetoableChangeListener
によって変更が拒否された場合。
KeyboardFocusManager
のクライアント定義の実装は、拒否されるフォーカス移動のセットを、グローバル・フォーカス状態に対するアクセス用メソッドおよび変更用メソッドをオーバーライドすることによって調整できます。
グローバル・フォーカス状態の変更リクエストが拒否された場合、KeyboardFocusManager
はフォーカス変更リクエストを要求したイベントを破棄する必要があります。 イベントのターゲットであったComponentがこのイベントを受け取ってはいけません。
KeyboardFocusManager
は、フォーカスとVetoableChangeListenerで概説されているように、拒否回復を開始することも要求されます。
最後に、KeyboardFocusManagerは次のような特殊ケースを処理する必要があります。
WINDOW_GAINED_FOCUS
イベントを処理するときに、KeyboardFocusManager
はWindowの適切な子Componentにフォーカスを設定する必要があります。 Windowの子Componentが以前にフォーカスをリクエストしたが、Window間のフォーカス変更リクエストをプラットフォームがサポートしないため拒否された場合は、フォーカスはその子Componentに設定されるべきです。 それ以外の場合でWindowがこれまでにフォーカスされたことがない場合は、フォーカスはWindowのフォーカスを受け取る初期Componentに設定されるべきです。 Windowが前にフォーカスされていた場合は、フォーカスはWindowの最新のフォーカス所有者に設定されるべきです。KeyboardFocusManager
は、反対のComponentまたはWindowが、ネイティブ・ウィンドウ・プラットフォームが許可するかぎりできるだけ正確であることを保証する必要があります。 たとえば、KeyboardFocusManager
は、反対のComponentを、ピア・レイヤーが最初に指定した重量Componentの軽量な子Componentにターゲットを変更する必要がある場合があります。
ピア・レイヤーが、反対のComponentまたはWindowはnull
であると指定した場合、KeyboardFocusManager
はこの値を設定できます。null
は、フォーカスまたはアクティブ化の変更に、ほかのComponentやWindowが関与していなかった可能性が高いことを示します。 プラットフォームの限界により、この計算はヒューリスティックに依存し、不正確な場合があります。 ただし、このヒューリスティックはピア・レイヤーによる最善の推察です。- ComponentまたはWindowが自分自身に対してフォーカスまたはアクティブ化を失うようなフォーカスとアクティブ化の変更は破棄される必要があります。
- ピア・レイヤーによって送信された、フォーカスされたWindowにアクティブWindowがフォーカスを譲ったことを示すイベントは破棄される必要があります。 ピアのWindowクラスの実装によってこれらの見せかけのイベントが生成される場合があります。
以前のリリースとの非互換性
クロス・プラットフォームの変更:
- すべてのComponentの、デフォルトのフォーカス・トラバーサル可能性は「true」になりました。 以前は、一部のComponent (特にすべての軽量Component)のデフォルトのフォーカス・トラバーサル可能性は「false」でした。 ただし、この変更にかかわらず、すべてのAWT Containerの
DefaultFocusTraversalPolicy
は、以前のリリースのトラバーサル順序を維持します。 - フォーカス・トラバーサル不可能な(つまりフォーカス不可能な)Componentにフォーカスするリクエストは拒否されます。 以前は、このようなリクエストは許可されていました。
Window.toFront()
およびWindow.toBack()
は、Windowが不可視の場合は何も実行しなくなりました。 以前は、動作はプラットフォーム依存でした。Component
にインストールされたKeyListenerは、フォーカス・トラバーサル操作にマッピングされるKeyEvent
を認識しなくなり、このようなイベントに対してComponent.handleEvent()
は呼び出されなくなりました。 以前は、AWT Componentはこれらのイベントを認識し、AWTがフォーカス・トラバーサルを開始する前にイベントを消費する機会がありました。 代わりに、この機能を必要とするコードは、そのComponent
のフォーカス・トラバーサル・キーを無効にし、フォーカス・トラバーサル自体を処理するようにしてください。 あるいは、AWTEventListener
またはKeyEventDispatcher
を使用して、すべてのKeyEvent
を事前待機することもできます。
Microsoft Windows固有の変更:
- Z軸順の変更後、
Window.toBack()
はフォーカスされたWindowを最前面のWindowに変更します。 requestFocus()
は、すべての場合にWindow間のフォーカス変更リクエストを許可するようになりました。 以前は、リクエストは重量に関しては許可されていましたが、軽量に関しては拒否されていました。