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
は abstract クラスです。AWT では、DefaultKeyboardFocusManager
クラスにデフォルトの実装が提供されます。
一部のブラウザは、異なるコードベースのアプレットを別のコンテキストに分割し、これらのコンテキストの間に壁を構築します。各スレッドおよび各 Component は、特定のコンテキストに関連付けられ、ほかのコンテキスト内のスレッドに影響を与えたり、Component にアクセスしたりすることはできません。このようなシナリオでは、コンテキストごとに 1 つの KeyboardFocusManager があります。別のブラウザは、すべてのアプレットを同じコンテキストに配置します。これは、すべてのアプレットに対して単一でグローバルな KeyboardFocusManager のみがあることを示します。この動作は実装に依存します。詳細はブラウザのドキュメントを参照してください。ただし、存在するコンテキストの数にかかわらず、ClassLoader あたり複数のフォーカス所有者、フォーカスされた Window、またはアクティブ Window が存在することはありません。
ユーザーの KeyEvent は通常フォーカス所有者に送られますが、これを避けるべき場合がまれにあります。インプットメソッドは特殊な Component の例で、インプットメソッドが KeyEvent を受け取るべきですが、関連付けられたテキスト Component は引き続きフォーカス所有者でありフォーカス所有者であるべきです。
KeyEventDispatcher は、クライアントコードが特定のコンテキスト内のすべての KeyEvent を事前に待機できるようにするための、軽量インタフェースです。このインタフェースを実装し、現在の KeyboardFocusManager に登録されたクラスのインスタンスは、フォーカス所有者に KeyEvent がディスパッチされる前にその KeyEvent を受け取ります。これにより、KeyEventDispatcher はイベントのターゲット変更、消費、自身によるイベントディスパッチ、またはその他の変更を行うことができます。
一貫性を保つため、KeyboardFocusManager 自体は KeyEventDispatcher です。デフォルトで、現在の KeyboardFocusManager は、登録された KeyEventDispatcher によりディスパッチされないすべての KeyEvent を受信します。現在の KeyboardFocusManager は KeyEventDispatcher としての登録を完全に解除することはできません。ただし、KeyEventDispatcher が、実際に KeyEvent をディスパッチしたかどうかにかかわらず、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.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
を全体的に置き換えるという手段をとる前に、このドキュメントで説明する 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 に関しては拒否されていました。