torutkのブログ

ソフトウェア・エンジニアのブログ

ユーザー操作イベントを一括で拾うには?

デバッグやログなどの目的で、JavaFXGUI上で発生したイベントを一括して取得する方法を調べてみました。簡単に見つかると思っていたら、ちょっとはまりました。

Swingであれば、EventQueueをいじれば取れるのですが、JavaFXはそれらしいクラスが見当たりません。

そしたら、JavaFXのイベント処理の流れを調べて説明している日本語のブログが見つかりました。
http://sunday-programming.hatenablog.com/entry/2014/02/02/223834

OracleJavaFXドキュメント(Handling Events)を解説しています。
http://docs.oracle.com/javase/8/javafx/events-tutorial/events.htm

このブログとOracleJavaFXドキュメントを調べていくと、ボタンやシェープなどをユーザーが操作すると、

1. 操作対象となるノードの判定
2. イベントを伝播するルートの構築
3. イベント捕捉(Event capturing)
4. イベントの浮上(Event bubbling)

という段階を踏みます。なかなかピンと来ませんが、どうやらシーングラフの最上位であるStageにはすべてのイベントが集まるようです。
そこで、Stageにevent filterを設定してみます。

public class ZoomPanCanvas extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("ZoomPanCanvas.fxml"));

        Scene scene = new Scene(root);

        stage.setScene(scene);
        setFilter(stage); // stageにイベントフィルタを仕掛ける
        stage.show();
    }

    private void setFilter(Stage stage) {
        stage.addEventFilter(EventType.ROOT, e -> System.out.println(e));
    }
  :

と、stageにaddEventFilterでイベントタイプの最上位であるEventType.ROOTを指定すると、すべてのイベントタイプが拾えると思われます。

これを実行すると、マウス操作で幾種類かイベントがプリント出力されます。

WindowEvent [source = javafx.stage.Stage@d2831d1, target = javafx.stage.Stage@d2831d1, eventType = WINDOW_SHOWING, consumed = false]
WindowEvent [source = javafx.stage.Stage@d2831d1, target = javafx.stage.Stage@d2831d1, eventType = WINDOW_SHOWN, consumed = false]
MouseEvent [source = javafx.stage.Stage@d2831d1, target = javafx.scene.Scene@11702c02, eventType = MOUSE_ENTERED_TARGET, consumed = false, x = 182.0, y = 22.0, z = 0.0, button = NONE, pickResult = PickResult [node = Canvas[id=canvas], point = Point3D [x = 184.0, y = 22.0, z = 0.0], distance = 559.8076211353316]
MouseEvent [source = javafx.stage.Stage@d2831d1, target = AnchorPane[id=AnchorPane, styleClass=root], eventType = MOUSE_ENTERED_TARGET, consumed = false, x = 182.0, y = 22.0, z = 0.0, button = NONE, pickResult = PickResult [node = Canvas[id=canvas], point = Point3D [x = 184.0, y = 22.0, z = 0.0], distance = 559.8076211353316]
MouseEvent [source = javafx.stage.Stage@d2831d1, target = Canvas[id=canvas], eventType = MOUSE_ENTERED_TARGET, consumed = false, x = 182.0, y = 22.0, z = 0.0, button = NONE, pickResult = PickResult [node = Canvas[id=canvas], point = Point3D [x = 184.0, y = 22.0, z = 0.0], distance = 559.8076211353316]

ボタンを押すところのイベントは次のようになります。

MouseEvent [source = javafx.stage.Stage@d2831d1, target = Text[text="真ん中表示", x=0.0, y=0.0, alignment=LEFT, origin=BASELINE, boundsType=LOGICAL_VERTICAL_CENTER, font=Font[name=System Regular, family=System, style=Regular, size=12.0], fontSmoothingType=LCD, fill=0x333333ff], eventType = MOUSE_PRESSED, consumed = false, x = 344.0, y = 284.0, z = 0.0, button = PRIMARY, primaryButtonDown, pickResult = PickResult [node = Text[text="真ん中表示", x=0.0, y=0.0, alignment=LEFT, origin=BASELINE, boundsType=LOGICAL_VERTICAL_CENTER, font=Font[name=System Regular, family=System, style=Regular, size=12.0], fontSmoothingType=LCD, fill=0x333333ff], point = Point3D [x = 10.0, y = -8.0, z = 0.0], distance = 559.8076211353316]
MouseEvent [source = javafx.stage.Stage@d2831d1, target = Text[text="真ん中表示", x=0.0, y=0.0, alignment=LEFT, origin=BASELINE, boundsType=LOGICAL_VERTICAL_CENTER, font=Font[name=System Regular, family=System, style=Regular, size=12.0], fontSmoothingType=LCD, fill=0x333333ff], eventType = MOUSE_RELEASED, consumed = false, x = 344.0, y = 284.0, z = 0.0, button = PRIMARY, pickResult = PickResult [node = Text[text="真ん中表示", x=0.0, y=0.0, alignment=LEFT, origin=BASELINE, boundsType=LOGICAL_VERTICAL_CENTER, font=Font[name=System Regular, family=System, style=Regular, size=12.0], fontSmoothingType=LCD, fill=0x333333ff], point = Point3D [x = 10.0, y = -8.0, z = 0.0], distance = 559.8076211353316]
javafx.event.ActionEvent[source=javafx.stage.Stage@d2831d1]

ボタンが押されたというイベントは、マウスイベントのPRESSEDとRELEASEDが連続して発生したときに発生するので、MouseEventが2つとそれに続いてActionEventが発生するということかと思います。

EventFilterを設定することで、イベントを一括して取得することができるようです。