1 イベントの処理
このトピックでは、JavaFXアプリケーションにおけるイベントおよびイベント処理について説明します。 イベント・タイプ、イベント・ターゲット、イベント・キャプチャ、イベント・バブリング、およびイベント処理システムの基礎となるアーキテクチャについて学習します。
イベントは、ユーザーが実行したアクションをアプリケーションに通知し、それに対するレスポンスが返されるようにするために使用します。 JavaFXプラットフォームには、イベントをキャプチャしてターゲットにルーティングし、アプリケーションでそのイベントを必要に応じて処理できるようにするための構造が備わっています。
イベント
イベントは、マウスの移動やキーの押下など、アプリケーションで重要となるなんらかの出来事を表します。 JavaFXにおけるイベントは、javafx.event.Event
クラスまたはEvent
のサブクラスのインスタンスです。 JavaFXには、DragEvent
、KeyEvent
、MouseEvent
、ScrollEvent
など、いくつかのイベントが用意されています。 Event
クラスを拡張することによって独自のイベントを定義することもできます。
各イベントには、表1-1に示す情報が含まれています。
表1-1 イベント・プロパティ
プロパティ | 説明 |
---|---|
イベント・タイプ |
発生したイベントのタイプです。 |
ソース |
イベント・ディスパッチ・チェーンにおけるイベントの場所という観点でのイベントの起点です。 イベントがチェーンに沿って渡されるにつれ、ソースが変わります。 |
ターゲット |
アクションが発生したノードおよびイベント・ディスパッチ・チェーンにおける最終ノードです。 ターゲットは変わりませんが、イベント・キャプチャ・フェーズでイベント・フィルタによりイベントが消費された場合は、ターゲットにイベントは渡されません。 |
各サブクラスでは、イベントのタイプに固有の追加情報が扱われます。 たとえば、MouseEvent
クラスには、押されたボタンの種類、ボタンが押された回数、マウスの位置などの情報が含まれています。
イベント・タイプ
イベント・タイプは、EventType
クラスのインスタンスです。 各タイプにより、単一のイベント・クラスのイベントがさらに細かく分類されます。 たとえば、KeyEvent
クラスには次のイベント・タイプが含まれています。
-
KEY_PRESSED
-
KEY_RELEASED
-
KEY_TYPED
イベント・タイプは階層構造になっています。 すべてのイベント・タイプに名前とスーパータイプがあります。 たとえば、キー押下イベントの場合、名前はKEY_PRESSED
、スーパータイプはKeyEvent.ANY
です。 最上位のイベント・タイプのスーパータイプはnullです。 図1-1に、階層の一部を示します。
階層の最上位のイベント・タイプは、Event.ANY
と同等のEvent.ROOT
です。 サブタイプでは、イベント・クラス内のあらゆるイベント・タイプという意味でイベント・タイプANY
を使用します。 たとえば、あらゆるタイプのイベントに対して同じレスポンスが返されるようにするには、イベント・フィルタまたはイベント・ハンドラのイベント・タイプとしてKeyEvent.ANY
を使用します。 キー解放時にのみレスポンスが返されるようにするには、フィルタまたはハンドラのイベント・タイプとしてKeyEvent.KEY_RELEASED
を使用します。
イベント・ターゲット
イベントのターゲットは、EventTarget
インタフェースを実装するクラスのインスタンスです。 buildEventDispatchChain
を実装した場合、イベントをターゲットに配信するためのイベント・ディスパッチ・チェーンが作成されます。
EventTarget
インタフェースはWindow
、Scene
およびNode
クラスにより実装され、これらのクラスのサブクラスにより実装が継承されます。 したがって、ユーザー・インタフェース内のほとんどの要素にはディスパッチ・チェーンが定義されているため、イベント・ディスパッチ・チェーンの作成に関与することなくイベントに対するレスポンスに重点を置いて作業できます。
ユーザー・アクションに対するレスポンス用としてカスタムUIコントロールを作成し、そのコントロールがWindow
、Scene
またはNode
のサブクラスである場合、コントロールは継承を通じてイベント・ターゲットになります。 コントロールまたはその要素がWindow
、Scene
またはNode
のサブクラスでない場合は、そのコントロールまたは要素に対してEventTarget
インタフェースを実装する必要があります。 たとえば、MenuBar
コントロールは継承を通じてターゲットになりますが、メニュー・バーのMenuItem
要素にイベントが渡されるようにするには、EventTarget
インタフェースを実装する必要があります。
イベント配信プロセス
イベント配信プロセスは次のステップで構成されます。
-
ターゲットの選択
-
ルートの構成
-
イベント・キャプチャ
-
イベント・バブリング
ターゲットの選択
アクションが実行されると、内部ルールに基づいてターゲットとなるノードが判別されます。
-
キー・イベントの場合、フォーカスのあるノードがターゲットになります。
-
マウス・イベントの場合、カーソル位置にあるノードがターゲットになります。 合成マウス・イベントの場合、タッチ・ポイントがカーソル位置とみなされます。
-
タッチ画面上でのジェスチャによって生成される連続ジェスチャ・イベントの場合、ジェスチャ開始時のすべてのタッチの中心にあるノードがターゲットになります。 トラックパッドなど、タッチ画面以外でのジェスチャによって生成される間接ジェスチャ・イベントの場合、カーソル位置にあるノードがターゲットになります。
-
タッチ画面上でのスワイプによって生成されるスワイプ・イベントの場合、すべての指の経路全体の中心にあるノードがターゲットになります。 間接スワイプ・イベントの場合、カーソル位置にあるノードがターゲットになります。
-
タッチ・イベントの場合、最初の押下位置にあるノードが各タッチ・ポイントのデフォルト・ターゲットになります。 イベント・フィルタまたはイベント・ハンドラでタッチ・ポイントに対して
ungrab()
、grab()
またはgrab(
node)
メソッドを使用することによって、別のターゲットを指定することもできます。
カーソル位置またはタッチ・ポイントに複数のノードが存在する場合は、最上位ノードがターゲットとみなされます。 たとえば、図1-2に示す三角形をユーザーがクリックするか、タッチした場合、円と三角形を含む四角形ではなく、三角形がターゲットになります。
マウス・ボタンが押されてターゲットが選択された場合、ボタンが放されるまで、後続のすべてのマウス・イベントが同じターゲットに配信されます。 同様に、ジェスチャ・イベントの場合、ジェスチャが開始されてから完了するまで、ジェスチャ開始時に指定されたターゲットにジェスチャ・イベントが配信されます。 タッチ・イベントの場合、デフォルトでは、ungrab()
、grab()
またはgrab(
node)
メソッドを使用してターゲットが変更されないかぎり、各タッチ・ポイントに指定された初期ターゲット・ノードにイベントが配信されます。
ルートの構成
初期イベント・ルートは、選択されたイベント・ターゲットのbuildEventDispatchChain()
メソッドの実装で作成されたイベント・ディスパッチ・チェーンによって決まります。 たとえば、図1-2に示す三角形をユーザーがクリックした場合、図1-3のグレーのノードが初期ルートになります。 イベント・ターゲットとしてシーン・グラフ・ノードが選択された場合は、Node
クラスのbuildEventDispatchChain()
メソッドのデフォルト実装で設定されている初期イベント・ルートがステージからそれ自体までの経路になります。
ルートは、そのルートに沿ったイベント・フィルタおよびイベント・ハンドラによりイベントが処理されたときに変更されることがあります。 また、いずれかの時点でイベント・フィルタまたはイベント・ハンドラによりイベントが消費されると、初期ルート上の一部のノードにイベントが渡されない場合があります。
イベント・キャプチャ・フェーズ
イベント・キャプチャ・フェーズでは、アプリケーションのルート・ノードによってイベントがディスパッチされ、イベント・ディスパッチ・チェーンに沿ってターゲット・ノードに渡されます。 図1-3に示すイベント・ディスパッチ・チェーンを使用した場合、イベントはイベント・キャプチャ・フェーズにステージ・ノードから三角形ノードに配信されます。
チェーン内のいずれかのノードに、発生したイベントのタイプに対するイベント・フィルタが登録されている場合は、そのフィルタがコールされます。 フィルタが完了した時点で、イベントはチェーンに沿って次の下位ノードに渡されます。 ノードにフィルタが登録されていない場合、イベントはチェーンに沿って次の下位ノードに渡されます。 フィルタによりイベントが消費されなければ、イベントは最終的にイベント・ターゲットに渡されて処理されます。
イベント・バブリング・フェーズ
イベント・ターゲットに渡されてすべての登録済フィルタによって処理されたイベントは、ディスパッチ・チェーンに沿ってターゲットからルート・ノードに戻されます。 図1-3に示すイベント・ディスパッチ・チェーンを使用した場合、イベントはイベント・バブリング・フェーズに三角形ノードからステージ・ノードに戻されます。
チェーン内のいずれかのノードに、発生したイベントのタイプに対するハンドラが登録されている場合は、そのハンドラがコールされます。 ハンドラが完了した時点で、イベントはチェーンに沿って次の上位ノードに戻されます。 ノードにハンドラが登録されていない場合は、イベントはチェーンに沿って次の上位ノードに戻されます。 ハンドラによりイベントが消費されなければ、イベントは最終的にルート・ノードに戻されて、処理は完了します。
イベント処理
イベント処理は、EventHandler
インタフェースの実装であるイベント・フィルタおよびイベント・ハンドラによって行われます。 イベント発生時にアプリケーションに通知する場合は、そのイベントに対するフィルタまたはハンドラを登録します。 フィルタとハンドラの主な違いはそれぞれが実行されるタイミングです。
イベント・フィルタ
イベント・フィルタはイベント・キャプチャ・フェーズに実行されます。 親ノードのイベント・フィルタでは、複数の子ノードのイベント処理を共通化でき、必要であればイベントを消費して子ノードにそのイベントが渡されないようにすることができます。 発生したイベントのタイプに対するフィルタがノードに登録されている場合、イベントがノードを通過するときにそのフィルタが実行されます。
1つのノードに複数のフィルタを登録できます。 各フィルタがコールされる順序は、イベント・タイプの階層に基づきます。 特定のイベント・タイプに対するフィルタは、一般イベント・タイプに対するフィルタよりも先に実行されます。 たとえば、MouseEvent.MOUSE_PRESSED
イベントに対するフィルタは、InputEvent.ANY
イベントに対するフィルタよりも先にコールされます。 同一レベルにある2つのフィルタの実行順序は決まっていません。
イベント・ハンドラ
イベント・ハンドラはイベント・バブリング・フェーズに実行されます。 子ノードのイベント・ハンドラによりイベントが消費されない場合は、子ノードでのイベント処理後に親ノードのイベント・ハンドラがそのイベントに作用したり、複数の子ノードのイベント処理を共通化できます。 発生したイベントのタイプに対するハンドラがノードに登録されている場合、イベントがノードを通過するときにそのハンドラが実行されます。
1つのノードに複数のハンドラを登録できます。 各ハンドラがコールされる順序は、イベント・タイプの階層に基づきます。 特定のイベント・タイプに対するハンドラは、一般イベント・タイプに対するハンドラよりも先に実行されます。 たとえば、KeyEvent.KEY_TYPED
イベントに対するハンドラは、InputEvent.ANY
イベントに対するハンドラよりも先にコールされます。 同一レベルにある2つのハンドラの実行順序は決まっていませんが、例外として、「コンビニエンス・メソッドの使用」で説明するコンビニエンス・メソッドによって登録されたハンドラは最後に実行されます。
イベントの消費
イベント・ディスパッチ・チェーンの任意の時点でconsume()
メソッドをコールすることによって、イベント・フィルタまたはイベント・ハンドラでイベントを消費できます。 このメソッドにより、イベントの処理が完了し、イベント・ディスパッチ・チェーンのトラバースが終了したことが通知されます。
イベント・フィルタでイベントが消費されると、イベント・ディスパッチ・チェーンの子ノードがそのイベントに作用できなくなります。 イベント・ハンドラでイベントが消費されると、イベント・ディスパッチ・チェーンで親ハンドラによるイベント処理がその後行われなくなります。 ただし、イベントを消費するノードにそのイベントに対するフィルタまたはハンドラが複数登録されている場合は、ピア・フィルタまたはピア・ハンドラが引き続き実行されます。
たとえば、図1-3に示すイベント・ディスパッチ・チェーンを使用し、ペイン・ノードにKeyEvent.KEY_PRESSED
イベントに対するイベント・フィルタおよびInputEvent.ANY
イベントに対するイベント・フィルタが登録されているものと仮定します。 キー押下イベントに対するフィルタによりイベントが消費された場合、入力イベントに対するフィルタが実行され、三角形ノードにはイベントは渡されません。
JavaFX UIコントロールのデフォルト・ハンドラでは一般にほとんどの入力イベントが消費されます。