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アプリケーションを作成できるはずです。このドキュメントには次のセクションがあります。
フォーカス・モデルは、現在のフォーカス状態の照会、フォーカス変更の開始、およびデフォルトのフォーカス・イベント・ディスパッチのカスタム・ディスパッチャへの置換をクライアント・コードが実行するためのAPIのセットを提供する、単独のKeyboardFocusManagerクラスに集中しています。クライアントはフォーカス状態を直接照会することも、フォーカス状態に変更があった場合にPropertyChangeEventを受け取るPropertyChangeListenerを登録することもできます。
KeyboardFocusManagerでは次の主な概念と用語が導入されます。
setFocusTraversalPolicyProvider
を使用して設定できます。
すべてのWindowおよびJInternalFrameは、デフォルトで「フォーカス・サイクル・ルート」です。それが唯一のフォーカス・サイクル・ルートである場合は、フォーカス可能なすべての下位Componentがフォーカス・サイクルに含まれるべきであり、そのフォーカス・トラバーサル・ポリシーは、通常のフォワード(またはバックワード)のトラバーサルの間にすべての下位Componentに到達できることを保証することによって、すべての下位Componentがフォーカス・サイクルに含まれるようにするべきです。一方、WindowまたはJInternalFrameにフォーカス・サイクル・ルートである下位Componentがある場合、このような各下位Componentは、自身がルートであるフォーカス・サイクルと、もっとも近いフォーカス・サイクル・ルートの上位Componentのフォーカス・サイクルの、2つのフォーカス・サイクルのメンバーになります。このような「下位」のフォーカス・サイクル・ルートのフォーカス・サイクルに所属するフォーカス可能なComponentにトラバースするためには、まず(フォワードまたはバックワードで)トラバースしてその下位のフォーカス・サイクル・ルートに到達し、次に「ダウンサイクル」操作を使用してその下位Componentに到達します。
次はその例です。
次のことを前提にしています。
Window
で、これはフォーカス・サイクル・ルートである必要があることを意味します。
Container
です。
Container
です。
Component
です。
KeyboardFocusManager
は抽象クラスです。AWTでは、DefaultKeyboardFocusManager
クラスにデフォルトの実装が提供されます。
一部のブラウザは、異なるコード・ベースのアプレットを別のコンテキストに分割し、これらのコンテキストの間に壁を構築します。各スレッドおよび各Componentは、特定のコンテキストに関連付けられ、ほかのコンテキスト内のスレッドに影響を与えたり、Componentにアクセスしたりすることはできません。このようなシナリオでは、コンテキストごとに1つのKeyboardFocusManagerがあります。別のブラウザは、すべてのアプレットを同じコンテキストに配置します。これは、すべてのアプレットに対して単一でグローバルなKeyboardFocusManagerのみがあることを示します。この動作は実装に依存します。詳細はブラウザのマニュアルを参照してください。ただし、存在するコンテキストの数にかかわらず、ClassLoaderあたり複数のフォーカスの所有者、フォーカスされたWindow、またはアクティブWindowが存在することはありません。
ユーザーの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を実装し、この機能を使用する際には同様の制限が適用されます。
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をクリックすると、次のイベントがディスパッチされ、順に処理されます。
WINDOW_ACTIVATED
イベントを受信します。
WINDOW_GAINED_FOCUS
イベントを受信します。
FOCUS_GAINED
イベントを受信します。
FOCUS_LOST
イベントを受信します。
WINDOW_LOST_FOCUS
イベントを受信します。
WINDOW_DEACTIVATED
イベントを受信します。
WINDOW_ACTIVATED
イベントを受信します。
WINDOW_GAINED_FOCUS
イベントを受信します。
FOCUS_GAINED
イベントを受信します。
さらに、各イベント・タイプは反対のイベント・タイプと一対一対応でディスパッチされます。たとえば、ComponentがFOCUS_GAINED
イベントを受け取った場合、間にFOCUS_LOST
を受け取ることなく別のFOCUS_GAINED
イベントを受け取ることは決してありません。
最後に、これらのイベントは情報目的だけで配信されることに注意してください。たとえば、前のFOCUS_LOST
イベントの処理中に、フォーカスを失うComponentにフォーカスを戻すことをリクエストすることによって、保留中のFOCUS_GAINED
イベントが配信されることを防ぐことはできません。クライアント・コードがこのようなリクエストを行う場合があっても、保留中のFOCUS_GAINED
は配信され、フォーカスを元のフォーカス所有者に戻すイベントがあとに続きます。
FOCUS_GAINED
イベントをどうしても抑制する必要がある場合は、クライアント・コードはフォーカスの変更を拒否するVetoableChangeListener
をインストールできます。「フォーカスとVetoableChangeListener」を参照してください。
各イベントには、フォーカスまたはアクティベーションの変更に関係する「反対の」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を使用する場合にはこのプラットフォームの制限を無視できます。
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に対する推奨は次にリストされています。
CTRL-TAB
(KEY_PRESSED
) TAB
(KEY_PRESSED
)およびCTRL-TAB
(KEY_PRESSED
)
CTRL-SHIFT-TAB
(KEY_PRESSED
) SHIFT-TAB
(KEY_PRESSED
)およびCTRL-SHIFT-TAB
(KEY_PRESSED
)
Componentは、フォーカス・トラバーサル・キーをComponent.setFocusTraversalKeysEnabled
を使用してすべてまとめて有効または無効にできます。フォーカス・トラバーサル・キーが無効の場合、Componentはこれらのキーに対するすべてのKeyEventを受け取ります。フォーカス・トラバーサル・キーが有効の場合、Componentはトラバーサル・キーのKeyEventを受け取ることはなく、KeyEventはフォーカス・トラバーサル操作に自動的にマッピングされます。
通常のフォワードおよびバックワードのトラバーサルでは、AWTのフォーカスの実装は、次にどのComponentにフォーカスするかを、フォーカス所有者のフォーカス・サイクル・ルートまたはフォーカス・トラバーサル・ポリシー・プロバイダのFocusTraversalPolicy
に基づいて決定します。フォーカス所有者がフォーカス・サイクルのルートの場合、通常のフォーカス・トラバーサルの間にどのComponentが次または前のComponentを表すかについて、あいまいになる場合があります。したがって、現在のKeyboardFocusManager
は「現在の」フォーカス・サイクル・ルートへの参照を維持しており、これはすべてのコンテキストにまたがってグローバルです。現在のフォーカス・サイクル・ルートは、あいまいさを解決するために使用されます。
アップサイクル・トラバーサルでは、フォーカス所有者は、現在のフォーカス所有者のフォーカス・サイクル・ルートに設定され、現在のフォーカス・サイクル・ルートは新しいフォーカス所有者のフォーカス・サイクル・ルートに設定されます。ただし、現在のフォーカス所有者のフォーカス・サイクル・ルートがトップレベル・ウィンドウの場合、フォーカス所有者はフォーカス・サイクル・ルートのデフォルトでフォーカスするコンポーネントに設定され、現在のフォーカス・サイクル・ルートは変更されません。
ダウンサイクル・トラバーサルでは、現在のフォーカス所有者がフォーカス・サイクル・ルートである場合は、フォーカス所有者は、現在のフォーカス所有者のデフォルトでフォーカスするコンポーネントに設定され、現在のフォーカス・サイクル・ルートは現在のフォーカス所有者に設定されます。現在のフォーカス所有者がフォーカス・サイクル・ルートではない場合、フォーカス・トラバーサル操作は行われません。
FocusTraversalPolicy
は、あるフォーカス・サイクル・ルートまたはフォーカス・トラバーサル・ポリシー・プロバイダ内のComponentのトラバース順序を定義します。FocusTraversalPolicy
のインスタンスはContainer間で共有できるため、それらのContainerは同じトラバーサル・ポリシーを実装できます。FocusTraversalPolicyは、フォーカス・トラバーサル・サイクル階層が変わっても再度初期化する必要はありません。
各FocusTraversalPolicy
は、次の5つのアルゴリズムを定義する必要があります。
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は可視性、表示可能性、有効性、フォーカス可能性のすべてを満たす場合にのみ適合します。
DefaultFocusTraversalPolicy
: 適合性テストを再定義するContainerOrderFocusTraversalPolicy
のサブクラス。クライアント・コードのComponent.isFocusTraversable()
またはComponent.isFocusable()
のオーバーライド、またはComponent.setFocusable(boolean)
の呼出しによって、Componentのフォーカス可能性を明示的に設定した場合は、DefaultFocusTraversalPolicy
はContainerOrderFocusTraversalPolicy
とまったく同じように動作します。デフォルトのフォーカス可能性を使用する場合は、DefaultFocusTraversalPolicy
はフォーカス不可能なピアを持つコンポーネントをすべて拒否します。Swingは、クライアント・コードで使用する2つの追加の標準FocusTraversalPolicy実装を提供します。各実装はInternalFrameFocusTraversalPolicyです。
次の図は、暗黙的なフォーカス移動を示します。
次のことを前提にしています。
標準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.getComponentAfter
またはFocusTraversalPolicy.getComponentBefore
で次または前のComponentを計算するときに、
next
のComponentがフォーカス・トラバーサル・ポリシー・プロバイダのfirst
のComponentである場合、フォーカス・トラバーサル・ポリシー・プロバイダの後ろのComponentが返されます
previous
のComponentがフォーカス・トラバーサル・ポリシー・プロバイダのlast
のComponentである場合、フォーカス・トラバーサル・ポリシー・プロバイダの前のComponentが返されます
FocusTraversalPolicy.getComponentAfter
で次のComponentを計算するときに、
FocusTraversalPolicy.getComponentAfter
メソッドに渡されるComponentがトラバーサル可能なContainerであり、それがフォーカス・トラバーサル・ポリシー・プロバイダである場合は、このプロバイダのデフォルトのComponentが返されます
FocusTraversalPolicy.getComponentBefore
で前の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を無効にしても、フォーカス・トラバーサルは自動的には開始されません。
クライアント・コードがフォーカス・トラバーサルを開始し、ほかにフォーカスするComponentがない場合、フォーカス所有者は変更されません。クライアント・コードがフォーカス所有者を直接または間接的に非表示にするか、またはフォーカス所有者を表示不可能またはフォーカス不可能にすることによって自動的なフォーカス・トラバーサルを開始し、ほかにフォーカスするComponentがない場合、グローバル・フォーカス所有者はクリアされます。クライアント・コードがフォーカス所有者を直接または間接的に無効することによって自動的なフォーカス・トラバーサルを開始し、ほかにフォーカスするComponentがない場合、フォーカス所有者は変更されません。
フォーカス可能なComponentは、フォーカス所有者になることができ(「フォーカス可能性」)、FocusTraversalPolicyを使用してキーボード・フォーカス・トラバーサルに参加します(「フォーカス・トラバーサル可能性」)。2つの概念に区別はなく、Componentはフォーカス可能かつフォーカス・トラバーサル可能であるか、またはどちらも不可能である必要があります。Componentは、isFocusable()メソッドを介してこの状態を表します。デフォルトでは、すべてのComponentがこのメソッドからtrueを返します。クライアント・コードは、Component.setFocusable(boolean)を呼び出すことでこのデフォルトを変更できます。
パレット・ウィンドウおよびインプット・メソッドをサポートするために、クライアント・コードはWindowがフォーカスされたWindowにならないようにすることができます。移行性によって、Windowまたはその下位Componentがフォーカス所有者になることを防ぎます。フォーカス不可能な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またはその下位Componentの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を介して、コンテキスト全体でのフォーカス状態の変更や、Component内のフォーカス関連の状態の変更を待機できます。
KeyboardFocusManager
は次のプロパティをサポートしています。
focusOwner
: フォーカス所有者
focusedWindow
: フォーカスされたWindow
activeWindow
: アクティブWindow
defaultFocusTraversalPolicy
: デフォルトのフォーカス・トラバーサル・ポリシー
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は常にフォーカス・サイクル・ルートであり、このプロパティは変更できません。
KeyboardFocusManager
は次のプロパティに対してVetoableChangeListener
もサポートしています。
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は拒否回復を次のように開始します。
KeyboardFocusManager
はグローバル・フォーカス所有者をクリアします。
KeyboardFocusManager
はグローバル・フォーカス所有者をクリアします。
VetoableChangeListener
は、拒否回復の結果として開始されたフォーカス変更を拒否しないように注意する必要があります。この状況を想定しないと、拒否されたフォーカス変更と回復の試行の無限サイクルに陥ることがあります。
一部のネイティブ・ウィンドウ・システムでは、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()
: Window.toBack()
: Window.show()/Window.setVisible(true)/Frame.setState(NORMAL)
: Window.hide()/Window.setVisible(false)/Window.dispose()/Frame.setState(ICONIFIED)
:
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に変更する必要がある場合があります。
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にターゲットを変更する必要がある場合があります。null
であると指定した場合、KeyboardFocusManager
はこの値を設定できます。null
は、フォーカスまたはアクティブ化の変更に、ほかのComponentやWindowが関与していなかった可能性が高いことを示します。プラットフォームの限界により、この計算はヒューリスティックに依存し、不正確な場合があります。ただし、このヒューリスティックはピア・レイヤーによる最善の推察です。
クロス・プラットフォームの変更:
DefaultFocusTraversalPolicy
は、以前のリリースのトラバーサル順序を維持します。
Window.toFront()
およびWindow.toBack()
は、Windowが不可視の場合は何も実行しなくなりました。以前は、動作はプラットフォーム依存でした。
Component
にインストールされたKeyListenerは、フォーカス・トラバーサル操作にマッピングされるKeyEvent
を認識しなくなり、このようなイベントに対してComponent.handleEvent()
は呼び出されなくなりました。以前は、AWT Componentはこれらのイベントを認識し、AWTがフォーカス・トラバーサルを開始する前にイベントを消費する機会がありました。代わりに、この機能を必要とするコードは、そのComponent
のフォーカス・トラバーサル・キーを無効にし、フォーカス・トラバーサル自体を処理するようにしてください。あるいは、AWTEventListener
またはKeyEventDispatcher
を使用して、すべてのKeyEvent
を事前待機することもできます。
Microsoft Windows固有の変更:
Window.toBack()
はフォーカスされたWindowを最前面のWindowに変更します。
requestFocus()
は、すべての場合にWindow間のフォーカス変更リクエストを許可するようになりました。以前は、リクエストは重量Componentに関しては許可されていましたが、軽量Componentに関しては拒否されていました。