AWTのフォーカスサブシステム
JDK1.4以前のJava2 Standard Editionでは、AWTのフォーカスサブシステムが不十分でした。明らかになっている100を超えるバグと共に、主に設計とAPIの問題に悩まされました。これらのバグの多くは、プラットホームの矛盾点や、重量級コンポーネント用のネイティブフォーカスシステムと軽量コンポーネント用のJavaのフォーカスシステム間での不整合が原因でした。
AWTのフォーカスの実装において最悪の問題の一つは、「現在フォーカスされているコンポーネントを知ることができない」という事です。そのような問い合わせ用のAPIが無いだけでなく、現在のフォーカスをあらわす情報が保持されるようになっていない事も原因となっています。
さらに悪いことに、Window(FrameとDialogは違います)の子である軽量コンポーネントがキーボードからの入力を受取ることができないということです。この問題はウィンドウがWINDOW_ACTIVATED
イベントを一切受信しないので絶対にアクティブ化できないことと、アクティブ化したウィンドウだけが、フォーカスを得たコンポーネントを持つことが出来るからです。
さらに、たくさんの開発者が、FocusEventとWindowEventのAPIは、フォーカスやアクティブ化の変更に関係するコンポーネントを確定する方法を提供していないので、不十分だと気づきました。この例として、コンポーネントがFOCUS_LOST
イベントを受取ったとき、どのコンポーネントがフォーカスされるのか知る方法はありませんでした。Microsoft Windowsがこの機能をオープンに提供するようになってから、それまで疎外されて落胆していた開発者はMicrosoft WindowsのC/C++やVisual BasicからJavaにこの機能を移植しました。
私たちは、これらとそのほかの問題に取り組むために、JDK1.4のAWTの為の新しいフォーカスモデルを設計しました。設計の変更点で主だったものは、新しく中央集権化されたKeyboardFocusManagerクラスと、軽量コンポーネント用のフォーカスアーキテクチャの下で構築されました。AWTにおける、フォーカス関連のプラットホーム依存なコードの量は、より少なくなり、全てプラッガブルで拡張性に富んだ公開APIに置き換えられました。私たちは、既存の実装との下位互換性を保とうとしましたが、優雅で役に立つ結果にたどりつくために小さな互換性のない変更をせざるを得ませんでした。私たちは、これら互換性の無い変更が及ぼす影響が既存のアプリケーションにとって些細なものであることを願っています。
このドキュメントは"新しいAPI"と"新しいモデルと関係のある既存のAPI"の両方の公式な定義です。このドキュメントとフォーカス関連のクラスとメソッドのJavadocとを使えば、開発者は十分に、フォーカスの振る舞い(プラットホームを問わず一貫しているが、専用の)を備えたAWTとSwingのアプリケーションを開発できるようになるはずです。このドキュメントは下記のセクションで構成されています:
- KeyboardFocusManagerの概要
- KeyboardFocusManagerとBrowserのコンテキスト
- KeyEventDispatcher
- FocusEvent と WindowEvent
- Eventの配信
- 相対コンポーネントとウィンドウ
- 一時的なFocusEvents
- focus traversal
- focus traversal policy
- focus traversal policy provider
- Programmatic Traversal
- Focusability
- フォーカス可能なウィンドウ
- フォーカスを要求する
- フォーカスとPropertyChangeListener
- フォーカスとVetoableChangeListener
- Z-Order
- DefaultKeyboardFocusManagerの差し替え
- 前のリリースと非互換な部分
KeyboardFocusManagerの概要
フォーカスモデルは1つのクラス(KeyboardFocusManager)に中央集権化しています。そのクラスは、フォーカスの状態を問い合わせるクライアントコードの為に、APIセット(フォーカスの変更を開始する、標準のフォーカスイベント振り分け処理をカスタム"dispatcher"に置き換える)を提供しています。クライアントは直接フォーカスの状態を問い合わせることが可能です。もしくは、フォーカス状態に変化があった場合にPropertyChangeEvents
を受信する為の、PropertyChangeListener
を登録することが可能です。
KeyboardFocusManagerには7つの主なコンセプトとそれら専用の用語を取り入れられました:
- The "focus owner" -- キーボード入力を受取るコンポーネント
- The "permanent focus owner" -- 常に最後にフォーカスを受取るコンポーネント。一時的なフォーカスの変化が起こっていない状態では、"focus owner" と "permanent focus owner" は等しくなります。一時的なフォーカスの変更が終了した時は、"permanent focus owner"は、もう一度"focus owner"になります。
- The "focused Window" -- "focus owner"を持つウィンドウ。
- The "active Window" -- FrameもしくはDialog("focused Window"もしくは、"focused Window"のオーナーである、最初のFrameもしくはDialog)
- "Focus traversal" -- ユーザーがマウスカーソルを動かさずに、"focus owner"を変更すること。概して、これはキーボード(例:TABキー)か、それに相当する手に入りやすいデバイスを使って行われます。クライアントコードもこの"traversal"をプログラムから起動することができます。標準の"focus traversal"は"次の"コンポーネントへの"進める"ことも、"前の"コンポーネントへ"戻る"ことをも可能にします。
- "Focus traversal cycle" -- コンポーネント階層の一部("focus cycle"の中で"前へ" もしくは "後へ"標準のフォーカスが走査するような、"focus cycle"に含まれないものを除く全てコンポーネント)。この"cycle"は任意のコンポーネントから、その"次の"(foward traversal)と"前の"(forward traversal)コンポーネントへのマッピングを提供します。
- "Focus cycle root" -- コンテナ(特定の"focus traversal cycle"の為のコンポーネント階層における"root")。"focus owner"が特定のサイクルの中のコンポーネントである場合、標準の"進む" と "戻る" focus traversalは、コンポーネント階層の"focus cycle root"よりも上に、"focus owner"を移動させることはできません。その代わりに、キーボードとプログラムで、"focus traersal cycle"の階層を上がったり下がったりするナビゲーションを可能とする為、2つの"up cycle"と"down cycle"という操作が定義されました。
- "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"が存在します。
- Aは"focus cycle root"、A、B、C、FはAの"focus cycle"のメンバ
- Bは"focus cycle root"、B、E、DはBの"focus cycle"のメンバ
- 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種類のイベントを定義しています:
WindowEvent.WINDOW_ACTIVATED
: このイベントは、Frame
もしくはDialog
(決してWindow
では無い)がアクティブウィンドウになった時に、それらに配信されます。WindowEvent.WINDOW_GAINED_FOCUS
: このイベントはWindow
がフォーカスされたウィンドウになった場合に、そのWindow
に配信されます。フォーカス可能なウィンドウだけがこのイベントを受信できます。FocusEvent.FOCUS_GAINED
: このイベントはComponent
に、それが"focus owner"になった時に配信されます。フォーカス可能なComponent
だけがこのイベントを受信できます。FocusEvent.FOCUS_LOST
: このイベントは、そのComponent
が"focus owner"ではなくなる時に、そのComponent
に配信されます。WindowEvent.WINDOW_LOST_FOCUS
: このイベントはWindow
に、それがフォーカスされたWindowではなくなるときに、配信れます。WindowEvent.WINDOW_DEACTIVATED
: このイベントはDialog
(決してWindow
では無い)に、それがアクティブなウィンドウでは無くなる時に、配信されます。
Eventの配信
イベントは、それらが起こった順番で配信されます。例えば、ユーザーがアクティブでないフレームBのフォーカス可能な子コンポーネントAをクリックした場合、イベントは下記の順番で、配信されます:
- Bが
WINDOW_ACTIVATED
イベントを受信 - Bが
WINDOW_GAINED_FOCUS
イベントを受信 - Aが
FOCUS_GAINED
イベントを受信
その後、ユーザーが、ほかのフレームDのフォーカス可能な子コンポーネントCをクリックすると、下記の順番でイベントが配信されます:
- Aが
FOCUS_LOST
イベントを受信 - Bが
WINDOW_LOST_FOCUS
イベントを受信 - Bが
WINDOW_DEACTIVATED
イベントを受信 - Dが
WINDOW_ACTIVATED
イベントを受信 - Dが
WINDOW_GAINED_FOCUS
イベントを受信 - Cが
FOCUS_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.getOppositeComponent
、WindowEvent.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
クラスは、一時状態を引数として受取る、requestFocus
とrequestFocusInWindow
というメソッドを持っています。しかし、任意の一時状態の定義を、全てのネイティブウィンドウシステム上で、実装できない可能性があるので、このメソッドの正しい動作は、軽量コンポーネントにおいてのみ保証されます。このメソッドは一般的に利用される事を意図されたものでは無く、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
のコンテキスト全体の既定セットを再帰的に継承します。
AWTKeyStroke
APIを使うと、クライアントコードは、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-TAB
のKEY_PRESSED
- その他:
TAB
のKEY_PRESSED
とCTRL-TAB
のKEY_PRESSED
- TextArea:
- 前のコンポーネントへ戻す:
- TextAreas:
CTRL-SHIFT-TAB
のKEY_PRESSED
- その他:
SHIFT-TAB
のKEY_PRESSED
とCTRL-SHIFT-TAB
のKEY_PRESSED
- TextAreas:
- "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つの"focus cycle root"と1つのコンポーネントaがそのサイクルの中にあるとすると、その次のコンポーネントはaの後です。
- 1つの"focus cycle root"と1つのコンポーネントaがそのサイクルの中にあるとすると、その前のコンポーネントはaの前です。
- 1つの"focus cycle root"があり、そのサイクルの中の"最初"のコンポーネントがあるとすると、その"最初"のコンポーネントは、"進む"の方向のトラバースの1サイクルが終了した時にフォーカスされます。
- 1つの"focus cycle root"があり、そのサイクルの中の"最後"のコンポーネントがあるとすると、その"最後"のコンポーネントは"戻る"の方向のトラバースの1サイクルが終了した時にフォーカスされます。
- 1つの"focus cycle root"があり、そのサイクルの中の"既定"のコンポーネントがあるとすると、その"既定"のコンポーネントは、新しい"traversal cycle"への"traverse down"操作の際に、フォーカスを受取ります。これは"最初"のコンポーネントと同じかもしれませんが、そうである必要はありません。
FocusTraversalPolicy
はオプションとして、下記のアルゴリズムを提供するかもしれません:
Windowがあるとして、そのWindowの"初期"コンポーネント"があるとします。その"初期"コンポーー年とはWindowが表示されたときに、最初のフォーカスを受取ります。既定では、これは、"既定"のコンポーネントと同じです。
さらに、SwingはFocusTraversalPolicy
の1つのサブクラス(InternalFrameFocusTracersalPolicy
)を提供しています。開発者が下記のアルゴリズムを提供することができるように:
JInternalFrame
があり、JInternalFrame
上の"初期"コンポーネントがあるとします。
その初期コンポーネントはJInternalFrame
が最初に選択された時に、フォーカスを受信します。既定のままでは、これはJInternalFrame
の"既定"コンポーネントへのフォーカスと同じです。
FocusTraversalPolicy
はContainer.setFocusTraversalPolicy
をつかって、コンテナ上にインストールされます。もしポリシーが明示的に設定されていない場合、コンテナは、一番近い"focus-cycle-root"である祖先から継承します。最上位の初期のそれらの"focus traversal policy"は、コンテキストの既定のポリシーです。コンテキストの既定のポリシーは、KeyboardFocusManager.setDefaultFocusTraversalPolicy
を使って構築されます。
AWTはクライアントコードで使うための2つの標準のFocusTraversalPolicy
の実装を提供しています。
ContainerOrderFocusTraversalPolicy
: コンテナにコンポーネントを追加した順番で、"focus traversal cycle"に入った全コンポーネントに渡ってトラバースします。それぞれのコンポーネントはそのaccept(Component)
メソッドで適合性をチェックされます。既定では、コンポーネントは表示可能、有効、フォーカス可能なものだけが適合します。既定では、ContainerOrderFocusTraversalPolicy
は暗黙の内に、フォーカスの"down-cycle"を送信します。これは、通常の"forward focus traversal"が行われている間、"focus-cycle-root"の後にフォーカスされるコンポーネントは、"focus-cycle-root"の既定コンポーネントであるということです。このことで、"up and down-cycle traversal"のコンセプトが無いアプリケーションへの下位互換性を提供しています。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
の一種です。
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"のコンセプトが無いアプリケーションへの下位互換性を提供しています。- 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"の前のコンポーネントが返されます。
- 取得されたコンポーネントがコンテナ且つ"focus traversal policy provider"の場合、そのコンテナの
FocusTraversalPolicy.getFirstComponent
か、FocusTraversalPolicy.getLastComponent
で算出した、"最初"か、"最後"のコンポーネント、
- 取得されたコンポーネントが、コンテナで、且つ、"focus traversal policy provider"である場合、そのコンテナの
FocusTraversalPolicy
に既定コンポーネントを問い合わせて、その結果として、既定コンポーネントが返されます。
- 取得されたコンポーネントが、コンテナで、且つ、"focus traversal policy provider"である場合、そのコンテナの
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のオーナーであるかもしれません。既定では、全てのFrame
とDialog
は"focusable"です。Frame
やDialog
ではないが、スクリーンに表示されているFrame
やDialog
を直接保持しており、少なくとも1つの"focus traversal cycle"に属するコンポーネントを持つ全てのウィンドウは、既定で"focusable"となります。"non-focusable"なWindowを作るには、Window.setFocusableWindowState(false)
を使ってください。
ウィンドウが"non-focusable"の場合、この制限はKeyboardFocusManager
がWINDOW_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
クラスも、クライアントコードが一時的な状態を定義することを可能にする、requestFocus
とrequestFocusInWindow
をサポートしています。一時的なFocusEvents
を参照してください。
フォーカスとPropertyChangeListener
クライアントコードPropertyChangeListener
経由で、コンテキストワイドなフォーカスの状態の変更や、フォーカスに関係する状態を監視することが出来ます。
KeyboardFocusManager
は下記のプロパティをサポートしています:
focusOwner
: "focus owner"focusedWindow
: "focused Window"activeWindow
: "active WindowdefaultFocusTraversalPolicy
: 既定の"focus traversal policy"forwardDefaultFocusTraversalKeys
: 既定のFORWARD_TRAVERSAL_KEYS
のSet
backwardDefaultFocusTraversalKeys
: 既定のBACKWARD_TRAVERSAL_KEYS
のSet
upCycleDefaultFocusTraversalKeys
: 既定のUP_CYCLE_TRAVERSAL_KEYS
のSet
downCycleDefaultFocusTraversalKeys
: 既定のDOWN_CYCLE_TRAVERSAL_KEYS
のSet
currentFocusCycleRoot
: 現在の"focus cycle root"
現在のKeyboardFocusManager
にインストールされたPropertyChangeListener
は"focus owner"、"focused Window"、"active Window"、全てのコンテキストで共有されるグローバルなフォーカス状態を構成する現在の"focus cycle root"のいずれであったとしても、そのKeyboardFocusManager
のコンテキストの中のフォーカスの変更しか監視することはできません。私達はこれを、クライアントコードがPropertyChangeListener
をインストールする前に、セキュリティチェックをパスする必要があるよりは、煩わしくないと信じています。
Component
は下記のフォーカスに関連するプロパティをサポートしています:
focusable
: そのComponent
の"focusability"focusTraversalKeysEnabled
: そのComponent
の"focus traversal keys"の有効状態forwardFocusTraversalKeys
: そのComponent
のFORWARD_TRAVERSAL_KEYS
のSet
backwardFocusTraversalKeys
: そのComponent
のBACKWARD_TRAVERSAL_KEYS
のSet
upCycleFocusTraversalKeys
: そのComponent
のUP_CYCLE_TRAVERSAL_KEYS
のSet
Component
のプロパティに加えて、Container
は下記のフォーカス関連プロパティをサポートしています:
downCycleFocusTraversalKeys
: そのContainer
のDOWN_CYCLE_TRAVERSAL_KEYS
のSet
focusTraversalPolicy
: そのContainer
の"focus traversal policy"focusCycleRoot
: そのContainer
の"focus cycle root"の状態
Container
のプロパティに加えて、Window
は下記のフォーカス関連プロパティをサポートしています:
focusableWindow
: そのWindow
の"focusable Window state"
Window
にインストールされたPropertyChangeListener
は絶対に、focusCycleRoot
プロパティのPropertyChangeEvent
を監視することはできません。WIndow
は常に、"focus cycle root"であり、このプロパティは変更できません。
Focus and VetoableChangeListener
KeyboardFocusManager
も下記のプロパティに対して、VetoableChangeListener
をサポートします:
- "focusOwner": the focus owner
- "focusedWindow": the focused Window
- "activeWindow": the active Window
VetoableChangeListener
がPropertyVetoException
を投げる事によって、フォーカスや、アクティベーションの変更を禁止する場合、その変更は無視されます。既にその変更を承認した、VetoableChangeLisntener
は、状態が前の値に戻った事を示すPropertyChangeEvent
を非同期で受信します。
VetoableChageListener
は、変更が、KeyboardFocusManager
に反映される前に、その状態の変更が通知されます。逆に、PropertyChangeListener
は変更の反映後に、通知されます。これは、全てのVetoableChangeListener
は、どんなPropertyChangeListener
より先に通知を受取る事を表しています。
VetoableChageListener
は、冪等でなければならず、特定のフォーカス変更(FOCUS_LOSTと、FOCUS_GAINED等)におけるフォーカスを失うイベントと、得るイベントのどちらもを拒否しなければなりません。例として、VetoableChangeListener
が、FOCUS_LOST
イベントを禁止する場合、KeyboardFocusManager
はEventQueue
の検索と、関連するペンディングの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
は、拒否のリカバリの結果として開始されるフォーカスの変更を禁止することを注意深く避けなければなりません。この予測に失敗すると、フォーカス変更の禁止と、それをリカバリーしようとする無限のサイクルを引き起こしてしまいます。