この記事はJavaFX Advent Calender 2016の17日目の記事になります。
前日はid:skrbさんのSooner or Later - JavaFX in the Boxでした。
明日はid:aoe-tkさんです。
前回今年最後とか言ったな。あれは嘘だ。
はい、というわけで、なんとJavaFX Advent Calender 2016に二度も顔出しですよ。この引きこもりの王者クソニート様が。誰かお仕事くらさい
というのもですね、JavaFX9で追加される新機能は前回紹介した内容以外にも、PulseListenerの追加やPlatform.startupやら、他にも小さな機能の追加があるのです。これらの使い道が私には思いつかなかったか、そもそもあまり興味を持っていなかったかなどの理由でスルーしたのですが、実はスルーしてた中でPlatformにNested Event Loop関連のAPIが追加になっていたようなのです!
いや、もうマジでこれ知らなかった。知ってたら前回の記事に絶対追加してた。というわけで、興奮してついうっかり2回も顔出しすることにしました。ほんとスマンかった。
なお、このことはJavaFX: New & Noteworthy(※PDF)にも書いてあります。しかし、Nested Event Loopの話がPulseとPulse Listenerのページの間に挟まれているんですよね。Pulseとかあまり興味ないせいか、読むのをすっ飛ばした様で気がつかなかったんですね~。アホですね~。
というわけで、Platformに追加されるAPI(主にNested Event Loop)について解説します。
Platform.startup
Application.launch使う限り使わないと思われるAPI。SWTとかSwingから使うときとかを想定している模様。
JavaFX Application Threadを生成直後に引数に渡したRunnableを実行するとかそんな感じっぽいけど、普通に使う場面はないと思う。ないよね?思いつかなかったから前回もスルーしたんだけど。
Platform.requestNextPulse
Scene.addPostLayoutPulseListener,Scene.addPreLayoutPulseListenerと共に追加されたPulse関連のAPI。次のPulseの要求が出来る。
JavaFXには我々が扱うJavaFX Application Threadの他にPrism Render Threadという、画面描画用のスレッドがあるのですが、この描画スレッドとJavaFX Application Threadを同期するイベントがPulseになります。
JavaFXってマウスイベントとかにNodeを追随させようとしても、妙にマウスの動きからNodeの動きがずれるのが、改善されたりしないかとか思ったけど、全くそんなことはなかったぜ。
これらPulse関連APIの使い道は、今のところ私には思いつきません。PulseListenerの方はレイアウトにどれぐらい時間かかってるか、デバッグ的な使い方くらいかなぁ。私の頭じゃ。
Pulseについての詳細は以下のページを参照してください。
docs.oracle.com
Nested Event Loop
さて、今回の記事の本題です。といっても、実はこのNested Event LoopはJavaFX初期からあって、3年前の私の記事でも実はついでに紹介しています。
nodamushi.hatenablog.com
大ざっぱに書くと、JavaFX Application Threadは通常以下のような処理をしています。
Nested Event LoopのAPIを使うと、この流れを以下の図のように、複製することが可能になります。
複製と言っても、新しいスレッドが走っているとかではなくて、単純に呼び出した関数の中でイベント処理のループが走り始めると言うだけです。
この機能がおもにどこで使われているかというと、showAndWaitメソッドです。showAndWaitでウィンドウが閉じるまで処理を待機しても、JavaFX Application Threadは停止することなく、他の処理を続けることが出来ます。この時に使われているのがNested Event Loopです。
今までは、Stage.showAndWaitからしかアクセスできなかったこのNested Event LoopのAPIが公開され、いつでもどこでも使えるようになります。
一番簡単に思いつく使い道で、かつ、効果が高いのは、Stageのダイアログと同様にNode単位でのモーダルダイアログでしょう。
今までは、このようなことを実現しようとしたら、次のような2つに分かれた手順が必要でした。
- なんらかのイベントハンドラ等からダイアログ用のNodeを作り、対象ノードの操作を不可能にする。(ここで最初のイベントハンドラの処理終了)
- ダイアログのYesやCancelといったボタンが押されたときに動作するイベントハンドラが、残りの処理を行い、ダイアログを消去する。
しかし、Nested Event Loopを使うと、一つのイベントハンドラの中で処理が完結します。
これはかなりプログラムを書くのが楽ですね。
最近はJavaもラムダを導入したり、実質finalが導入されたりと色々工夫されて、イベントハンドラを書くのが楽になってきましたが、それでも処理が二分割されるようなプログラムを書くのって面倒くさいんですよね。
処理が二分割で済めば良いですが、何回かダイアログを表示して、しかもそのダイアログ内容が途中で分岐するような処理だったら、面倒なことこの上ないですよね。
一つのEvent Handlerで処理が済むというのは、それだけで実装面からも保守管理の点からもメリットです。
Nested Event Loopの使い方
Nested Event Loopを利用するには、以下のフィールド変数(ローカル変数は駄目)が必要です。
private Object waitKey = null;
- waitKey: Nested Event Loopを解除するのに必要になるキー。なくしたらexitできないので要注意。final宣言してしまっても良い。
フィールド変数を用意したら、後は以下の関数を定義しておけば良いでしょう。
なお、基本的にはJavaFX Application Threadからしか操作しないので、waitKeyをvolatile等にする必要はありません。
private Object waitKey = null; public boolean isWait(){return waitKey!=null;} // 戻り値はintなどに決まっているなら、それに変換しても良い。 protected Object enterWait(){ if(!isWait()){ waitKey = new Object(); Object result = Platform.enterNestedEventLoop(waitKey); return result; } return null; } // 引数のresultは上↑のresultになる。 protected void exitWait(Object result){ if(isWait()){ Object key=waitKey; waitKey=null; Platform.exitNestedEventLoop(key,result); } }
そしたら、後はこんな感じ。
//ダイアログのNodeを作成完了後 cancelButton.setOnAction(e->exitWait(0)); okButton.setOnAction(e->exitWait(1)); noButton.setOnAction(e->exitWait(2)); //何かにダイアログを追加 targetPane.getChildren().add( dialog ); //ダイアログのボタンが押されるまで待機 boolean isOK = enterWait() == 1; //ダイアログを削除 targetPane.getChildren().remove( dialog ); if(isOK){//OKなら処理 … }
むろん、Dialogクラスのように、ダイアログ表示機能と待機機能を何かのクラスにまとめておいても良いでしょう。
このように、Nested Event Loopを使えば、処理の途中でGUIを介した処理の結果が必要である場面の記述が簡単になります。色々夢が広がリングですね。
というわけで、今度こそ、良いお年を。