AWTのフォーカスサブシステム

翻訳元:The AWT Focus Subsystem

JDK1.4以前のJava2 Standard Editionでは、AWTのフォーカスサブシステムが不十分でした。明らかになっている100を超えるバグと共に、主に設計とAPIの問題に悩まされました。これらのバグの多くは、プラットホームの矛盾点や、重量級コンポーネント用のネイティブフォーカスシステムと軽量コンポーネント用のJavaのフォーカスシステム間での不整合が原因でした。

AWTのフォーカスの実装において最悪の問題の一つは、「現在フォーカスされているコンポーネントを知ることができない」という事です。そのような問い合わせ用のAPIが無いだけでなく、現在のフォーカスをあらわす情報が保持されるようになっていない事も原因となっています。

さらに悪いことに、Window(FrameとDialogは違います)の子である軽量コンポーネントがキーボードからの入力を受取ることができないということです。この問題はウィンドウがWINDOW_ACTIVATEDイベントを一切受信しないので絶対にアクティブ化できないことと、アクティブ化したウィンドウだけが、フォーカスを得たコンポーネントを持つことが出来るからです。

さらに、たくさんの開発者が、FocusEventとWindowEventのAPIは、フォーカスやアクティブ化の変更に関係するコンポーネントを確定する方法を提供していないので、不十分だと気づきました。この例として、コンポーネントFOCUS_LOSTイベントを受取ったとき、どのコンポーネントがフォーカスされるのか知る方法はありませんでした。Microsoft Windowsがこの機能をオープンに提供するようになってから、それまで疎外されて落胆していた開発者はMicrosoft WindowsC/C++Visual BasicからJavaにこの機能を移植しました。

私たちは、これらとそのほかの問題に取り組むために、JDK1.4のAWTの為の新しいフォーカスモデルを設計しました。設計の変更点で主だったものは、新しく中央集権化されたKeyboardFocusManagerクラスと、軽量コンポーネント用のフォーカスアーキテクチャの下で構築されました。AWTにおける、フォーカス関連のプラットホーム依存なコードの量は、より少なくなり、全てプラッガブルで拡張性に富んだ公開APIに置き換えられました。私たちは、既存の実装との下位互換性を保とうとしましたが、優雅で役に立つ結果にたどりつくために小さな互換性のない変更をせざるを得ませんでした。私たちは、これら互換性の無い変更が及ぼす影響が既存のアプリケーションにとって些細なものであることを願っています。

このドキュメントは"新しいAPI"と"新しいモデルと関係のある既存のAPI"の両方の公式な定義です。このドキュメントとフォーカス関連のクラスとメソッドのJavadocとを使えば、開発者は十分に、フォーカスの振る舞い(プラットホームを問わず一貫しているが、専用の)を備えたAWTとSwingのアプリケーションを開発できるようになるはずです。このドキュメントは下記のセクションで構成されています:

KeyboardFocusManagerの概要

フォーカスモデルは1つのクラス(KeyboardFocusManager)に中央集権化しています。そのクラスは、フォーカスの状態を問い合わせるクライアントコードの為に、APIセット(フォーカスの変更を開始する、標準のフォーカスイベント振り分け処理をカスタム"dispatcher"に置き換える)を提供しています。クライアントは直接フォーカスの状態を問い合わせることが可能です。もしくは、フォーカス状態に変化があった場合にPropertyChangeEventsを受信する為の、PropertyChangeListenerを登録することが可能です。

KeyboardFocusManagerには7つの主なコンセプトとそれら専用の用語を取り入れられました:

  1. The "focus owner" -- キーボード入力を受取るコンポーネント
  2. The "permanent focus owner" -- 常に最後にフォーカスを受取るコンポーネント。一時的なフォーカスの変化が起こっていない状態では、"focus owner" と "permanent focus owner" は等しくなります。一時的なフォーカスの変更が終了した時は、"permanent focus owner"は、もう一度"focus owner"になります。
  3. The "focused Window" -- "focus owner"を持つウィンドウ。
  4. The "active Window" -- FrameもしくはDialog("focused Window"もしくは、"focused Window"のオーナーである、最初のFrameもしくはDialog)
  5. "Focus traversal" -- ユーザーがマウスカーソルを動かさずに、"focus owner"を変更すること。概して、これはキーボード(例:TABキー)か、それに相当する手に入りやすいデバイスを使って行われます。クライアントコードもこの"traversal"をプログラムから起動することができます。標準の"focus traversal"は"次の"コンポーネントへの"進める"ことも、"前の"コンポーネントへ"戻る"ことをも可能にします。
  6. "Focus traversal cycle" -- コンポーネント階層の一部("focus cycle"の中で"前へ" もしくは "後へ"標準のフォーカスが走査するような、"focus cycle"に含まれないものを除く全てコンポーネント)。この"cycle"は任意のコンポーネントから、その"次の"(foward traversal)と"前の"(forward traversal)コンポーネントへのマッピングを提供します。
  7. "Focus cycle root" -- コンテナ(特定の"focus traversal cycle"の為のコンポーネント階層における"root")。"focus owner"が特定のサイクルの中のコンポーネントである場合、標準の"進む" と "戻る" focus traversalは、コンポーネント階層の"focus cycle root"よりも上に、"focus owner"を移動させることはできません。その代わりに、キーボードとプログラムで、"focus traersal cycle"の階層を上がったり下がったりするナビゲーションを可能とする為、2つの"up cycle"と"down cycle"という操作が定義されました。
  8. "Focus traversal policy provider" - 実際に設定された"FocusTraversalPolicyProvider"を持つコンテナ。このコンテナは"focus traversal"のポリシーの取得に使われます。このコンテナは、その子コンポーネントが"traverse"(進むと戻る)される順番を変更するだけで、新しい"focus cycle"を定義するものではありません。そのコンテナの"Focus traversal policy provider"はsetFocusTraversalPolicyProviderを使うことで設定できます。

すべてのWindowとJInternalFrameは、既定で"focus cycle root"です。もしそれらが"focus cycle root"だけであれば、すべてのそれらフォーカス可能な子孫は、そのフォーカスサイクルに入るべきであり、さらに、標準の"traversal"("戻る"と"進む")である場合は、その"focus traversal policy"は、確実にその全てのコンポーネントにフォーカスが到達できるように履行されるべきです。一方もし、そのWindowもしくはJInternalFrameが、それ自身が"focus cycle root"である子孫を持っている場合、そのような子孫は2つの"focus cycle"のメンバです:1つは、その"root"階層の、もう一つは、"focus-cycle-root"に一番近い祖先の階層。このような子孫の"focus cycle root"に属するフォーカス可能なコンポーネントを"traverse"する為、最初にその子孫に到達するために標準の"traverse(進むもしくは戻る)"を行い、それから、その子孫のさらなる子孫達に順番に到達する為、"down cycle"の操作を行います。

例:


A(Window=focus cycle root)
├─C(コンテナ≠focus cycle root)
│ └─F(Component)
└─B(コンテナ=focus cycle root)
├─E(Component)
└─D(コンテナ=focus cycle root)
├─G(Component)
└─H(Component)

この例には、下記の3つの"focus cycle root"が存在します。

  1. Aは"focus cycle root"、A、B、C、FはAの"focus cycle"のメンバ
  2. Bは"focus cycle root"、B、E、DはBの"focus cycle"のメンバ
  3. Dは"focus cycle root"、D、G,HはDの"focus cycle"のメンバ

既定では、Windowは"focus cycle root"である唯一のコンテナです。KeyboardFocusManagerは抽象クラスです。AWTはDefaultKeyboardFocusManagerクラスで標準実装を提供しています。

KeyboardFocusManagerとブラウザのコンテキスト

いくつかのブラウザは違うコードベースのアプレットを分割したコンテキストに隔離します。お互いのスレッドとお互いのコンポーネントは特定のコンテキストに関連付けられ、別のコンテキストからはスレッドへの干渉及び、コンポーネントへのアクセスが出来ません。このような場合、KeyboardFocusManagerはコンテキストごとに1つとなります。違うブラウザは全てのアプレットを同じコンテキストに置きます。この動作はブラウザの実装に依存しています。詳細な情報はあなたのブラウザのドキュメントを見てください。どんなにたくさんのコンテキストがあるとしても、"focus owner"(フォーカスされたWindowもしくはアクティブWindow)は、ClassLoaderにつき1つで、それ以上になる事はありません。

KeyEventDispatcher と KeyEventPostProcessor

ユーザーのKeyEventsは全て"focus owner"に通知されるべきですが、それが望ましくないケースもまれに存在します。"input method"は、たとえ、関連するテキストコンポーネントが"focus owner"である(もしくは、のままであるべき)としても、全てのKeyEventを受取る、特別なコンポーネントの一例です。

KeyEventDispatcherはクライアントコードに特定のコンテキストの全てのKeyEventを先に受取る事を可能にする、軽量インターフェースです。このインターフェースを実装し、現在のKeyboardFocusManagerに登録されたクラスのインスタンスは、"focus owner"に振り分けれらる前にKeyEventsを受信して、KeyEventDispatcherが再度そのイベントを(消費、振り分け、もしくはその他の変更)出来るようにします。

FocusEvent と WindowEvent

AWTは"java.awt.event"の2つの違うクラスの中で、フォーカスモデルの中心となる下記の6種類のイベントを定義しています:

  1. WindowEvent.WINDOW_ACTIVATED: このイベントは、FrameもしくはDialog(決してWindowでは無い)がアクティブウィンドウになった時に、それらに配信されます。
  2. WindowEvent.WINDOW_GAINED_FOCUS: このイベントはWindowがフォーカスされたウィンドウになった場合に、そのWindowに配信されます。フォーカス可能なウィンドウだけがこのイベントを受信できます。
  3. FocusEvent.FOCUS_GAINED: このイベントはComponentに、それが"focus owner"になった時に配信されます。フォーカス可能なComponentだけがこのイベントを受信できます。
  4. FocusEvent.FOCUS_LOST: このイベントは、そのComponentが"focus owner"ではなくなる時に、そのComponentに配信されます。
  5. WindowEvent.WINDOW_LOST_FOCUS: このイベントはWindowに、それがフォーカスされたWindowではなくなるときに、配信れます。
  6. WindowEvent.WINDOW_DEACTIVATED: このイベントはDialog(決してWindowでは無い)に、それがアクティブなウィンドウでは無くなる時に、配信されます。

Eventの配信

イベントは、それらが起こった順番で配信されます。例えば、ユーザーがアクティブでないフレームのフォーカス可能な子コンポーネントをクリックした場合、イベントは下記の順番で、配信されます:

  1. BWINDOW_ACTIVATEDイベントを受信
  2. BWINDOW_GAINED_FOCUSイベントを受信
  3. AFOCUS_GAINEDイベントを受信

その後、ユーザーが、ほかのフレームDのフォーカス可能な子コンポーネントCをクリックすると、下記の順番でイベントが配信されます:

  1. AFOCUS_LOSTイベントを受信
  2. BWINDOW_LOST_FOCUSイベントを受信
  3. BWINDOW_DEACTIVATEDイベントを受信
  4. DWINDOW_ACTIVATEDイベントを受信
  5. DWINDOW_GAINED_FOCUSイベントを受信
  6. CFOCUS_GAINEDイベントを受信

それぞれのイベントは、次のイベントが配信される前に、配信される事に注意してください。この制限は、違うコンテキストの違う"event dispatching thread"上でコントロールされるコンポーネントにも適用されます。

さらに言うと、それぞれの種類のイベントは、相対するイベントと1対1になっています。例として、もしコンポーネントがFOCUS_GAINEDイベントを受信した場合、FOCUS_LOSTイベントを受信しない限りは、他のFOCUS_GAINEDイベントを受信する事はできません。

最後に、それらのイベントは情報提供のみを目的として配信される事に注意してください。先行するFOCUS_LOSTイベントをハンドリングしている間に、フォーカスを失おうとしているコンポーネントにフォーカスを戻すように要求することで、起ころうとしているFOCUS_GAINEDイベントの配信を防ぐことは不可能です。クライアントコードがそのようなリクエストを行ったとしても、先行するFOCUS_GAINEDは配信されてしまいまい、その後に、そのイベントが元の"focus owner"にフォーカスを戻します。

もし、FOCUS_GAINEDを食い止める事が不可欠な場合は、クライアントコードは、フォーカスの変更を拒否するVetoableChangeListenerを設定することできます。Focus and VetoableChangeListenerを参照してください。

相対コンポーネントとウィンドウ

それぞれのイベントは、フォーカスや、アクティベーションの変化に関係する"相対"コンポーネントもしくはウィンドウの情報を含んでいます。例えば、FOCUS_GAINEDイベントの場合の"相対"コンポーネントは、フォーカスを失ったコンポーネントとなります。ネイティブアプリケーションや、異なるVMやコンテキスト上で動作するJavaアプリケーションで、フォーカスやアクティベーションの変更が起こった場合は、"相対"コンポーネントもしくはウィンドウはnullとなります。この情報には、FocusEvent.getOppositeComponentWindowEvent.getOppositeWindowを使うことでアクセスできます。

いくつかのプラットホームでは、2つの違う重量級コンポーネント間で起こる、フォーカスとアクティベーションの変化の相対コンポーネントもしくはウィンドウを認識することができません。このようなケースでは、いくつかのプラットホームで、相対コンポーネントがnullに設定され、その他のプラットホームでは、有効なnullではない値が設定される可能性があります。しかし、1つの重量級コンポーネントを共有する軽量級コンポーネント間で起こるフォーカスの変化については、相対コンポーネントが常に正しく設定されます。よって、純粋なSwingアプリケーションでは、トップレベルウィンドウで起こったフォーカスの変化の相対コンポーネントを使うので、このプラットホームの制限は無視できます。

一時的なFocusEvents

FOCUS_GAINED と FOCUS_LOST イベントは、"一時的"か"恒久的"かのどちらかにマーキングされます。

一時的なFOCUS_LOSTイベントは、コンポーネントがフォーカスを失っていても、すぐにそのフォーカスを取り戻す場合に送信されます。これらのイベントは、フォーカスの変更が、データの検証のきっかけに使われる場合に便利です。例えば、1つのテキストコンポーネントが、ユーザーが他のコンポーネントとやり取りし始めたときに、その内容をコミットしたい場合には、FOCUS_LOSTイベントを受けることで、完了する事ができます。しかし、もし、その受信したFocusEventが、一時的なものであれば、そのテキストフィールドにはすぐにフォーカスが戻る事になるので、コミットすべきではありません。

恒久的なフォーカス移動は、大抵、ユーザーが、選択可能項目や重量級コンポーネントをクリック、もしくは、キーボードかそれに相当するデバイスでの"focus traversal"、requestFocus(),requestFocusInWindow()を行った結果で起こります。

一時的なフォーカスの移動は、大抵、Menu,PopupMenuの表示、Scrollbarのクリック及びドラッグ、タイトルバーをドラッグして行うウィンドウの移動、他のウィンドウへのフォーカスによって起こります。いくつかのプラットホームでは、これらの全ての動作で、どんなFocusEventsも生成しない可能性がある事に注意してください。その他のプラットホームでは一時的なフォーカス移動が起こります。

コンポーネントが、一時的なFOCUS_LOSTイベントを受取る時、そのイベントの相対コンポーネント(もしあれば)が一時的なFOCUS_GAINEDを受取っているかもしれませんが、恒久的なFOCUS_GAINEDも受取る事ができます。フォーカスウィンドウの変更は、新しい"focus owner"に恒久的なFOCUS_GAINEDイベントをもたらしますが、Menu及びPopupMenuの表示、Scrollbarのクリック及びドラッグは、一時的なFOCUS_GAINEDイベントを生成するべきです。

Componentクラスは、一時状態を引数として受取る、requestFocusrequestFocusInWindowというメソッドを持っています。しかし、任意の一時状態の定義を、全てのネイティブウィンドウシステム上で、実装できない可能性があるので、このメソッドの正しい動作は、軽量コンポーネントにおいてのみ保証されます。このメソッドは一般的に利用される事を意図されたものでは無く、Swingのような軽量コンポーネントライブラリの為のフックとして存在しています。


Focus Traversal

それぞれのComponentはそれ自身に既定の"focus traversal"操作の為の"focus traversal"のキーのSetを定義しています。Componentは"進む"と"戻る"と、1つの"focus traversal cycle"の"traversal up"の為の、別々の"focus traversal"のキーのセットをサポートしています。"focus cycle root"である、Containerも、"focus traversal cycle"の"traversal down"の為のセットをサポートしています。そのSetが明示的に定義されない場合は、Componentは親のセット、最終的にはKeyBoardFocusManagerのコンテキスト全体の既定セットを再帰的に継承します。

AWTKeyStrokeAPIを使うと、クライアントコードは、2つの特別なKeyEvent(KEY_PRESSED,KEY_RELEASED)を定義できます。"focus traversal"操作は定義されたKeyEventと関係なく起こりますが、"focus traversal"のキーと関連図けられた全てのKeyEvent(関連するKEY_TYPEDイベントを含む)は、消費されるようになり、どんなコンポーネントにも配信されなくなります。どんな特別なコンポーネントもしくは、KeybordFocusManagerの既定の"focus traversal"の操作の為でも、1つのKEY_TYPEDイベントを1つの"focus traversal"操作と対応付ける事や、同一のイベントを複数の"focus traversal"操作と対応付ける事は、実行時エラーとなります。

既定の"focus traversal"キーは、実装依存です。Sunは特定のプラットホームでは同じキーを使って実装するように推奨しています。WindowsとUnixで推奨されるきーは:

  • 次のコンポーネントへ進める:
    • TextArea: CTRL-TABKEY_PRESSED
    • その他: TABKEY_PRESSEDCTRL-TABKEY_PRESSED
  • 前のコンポーネントへ戻す:
    • TextAreas: CTRL-SHIFT-TABKEY_PRESSED
    • その他: SHIFT-TABKEY_PRESSEDCTRL-SHIFT-TABKEY_PRESSED
  • "focus traversal cycle"を1つ上る :
  • "focus traversal cycle"を1つ下る :

コンポーネントは、Component.setFocusTraversalKeysEnabledを使って、一斉に全ての"focus traversal"キーを有効や無効にすることができます。"focus traversal"キーが無効の時、コンポーネントは全てキーのKeyEvnetsを受信します。"focus traversal"キーが有効の時、そのKeyEventsが自動的に"focus traversal"操作にマップされる代わりに、コンポーネントはそのtraversalキーのKeyEventsを受信することはありません。

ノーマルな"戻る"及び"進む"操作において、AWTのフォーカス実装は"focus owner"の"focus traversal policy provider"か"focus cycle root"のFocusTraversalPolicyを規準に、次のフォーカス先を決めます。"focus owner"が"focus cycle root"であるとき、ノーマルの"focus traversal"の間、次(及び前)のコンポーネントが表示されているかどうかわからないかもしれません。よって、その現在のKeyboardFocusManagerは、その"時点(current)"での全てのコンテキストを横断するグローバルな"focus cycle root"への参照を保持します。

"up-cycle traversal"では、"focus owner"は"current focus owner"の"focus cycle root"にセットされ、"current focus cycle root"は、新しい"focus owner"の"focus cycle root"になります。しかし、"current focus owner"の"focus cycle root"が、トップレベルウィンドウである場合は、"focus owner"は"focus cycle root"の既定フォーカスコンポーネントに設定され、"current focus cycle root"は変更されません。

"down-cycle traversal"では、今の"focus owner"が"focus cycle root"の場合、"focus owner"は、今の"focus owner"の既定フォーカスコンポーネントに設定され、"current focus cycle root"は、今の"focus owner"に設定されます。もし、今の"focus owner"が"focus cycle root"では無い場合は、"focus traversal"操作は起こりません。

FocusTraversalPolicy

FocusTraversalPolicyは、特定の"focus cycle root"か、"focus traversal policy propvider"を含むコンポーネントがトラバースされる順番を定義します。FocusTraversalPolicyのインスタンスは、コンテナ間で共有することができ、それらのコンテナに同じ"traversal policy"を実装することができます。FocusTraversalPoliciesは、"focus -traversal-cycle"の階層が変わったとしても、再度初期化する必要はありません。

それぞれのFocusTraversalPolicyは下記の5個のアルゴリズムで定義されなければなりません:

  1. 1つの"focus cycle root"と1つのコンポーネントaがそのサイクルの中にあるとすると、その次のコンポーネントはaの後です。
  2. 1つの"focus cycle root"と1つのコンポーネントaがそのサイクルの中にあるとすると、その前のコンポーネントはaの前です。
  3. 1つの"focus cycle root"があり、そのサイクルの中の"最初"のコンポーネントがあるとすると、その"最初"のコンポーネントは、"進む"の方向のトラバースの1サイクルが終了した時にフォーカスされます。

  4. 1つの"focus cycle root"があり、そのサイクルの中の"最後"のコンポーネントがあるとすると、その"最後"のコンポーネントは"戻る"の方向のトラバースの1サイクルが終了した時にフォーカスされます。

  5. 1つの"focus cycle root"があり、そのサイクルの中の"既定"のコンポーネントがあるとすると、その"既定"のコンポーネントは、新しい"traversal cycle"への"traverse down"操作の際に、フォーカスを受取ります。これは"最初"のコンポーネントと同じかもしれませんが、そうである必要はありません。

FocusTraversalPolicyはオプションとして、下記のアルゴリズムを提供するかもしれません:


Windowがあるとして、そのWindowの"初期"コンポーネント"があるとします。その"初期"コンポーー年とはWindowが表示されたときに、最初のフォーカスを受取ります。既定では、これは、"既定"のコンポーネントと同じです。

さらに、SwingはFocusTraversalPolicyの1つのサブクラス(InternalFrameFocusTracersalPolicy)を提供しています。開発者が下記のアルゴリズムを提供することができるように:


JInternalFrameがあり、JInternalFrame上の"初期"コンポーネントがあるとします。
その初期コンポーネントはJInternalFrameが最初に選択された時に、フォーカスを受信します。既定のままでは、これはJInternalFrameの"既定"コンポーネントへのフォーカスと同じです。

FocusTraversalPolicyContainer.setFocusTraversalPolicyをつかって、コンテナ上にインストールされます。もしポリシーが明示的に設定されていない場合、コンテナは、一番近い"focus-cycle-root"である祖先から継承します。最上位の初期のそれらの"focus traversal policy"は、コンテキストの既定のポリシーです。コンテキストの既定のポリシーは、KeyboardFocusManager.setDefaultFocusTraversalPolicyを使って構築されます。

AWTはクライアントコードで使うための2つの標準のFocusTraversalPolicyの実装を提供しています。

  1. ContainerOrderFocusTraversalPolicy: コンテナにコンポーネントを追加した順番で、"focus traversal cycle"に入った全コンポーネントに渡ってトラバースします。それぞれのコンポーネントはそのaccept(Component)メソッドで適合性をチェックされます。既定では、コンポーネントは表示可能、有効、フォーカス可能なものだけが適合します。既定では、ContainerOrderFocusTraversalPolicyは暗黙の内に、フォーカスの"down-cycle"を送信します。これは、通常の"forward focus traversal"が行われている間、"focus-cycle-root"の後にフォーカスされるコンポーネントは、"focus-cycle-root"の既定コンポーネントであるということです。このことで、"up and down-cycle traversal"のコンセプトが無いアプリケーションへの下位互換性を提供しています。
  2. DefaultFocusTraversalPolicy: 適合性のチェック方法を再定義したContainerOrderFocusTraversalPolicyのサブクラスです。クライアントコードがコンポーネントがフォーカス可能かどうかをComponent.isFocusTraversable()Component.isFocusable()をオーバーライドするか、Component.setFocusable(boolean)を呼ぶかして、明示的に設定しなかった場合、DefaultFocusTraversalPolicyは、確実にContainerOrderFocusTraversalPolicyと同じように動作します。しかし、コンポーネントが、既定の"foucusability"に頼っている場合、DefaultFocusTraversalPolicyは"non-focusable"なピア((AWTのJavaコードとやり取りをしているネイティブコンポーネントのようなもの...多分))を持つ全てのコンポーネントを拒否します。ピアの"focusability"は実装依存です。Sunは、特定のネイティブプラットホーム向けの全てのコンポーネント実装は、同じ"focusability"でピアを構築するよう推奨しています。この推奨事項ではWindowsと、Unixでは、Canvas,Label,Panel,Scrollbar,ScrollPane,Windowと軽量コンポーネントは、"non-focusable"なピアを持ち、その他の全てのコンポーネントは"focusable"なピアを持ちます。これら推奨事項は、SunのAWT実装に置いて使われています。注意すべきことは、コンポーネントのピアの"focusability"は、そのコンポーネント自身の"focusability"とは違うもので、影響も与えないという事です。

Swingはクライアントコードで使うための、2つの標準FocusTraversalPolicy実装を提供しています。それぞれの実装はInternalFrameFocusTraversalPlicyの一種です。

  1. SortingFocusTraversalPolicy: 1つのComparetorを規準として"focus traversal cycle"内のコンポーネントをソートすることで、トラバースする順番を決定します。それぞれのコンポーネントは、accept(Component)を使って適合性をチェックされます。既定では、コンポーネントは、可視、表示可能、有効且つフォーカス可能なものだけが適合します。既定では、SortingFocusTraversalPolicyの"focus down-cycle"を暗黙のうちに送信します。これは、通常の"forward focus traversal"が行われている間、"focus-cycle-root"の後にフォーカスされるコンポーネントは、"focus-cycle-root"の既定コンポーネントであるということです。このことで、"up and down-cycle traversal"のコンセプトが無いアプリケーションへの下位互換性を提供しています。
  2. LayoutFocusTraversalPolicy:コンポーネントのソートをそれらのサイズ、位置、向きを規準に行うSortingFocusTraversalPolicyのサブクラスの1つで、各コンポーネントは、大雑把に、行列に分類されます。水平方向に配置するコンテナでは、列は左から右もしくは右から左、行は上から下となります。垂直方向に格納するコンテナでは、列が上から下に、行は左から右か、右から左となります。行に格納された全ての列は、次の行が処理される前に、全てトラバースされます。さらに、適合性のテストは、空のInputMapを持つ(もしくは継承する)JComponentを除外するように拡張されています。

標準の"look&feel"もしくは、他のBasicLookAndFeelから派生した"look&feel"と使う、Swingまたは、Swing/AWTの混合アプリケーションでは、全てのコンテナが既定でLayoutFocusTraversalPolicyを使います。.

純粋なAWTアプリケーションを含む、他の全てのアプリケーションは、既定で、DefaultFocusTraversalPolicyを使います。

Focus Traversal Policy Providers

"focus cycle root"では無いコンテナは、自身のFocusTraversalPolicyを提供するためのオプションを持っています。そのようにする為には、コンテナのfocusTraversalPolicyProvider属性をtrueにする為に下記のメソッドを使います


Container.setFocusTraversalPolicyProvider(boolean)

コンテナが、FocusTraversalPolicyProviderであるかどうかを知るには、下記のメソッドを使うべきです。


Container.isFocusTraversalPolicyProvider()

もし、"focus cycle root"の、FocusTraversalPolicyProvider属性をtrueにしたとしても、FocusTraversalPolicyProviderとはみなされず、他の"focus cycle root"と同じように動作します。

"focus cycle root"と、"focus traversal policy provider"の主な違いは、後者が、他のコンテナと同じようにフォーカスの出入りを許可し手いることです。しかしながら、"focus traversal plolicy provider"の子がトラバースされる順番は、"provider"のFocusTraversalPolicyによって決定されます。それは"focus traversal policy provider"が下記のように動作する事を有効にする為です。FocusTraversalPolicyは、下記のマナーに従ってそれらを扱います。

  • "focus traversal policy provider"は、"focus cycle root"の代わりにコンポーネントの"setFocusTraversalPolicy"メソッドに"focus traversal policy"を渡すことが出来ます。
  • FocusTraversalPolicy.getComponentAfterか、FocusTraversalPolicy.getComponentBeforeで次もしくは前のコンポーネントを算出しているとき、
    • 取得されたコンポーネントがコンテナ且つ"focus traversal policy provider"の場合、そのコンテナのFocusTraversalPolicyに、その既定コンポーネントを問い合わせ、結果として、その既定コンポーネントが返されます。
    • 取得されたコンポーネントが"focus traversal policy provider"の子である場合、このコンポーネントの次と前のコンポーネントは、その"focus traversal policy provder"のFocusTraversalPolicyを使います。しかし、フォーカスが、そのプロバイダから離れるときは、下記のルールが適用されます:
      • たまたま次に見つかったコンポーネントが"focus traversal policy provider"の最初のコンポーネントである場合、"focus traversal policy provider"の後のコンポーネントが返されます。
      • たまたま見つかった前のコンポーネントが、"focus traversal policy provider"の最後のコンポーネントである場合、"focus traversal policy provider"の前のコンポーネントが返されます。
  • FocusTraversalPolicy.getFirstComponentか、FocusTraversalPolicy.getLastComponentで算出した、"最初"か、"最後"のコンポーネント、
    • 取得されたコンポーネントが、コンテナで、且つ、"focus traversal policy provider"である場合、そのコンテナのFocusTraversalPolicyに既定コンポーネントを問い合わせて、その結果として、既定コンポーネントが返されます。

Programmatic Traversal

ユーザーが開始する"focus traversal"に加えて、クライアントコードも"focus traversal"操作の開始をプログラミングできます。クライアントコードが開始する"programmatic traversal"は、ユーザーから開始するトラバーサルと見分けが付かないように実行できます。"programmatic traversal"を開始するよい方法は、KeyboardFocusManagerにある下記のメソッドのうちの1つを使うことです:

  • KeyboardFocusManager.focusNextComponent()
  • KeyboardFocusManager.focusPreviousComponent()
  • KeyboardFocusManager.upFocusCycle()
  • KeyboardFocusManager.downFocusCycle()

それぞれのメソッドは、現在の"focus owner"から"traversal"を開始します。もし、その時点で、"focus owner"がない場合は、"traversal"は起こりません。さらに、"focus owner"が"focus cycle root"ではない場合は、downFocusCycle()は"traversal"操作を実行しません。

KeyboardFocusManagerも下記の様々なメソッドをサポートしています:

  • KeyboardFocusManager.focusNextComponent(Component)
  • KeyboardFocusManager.focusPreviousComponent(Component)
  • KeyboardFocusManager.upFocusCycle(Component)
  • KeyboardFocusManager.downFocusCycle(Container)

それぞれのメソッドは、"focus owner"からというより、その特定のコンポーネントから"traversal operation"を開始します。その"traversal"は、その特定のコンポーネントが"focus owner"であるかのように起こります。そうある必要がなくても

代わりに、それらに相当する、APIが、Componentと、Containerクラスに定義されています。

  • Component.transferFocus()
  • Component.transferFocusBackward()
  • Component.transferFocusUpCycle()
  • Container.transferFocusDownCycle()

KeyboardFocusMangerのメソッドと同様に、これらそれぞれのメソッドはそのコンポーネントが"focus owner"であるかのように"traversal operation"を開始します。そうある必要がなくても。

"focus owner"を直接もしくは、親を経由して間接的に、隠すか、無効にする事、及び、"focus owner"を表示不可、フォーカス不可として作る事は、自動的に"foward focus traversal"が開始されることにも注意してください。軽量か、重量級である親コンポーネントのどれかを無効にするとの中で、重量級の親コンポーネントを隠した時は、常に間接的にその子コンポーネントを隠すことになります。重量級コンポーネントである親を無効にする事は、その子も無効にする事です。よって、軽量コンポーネントである親を無効にしても、"focus traversal"が自動的に開始されません。

クライアントコードが"focus traversal"を開始して、他にフォーカスするコンポーネントが無い場合、"focus owner"は変更されずにとどまります。クライアントコードが、直接、間接を問わず"focus owner"を隠すこと、もしくは、"focus owner"を表示不可、フォーカス不可として作成することで、自動的に"focus traversal"が開始され、他のフォーカスすべきコンポーネントが無い場合、"global focus owner"はクリアされます。クライアントコードが、直接、間接を問わず"focus owner"を隠すこと、もしくは、"focus owner"を表示不可、フォーカス不可として作成することで、自動的な"focus traversal"を開始し、フォーカスすべきコンポーネントが無い場合は、"focus owner"は変更されずにとどまります。

Focusability

"focasable Component"は"focus owner"になる事ができ("focusability")、FocusTraversalPolicyを使って、キーボードによる"focus traversal"に参加することもできます("focus traversability")。これら2つのコンセプトに区別は無く、コンポーネントは"focusable"、"focus traversable"の両方であるか、両方でないようになっていなければなりません。コンポーネントはその状態をisFocusable()メソッド経由で表明します。既定では、全てのコンポーネントはこのメソッドでtrueを返します。クライアントコードはComponent.setFocusable(boolean)を呼ぶことで、この既定値を変更する事が出来ます。

Focusable Windows

パレットウィンドウとインプットメソッドをサポートする為に、クライアントコードは、ウィンドウがフォーカスされることを防ぐことが出来ます。他動性によって、Windowや、その子が"focus owner"になるのを防ぎます。"Non-focusable"なウィンドウは、もしかすると"focusable"なWindowのオーナーであるかもしれません。既定では、全てのFrameDialogは"focusable"です。FrameDialogではないが、スクリーンに表示されているFrameDialogを直接保持しており、少なくとも1つの"focus traversal cycle"に属するコンポーネントを持つ全てのウィンドウは、既定で"focusable"となります。"non-focusable"なWindowを作るには、Window.setFocusableWindowState(false)を使ってください。

ウィンドウが"non-focusable"の場合、この制限はKeyboardFocusManagerWINDOW_GAINED_FOCUSイベントを認識した時にそのウィンドウに適用されます。この時点で、そのフォーカスの変更は、拒否されて、フォーカスが別のWindowに再設定されます。この拒否と再設定の仕組みは、VetoableChageListenerがフォーカスの編子を拒否した場合と同じです。Focusand VetoableChangeListenerを参照してください。

新しいフォーカスの実装は、そのKeyEventが、ウィンドウまたは、その子が、そのウィンドウのオーナーの子の代理となることを意図しており、その代理となったウィンドウはイベントを受取るために、X11上にマッピングされなければならないので、Frameや、Dialogの直接的なオーナーではあるが、表示されていないウィンドウは、X11において、KeyEventを受取る事ができません。この制限をサポートする為、ウィンドウ用の"window focusability"と、"window focusability state"を分けました。"window focusability state"は、"window focusability"を決定する為、そのウィンドウが直接保持しているFrameもしくはDialogの表示状態と連動します。既定では、全てのウィンドウは"true"である"window focusability state"を持ちます。"window focusability state"を"false"に設定すると、直接保持しているFrameもしくはDialogの表示状態がどのようであろうとも、そのウィンドウは、フォーカスされることはありません。

Swingでは、アプリケーションは、ownerがnullであるJWindowを作成することが出来ます。Swingは、このようなJWindowは全て、プライベートな隠しフレームを保持することで、構築します。なぜなら、このFrameの表示状態は常に"false"で、ownerがnullとして構築されたJWindowは、たとえ"window focusability state"が"true"だったとしても、決して"focused Window"になる事はないからです。

"focused window"が"non-focusable"になった場合、AWTは、そのウィンドウのオーナーの直近で、フォーカスされていたコンポーネントをフォーカスしようとします。そして、ウィンドウのオーナーは新しい"focused Window"となります。もし、ウィンドウのオーナーも"non-focusable"なウィンドウであるときは、フォーカスを変更するリクエストは、再帰的にそのオーナーシップ階層を上ります。全てのプラットホームで、ウィンドウを跨いだフォーカスの変更をサポートしていないとしても(フォーカスの要求を参照)、全てのそのようなフォーカスの変更要求を失敗させることは可能です。このケースでは、グローバルな"focus owner"は、クリアされ、"focused Window"は元に戻ります。

フォーカスを要求する

コンポーネントはComponent.requestFocus()を呼ぶことで、"focus owner"になる事を要求できます。それは、コンポーネント(表示可能、可視、フォーカス可能なコンポーネントのみ)への恒久的なフォーカスの送信を開始します。これらの条件が満たされない場合は、そのリクエストはただちに拒否されます。また、無効なコンポーネントが"focus owner"になる事があっても、このケースでは全てのKeyEventが捨てられます。

コンポーネントのトップレベルウィンドウが、"focused Window"ではなく、そのプラットホームがウィンドウを跨ぐフォーカスの要求をサポートしていない場合、その要求も拒否されるでしょう。この理由でその要求が拒否された場合、後にユーザーによってウィンドウがフォーカスされる時に、その要求は、記憶され、許可されます。そうでない場合は、フォーカス変更要求は、"focused Window"を変更します。現在は、Microsoft Windowsがウィンドウを跨ぐフォーカス送信をサポートしています。Solarisはまだです。

フォーカス変更リクエストが許可されるかどうか、同期的に知る方法はありません。かわりに、クライアントコードは、コンポーネントに、FocusListenerをインストールしてFOCUS_GAINEDイベントの配信を監視しなければなりません。クライアントコードは、コンポーネントがこのイベントを受取るまで、そのコンポーネントが"focus owner"であるとかんがえてはなりません。そのイベントは、requestFocus()が終了する前に配信されるかもしれませんし、されないかもしれません。開発者は、1つの振る舞いもしくは、その他であると仮定してはいけません。((何?))

AWTは全てのフォーカス変更要求がイベントディスパッチスレッドで作成された場合において、type-aheadをサポートしています。クライアントコードがフォーカスの変更を要求し、AWTがそのネイティブウィンドウシステムによってその要求を許可する可能性がある場合、AWTは、現在処理されているイベントの後で、全てのKeyEventをタイムスタンプとともにキューに保持すべき、現在のKeyboardForcusManagerに通知します。それらのKeyEventは新しいコンポーネントが"focus owner"になるまで、配信されません。AWTは、ネイティブレベルでのフォーカス変更が失敗した、コンポーネントのピアが破棄された、もしくは、VetoableChangeListenerによってフォーカス変更が捨てられた場合には、それらの遅延配信要求をキャンセルします。KeyboardFocusManagerは、フォーカス変更要求がイベントディスパッチスレッド以外で作成された場合には、type-aheadのサポートを必要としません。

Component.requestFocus()は、プラットホームを横断して一貫した実装が不可能なので、開発者はComponent.requestFocusInWindow()を変わりに使う事を推奨します。このメソッドは、自動的に全てのプラットホームにおいて、ウィンドウを跨ぐフォーカスの送信を拒否します。フォーカス送信のプラットホームで定義された要素だけを取り除くことによって、クロスプラットホームな振る舞いを実現しています。

さらに言うと、requestFocusInWindow()boolean値を返します。もし、"false"が返されれば、そのリクエストは失敗することが保証されます。もし、"true"が返されれば、そのリクエストは、捨てられないもしくは、何らかの異常(そのリクエストがネイティブウィンドウシステムに許可される前にコンポーネントのピアが廃棄されること等)が無い限り、成功します。もう一度繰り返します。"true"が返される場合は、そのリクエストは、成功するかもしれないという事を表しており、開発者は、FOCUS_GAINEDイベントを受信するまで、このコンポーネントが"focus owner"であると絶対に仮定してはいけません。

もし、クライアントコードが、アプリケーション上で、"focus owner"となるコンポーネントを必要としていなくても、KeyboardFocusManager.clearGlobalFocusOwner()を呼び出す事が出来ます。このメソッドが呼ばれた時に、"focus owner"が存在している場合は、"focus owner"は、恒久的な"FOCUS_LOST"イベントを受信します。この時点から、AWTのフォーカス実装が、ユーザーもしくはクライアントコードが明示的になんらかのコンポーネントにフォーカスを設定するまで、全てのKeyEventを捨てます。

Componentクラスも、クライアントコードが一時的な状態を定義することを可能にする、requestFocusrequestFocusInWindowをサポートしています。一時的なFocusEventsを参照してください。

フォーカスとPropertyChangeListener

クライアントコードPropertyChangeListener経由で、コンテキストワイドなフォーカスの状態の変更や、フォーカスに関係する状態を監視することが出来ます。

KeyboardFocusManagerは下記のプロパティをサポートしています:

  1. focusOwner: "focus owner"
  2. focusedWindow: "focused Window"
  3. activeWindow: "active Window
  4. defaultFocusTraversalPolicy: 既定の"focus traversal policy"
  5. forwardDefaultFocusTraversalKeys: 既定のFORWARD_TRAVERSAL_KEYSSet
  6. backwardDefaultFocusTraversalKeys: 既定のBACKWARD_TRAVERSAL_KEYSSet
  7. upCycleDefaultFocusTraversalKeys: 既定のUP_CYCLE_TRAVERSAL_KEYSSet
  8. downCycleDefaultFocusTraversalKeys: 既定のDOWN_CYCLE_TRAVERSAL_KEYSSet
  9. currentFocusCycleRoot: 現在の"focus cycle root"

現在のKeyboardFocusManagerにインストールされたPropertyChangeListenerは"focus owner"、"focused Window"、"active Window"、全てのコンテキストで共有されるグローバルなフォーカス状態を構成する現在の"focus cycle root"のいずれであったとしても、そのKeyboardFocusManagerのコンテキストの中のフォーカスの変更しか監視することはできません。私達はこれを、クライアントコードがPropertyChangeListenerをインストールする前に、セキュリティチェックをパスする必要があるよりは、煩わしくないと信じています。


Componentは下記のフォーカスに関連するプロパティをサポートしています:

  1. focusable: そのComponentの"focusability"
  2. focusTraversalKeysEnabled: そのComponentの"focus traversal keys"の有効状態
  3. forwardFocusTraversalKeys: そのComponentFORWARD_TRAVERSAL_KEYSSet
  4. backwardFocusTraversalKeys: そのComponentBACKWARD_TRAVERSAL_KEYSSet
  5. upCycleFocusTraversalKeys: そのComponentUP_CYCLE_TRAVERSAL_KEYSSet

Componentのプロパティに加えて、Containerは下記のフォーカス関連プロパティをサポートしています:

  1. downCycleFocusTraversalKeys: そのContainerDOWN_CYCLE_TRAVERSAL_KEYSSet
  2. focusTraversalPolicy: そのContainerの"focus traversal policy"
  3. focusCycleRoot: そのContainerの"focus cycle root"の状態

Containerのプロパティに加えて、Windowは下記のフォーカス関連プロパティをサポートしています:

  1. focusableWindow: そのWindowの"focusable Window state"

WindowにインストールされたPropertyChangeListenerは絶対に、focusCycleRootプロパティのPropertyChangeEventを監視することはできません。WIndowは常に、"focus cycle root"であり、このプロパティは変更できません。

Focus and VetoableChangeListener

KeyboardFocusManagerも下記のプロパティに対して、VetoableChangeListenerをサポートします:

  1. "focusOwner": the focus owner
  2. "focusedWindow": the focused Window
  3. "activeWindow": the active Window

VetoableChangeListenerPropertyVetoExceptionを投げる事によって、フォーカスや、アクティベーションの変更を禁止する場合、その変更は無視されます。既にその変更を承認した、VetoableChangeLisntenerは、状態が前の値に戻った事を示すPropertyChangeEventを非同期で受信します。

VetoableChageListenerは、変更が、KeyboardFocusManagerに反映される前に、その状態の変更が通知されます。逆に、PropertyChangeListenerは変更の反映後に、通知されます。これは、全てのVetoableChangeListenerは、どんなPropertyChangeListenerより先に通知を受取る事を表しています。

VetoableChageListenerは、冪等でなければならず、特定のフォーカス変更(FOCUS_LOSTと、FOCUS_GAINED等)におけるフォーカスを失うイベントと、得るイベントのどちらもを拒否しなければなりません。例として、VetoableChangeListenerが、FOCUS_LOSTイベントを禁止する場合、KeyboardFocusManagerEventQueueの検索と、関連するペンディングのFOCUS_GAINEDイベントの削除をする必要がありません。代わりに、KeyboardFocusManagerは、このイベントの振り分けから自由で、尚且つ、そのイベントを禁止する事は、VetoableChangeListenerの責務です。さらに、FOCUS_GAINEDイベントを処理している間、KeyboardFocusManagerは、他のFOCUS_LOSTイベントと合成することによって、グローバルなフォーカス状態を再同期しようとするかもしれません。このイベントは、最初のFOCUS_LOSTイベントがちょうどそうであったように拒否されなければなりません。

KeyboardFocusManagerは、PropertyChangeListenerへ状態の変更を通知している間、どんなロックもしないかもしれません。にもかかわらず、この条件は、VeotableChangeListenerにおいては緩和されます。故に、クライアントで定義したVetoableChangeListenerは内部のvetoableChange(PropertyChangeEvent)で、追加のロックを取得する事(これはデッドロックを引き起こすかもしれません)を避けるべきです。もし、フォーカスや、アクティベーションの変更が拒否された場合、KeyboardFocusManagerはその拒絶のリカバリーを以下のように開始します:

  • もし、"active Window"の変更が拒否された場合、"focused or active Window"は、前の"focused or active Window"にリセットされます。もしそのようなウィンドウが無い場合は、KeyboardFocusManagerは"global focus owner"をクリアします。
  • もし、"focus owner"の変更が拒否された場合、その"focus owner"は前の"focus owner"であるコンポーネントにリセットされます。もし、それが不可能な場合、前の"focus owner"の後に、"focus traversal cycle"の次のコンポーネントにリセットされます。もし、それも不可能な場合、KeyboardFocusManagerは"global focus owner"をクリアします。

VetoableChangeListenerは、拒否のリカバリの結果として開始されるフォーカスの変更を禁止することを注意深く避けなければなりません。この予測に失敗すると、フォーカス変更の禁止と、それをリカバリーしようとする無限のサイクルを引き起こしてしまいます。