JavaFX で Windows のシステムトレイを使おうとした場合、
JavaFX 内での解決方法はなく、Java 6 で導入された java.awt.TrayIcon を使わないといけないようです。
TrayIcon 自体の使い方は
- Javaでかんたんタスクトレイ常駐アプリ - 砂漠の音楽
http://d.hatena.ne.jp/fuzzhead/20081027/p1
といったページなどでまとめられているので、そちらを参照して下さい。
ここでは TrayIcon を JavaFX で使う場合の注意点について触れます。
- TrayIcon に登録した Listner から、JavaFX の UI に対して操作を行う場合には、直接操作を行うのではなく、 Platform.runLater() を使って JAT コンテキスト内で処理されるようにする
- アプリケーションの終了は System.exit() ではなく、JavaFX で Platform.setImplicitExit(false) した上で Platform.exit() を使う。
その上で exit する際には必ず明示的に SystemTray.getSystemTray().remove() を呼んで SystemTrayIcon を消すようにする
という2点です。
ズバリ、この点については次のページを参考にしています。
- awt - JavaFX app in System Tray - Stack Overflow
http://stackoverflow.com/questions/12571329/javafx-app-in-system-tray - Demonstrate using the System Tray (AWT) to control a JavaFX application.
https://gist.github.com/jewelsea/e231e89e8d36ef4e5d8a
が、一応、私なりの補足を書いておくと…
注意点の1点目は JavaFX の GUI スレッド用の対応です。
JavaFX の解説書や JavaFX でスレッドを扱う場合の記事などで良く触れられますが、 JavaFX は GUI 用に独自のスレッド JavaFX Application Thread (JAT) を持っていて、GUI の操作は全て JAT のスレッドのコンテキスト内から行わないといけません。
しかし TrayIcon は、 JavaFX ではなく awt のためスレッドのコンテキストが分かれてしまっています。 そのため、TrayIcon に登録した Listner 側からは JavaFX GUI の操作が直接できません。
そういう場合には Platform.runLater() を使うと、JAT とは別のスレッドコンテキストから JAT に対して処理依頼が出来るようになります。
(runLater() で依頼された処理は、JAT 側のキューにためられて順次処理がされます)
例えば、TrayIcon をダブルクリックしたら、JavaFX のメインウィンドウ(primaryStage)を隠したい(hide() したい)場合などは次のような感じになります。
SystemTray tray = SystemTray.getSystemTray();
TrayIcon icon = new TrayIcon(ImageIO.read(getClass().getResourceAsStream("hoge.png")));
icon.setImageAutoSize(true); // hoge.png を trayicon サイズに自動調整
icon.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Platform.runLater(() -> {
primaryStage.hide();
});
}
});
tray.add(icon);
注意点の2点目は、JavaFX での終了処理の作法に絡んだ対応です。
JavaFX アプリは Platform.exit() を使うのが作法であります。
Application の Javadoc によれば
JavaFXアプリケーションを明示的に終了するには、Platform.exit()を呼び出すことをお薦めします。代替方法としてSystem.exit(int)を直接呼び出すことが許容されますが、Applicationはstop()メソッドを実行できなくなります。とありますので、stop() に何も実装していないのであれば、System.exit() でも良さそうではあります...
ではありますが、副作用も読み切れないので、Platform.exit() を使うという前提で話を進めます(笑
TrayIcon を使うのであれば、JavaFX のメインウィンドウを閉じる場面が多々あると思われます。
それでアプリケーション自体が終了しないための指定として Platform.setImplicitExit(false) を呼び出して、 JavaFX アプリの終了は内部で Platform.exit() を呼び出した場合だけにします。
ただ、Platform.exit() 呼び出し後に TrayIcon をそのままにしておくと、awt のスレッドが残ってしまい、アプリケーションが終了しません。
そこで、Platform.exit() と一緒に必ず SystemTray.getSystemTray().remove() を呼び出して TrayIcon の後始末=awt スレッドの終了もされるようにします。
私自身は、Application#stop() に実装をして、 Platform.exit() の呼び出し時に必ず呼び出されるようにしてみました。
これでうまく行く感じです。
ざっと実装の概要だけ書くとこんな感じです。
public class Main extends Application {
private TrayIcon icon;
@Override
public void start(Stage primaryStage) {
// start で TrayIcon の登録処理を実装
Platform.setImplicitExit(false);
SystemTray tray = SystemTray.getSystemTray();
icon = new TrayIcon(ImageIO.read(getClass().getResourceAsStream("reload_12x14.png")));
// 本当はここで Listener 登録をしているが、ここでは省略
tray.add(icon);
}
@Override
public void stop() throws Exception {
// stop で TrayIcon の登録処理を実装
SystemTray.getSystemTray().remove(icon);
super.stop();
}
}
「javafx platform.exit system.exit」といった Google 検索をした結果を見ていると、
Platform.exit();
System.exit();
なんて、2つ組み合わせての呼び出しも見受けられます。
どうしても強制的&確実にアプリケーション全体を終わらせたい場合には、こういう手もアリなのかもしれません…
0 件のコメント:
コメントを投稿