JavaFX 11に追加されたRobotクラスの紹介

このエントリは じゃばえふえっくす Advent Calendar 2018 の 3 日目のエントリとしてぶっ込みました。もうクリスマスは終わってますが、まだ空いていますし、 1 日目のエントリ で次のようなことを書いていたのをまだ放置していたので、それを拾うエントリとなります。

上記の Maven リポジトリをご覧になると分かりますが、Java FX 11 もリリースされました。リリースノートは次の通りです。

https://github.com/javafxports/openjdk-jfx/blob/jfx-11/doc-files/release-notes-11.md

まあ新機能は少ないのですが、 Spinner コントロールに改善が加えられていたり (詳しくは Yucchi_jp さんのこのブログエントリ を) 、新たに Robot というクラスが追加されています。この Robot クラスについては別のエントリで触れる予定です。

というわけで、JavaFX 11 になって新たに追加された Robot クラスについて触れたいと思います。

Robot クラスはプログラムから画面操作を行うために必要なメソッドが実装されたクラスです。あたかもロボットが画面操作を人の代わりに行うようなことを実現するというわけです。 AWT にも同様のクラス があります。一番のユースケースGUI の自動テストを実装することで、それを実現するために次のような操作を実行するためのメソッドが実装されています。

  • マウスの各種操作 (移動、クリック、スクロールホイール) を行う
    • マウスの現在位置を取得するためのメソッドもあります
  • キーボードをタイプする
    • タイプとは別にプレスとリリースにもメソッドが用意されています
  • 画面のスクリーンショットを撮る
  • 指定した座標の色を取得する

モジュールは javafx.graphics に、パッケージは javafx.scene.robot に入っています。

画面を操作するのに必要なものは一通りそろっているので、これで定形作業を肩代わりするプログラムを作るのに使えますね。ゲームの周回操作とかにもいいかも。

実はこのクラス、昔からあったのですが internal なクラスとなっていました。このクラスを使って自動テストフレームワークを作っていたライブラリが多く、Java のモジュール化に伴い「外に出してほしい」というリクエストが非常に多かったのですが、11 になって晴れて表に出てきてくれました。

ところで以前、自分の書いた次のエントリで、JavaFX で xeyes のクローンを作った話をしました。

aoe-tk.hatenablog.com

そこでこんなことを書いていました。

  • 以前は Mac 上で Swing の EDT を実行すると HeadlessException が飛ぶ問題があり、仕方なく Swing を土台として作っていたのですが、現在はこの問題は解消されているので、JavaFX を土台としたものに変更しています。
    • 唯一、グローバルなマウスカーソルのアドレスを取得するのに AWT に頼っていますが、JavaFX 11 で待望の Robot クラスが追加されるので、11 になれば "Pure JavaFX" なアプリケーションにできる見込みです。

てなわけで、早速この Robot を使って "Pure JavaFX" なアプリにしちゃいましょう。

まずは 10 以前のマウスポジションを取得するコード。

private void updateMousePosition() {
    SwingUtilities.invokeLater(() -> {
        final Point pointerLocation = MouseInfo.getPointerInfo().getLocation();
        Platform.runLater(() -> updateEye(pointerLocation.x, pointerLocation.y));
    });
}

Swing の EDT と JavaFX のアプリケーションスレッドを行ったり来たりして見通しが悪いですね。これを 11 の Robot クラスを使うように変更します。使い方は簡単で、普通にインスタンスを取得すればいいです。Controller の初期化処理で取得しておきます。

private Robot robot;

@Override
public void initialize(URL location, ResourceBundle resources) {
    robot = new Robot();
    // (以下略)

後はこのインスタンスを使ってマウスカーソルのグローバルポジションを取得するだけです。 updateMousePosition() メソッドの内容が次のように変わりました。

private void updateMousePosition() {
    updateEye(robot.getMouseX(), robot.getMouseY());
}

はい、これで複数スレッドを行き来する必要がなくなりました。しかも java.desktop モジュールへの依存も無くなるので、配布 JRE のイメージサイズも小さくすることができますね。 *1

少し付け加えると、 Robot から取得するマウス座標の値は AWT と違って double 型になります。なので updateEye() メソッドの引数の型を変える必要がありました。

というわけで JavaFX 11 の新機能である Robot クラスの紹介でした。さすがに今年の Advent Calendar への投稿はこれで終わりかな。皆様良いお年を。

*1:AWT、Java2D、Swing を全部雑に java.desktop モジュールにぶち込んでるんですよね...。お陰でサーバーサイドアプリケーションでも JavaBeans 関連の API を使うだけなのにこのモジュールへの依存が必要がだったり。