2016年7月8日金曜日

JavaFX で Windows のホットキー登録を行う / JIntellitype の利用

JavaFX で Windows のホットキー登録(Win32API の RegisterHotKey で登録できるもの)を行おうとした場合の方法についてです。

システムトレイの時とは違い、awt 等の既存の UI ライブラリにも無いため、
JNI に頼らなければいけないところですが、
既に Java にラッピングしたライブラリが開発・配布されていて、それが JIntellitype です。
そのため JIntellitype に頼るのが早道かと思われます。

実装コードは次のような感じです。

登録処理(ex. Windowsキー + C で画面を表示させたい場合)
    // register global Windows Hot Key
    JIntellitype.getInstance().registerHotKey(1, JIntellitype.MOD_WIN, (int)'C');
    JIntellitype.getInstance().addHotKeyListener(new HotkeyListener(){
        @Override
        public void onHotKey(int aIdentifier) {
            if(aIdentifier == 1){
                Platform.runLater(() -> {
                    primaryStage.show();
                    primaryStage.toFront();
                });
            }
        }
    });

解除処理
    JIntellitype.getInstance().unregisterHotKey(1);

終了処理
    JIntellitype.getInstance().cleanUp();
キーの登録は registerHotKey() → addHotKeyListener() で Listener 登録となります。
複数のキー登録にも対応していて、register 時の第一引数が識別子となっていて、Listener 側へも引数として渡ります。
また、JavaFX の GUI への処理は、これまたシステムトレイの時と同じく、直接呼び出すのではなく、 Platform.runLater() を使って JAT に対する処理依頼の形を取ります(ここも JAT とスレッドコンテキストが違うため)。

解除処理は登録時の識別子を渡して unregisterHotKey() です。

アプリケーション終了前には cleanUp() を呼び出して後始末処理をするようにして下さい。
システムトレイの時と同じく、Application#stop() に実装をして、 Platform.exit() の呼び出し時に必ず呼び出されるようにすると良いのではないかと思います。

実装は簡単なのですが、実際に Eclipse でライブラリを使う際に注意点があります。

JIntellitype の jar に対して Libraries として Build Path を通すのは当然ですが、
jar → JIntellitype.dll へのパス登録も必要になります。
次のどちらかの方法を使って DLL への関連付けを行います。
  • Project のプロパティ → Java Build Path → Libraries タブ → JIntellitype の jar を開いて "Native library location" を選択 → Edit
  • jar のプロパティ → Native Library 

※ 参考にしたページ

2016年7月7日木曜日

JavaFX で Windows のシステムトレイを使う

JavaFX で Windows のシステムトレイを使おうとした場合、
JavaFX 内での解決方法はなく、
Java 6 で導入された java.awt.TrayIcon を使わないといけないようです。

TrayIcon 自体の使い方は


といったページなどでまとめられているので、そちらを参照して下さい。

ここでは TrayIcon を JavaFX で使う場合の注意点について触れます。

  1. TrayIcon に登録した Listner から、JavaFX の UI に対して操作を行う場合には、直接操作を行うのではなく、 Platform.runLater() を使って JAT コンテキスト内で処理されるようにする
  2. アプリケーションの終了は System.exit() ではなく、JavaFX で Platform.setImplicitExit(false) した上で Platform.exit() を使う。
    その上で exit する際には必ず明示的に SystemTray.getSystemTray().remove() を呼んで SystemTrayIcon を消すようにする
という2点です。

ズバリ、この点については次のページを参考にしています。

が、一応、私なりの補足を書いておくと…

注意点の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 の ComboBox でキーイベント等をひろう

JavaFX の ComboBox でキーイベントをひろおうとすると、一段階工夫が必要になります。
ComboBox が内部に持つ TextField に対しての Listner 登録が必要になります。
例えば、次のような感じになります。
@FXML
private ComboBox<String> cmbbxSearchText;
(略)
    // ComboBox 内の TextField の文字列の変化
    cmbbxSearchText.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
        System.out.println("cmbbx text changed from " + oldValue + " to " + newValue);
    });
    // ComboBox 内の TextField での KeyPressed
    cmbbxSearchText.getEditor().setOnKeyPressed((event) -> {
        System.out.println("onKeyPressed:" + event);
    });
    // ComboBox 内の TextField での KeyTyped
    cmbbxSearchText.getEditor().setOnKeyTyped((event) -> {
        System.out.println("onKeyTyped:" + event);
    });

参考ページ
java - JavaFX - ComboBox listener for its texfield - Stack Overflow

ちなみに、上記サンプルの1つ目の ComboBox(内の TextField)の値の変化だけならば、
このページなどを見ると ComboBox の valueProperty() に対して Listener 登録すれば良いようです。

2016年7月6日水曜日

Files.walk() や Files.find() の困ったところ。 Files.walkFileTree() のススメ

Java の java.nio.file には java.nio.file.Files というとっても便利そうな static メソッドが集まったクラスがあります。
その中でも walk() や find() は、とあるディレクトリ配下を一気に収集して Stream として返してくれる、というとっても便利そうなメソッドなのですが、
なんとも悲しい落とし穴があります。

それは途中で Exception が発生した場合にはそこで終了してしまい、エラーハンドリングが出来ないというものです。
例えば、
try(Stream<Path> stp = Files.walk(Paths.get(”c:\\Windows”))){
    stp.forEach(System.out::println); }
のように c:\\Windows 配下のような権限不可で読めないファイル・ディレクトリの宝庫のところを対象として walk() すると、どこかで java.nio.file.AccessDeniedException とかが起きて walk 処理が止まってしまいます。
そういうところは飛ばして続けるという選択肢がありません。
これは Files.Find() でも同じです。

この点については、このページで議論されているように、FileVisitOption.IGNORE_ON_IOEXCEPTION なんてのが指定できると良いのにと思います。

ともかく、現状、こういう権限不可で読めない状況に対処して処理を続けたい場合には、
Files.walkFileTree() を使い visitFileFailed() でエラーハンドリングをするという対処方法になるようです。
(walk(), find() 相当の収集処理は visitFile() で自力で実装する)

で実装例がありますが、ポイントのところだけ抜き出すと
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        System.out.printf("Visiting file %s\n", file);
        /* ここで正常ケースの file について、リスト化していくなり、何か処理するなりを実装 */
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException {
        System.err.printf("Visiting failed for %s\n", file);
        /* アクセス不可なファイルは無視する */
        return FileVisitResult.SKIP_SUBTREE;
    }
});
といった感じになります。

2016年7月4日月曜日

JavaFX 開発で Scene Builder を使いはじめて気付いた Tips

JavaFX で UI を作るのに fxml を直接書く、というのは初学者にはなんとも敷居が高いと思う。
(もちろん、勉強&仕組みを知るために書いてみること自体は否定しない)
fxml ではなく、普通に Java のコードで生成していくのもあるし、そういうサンプルもある。
ただ、現時点では Scene Builder を使うのが何かと敷居が低いし、とっつきやすいと思う。
(先の記事でとりあげた入門ページでも Scene Builder を使っている)

で、Scene Builder を使い始めて感じた Tips のメモ

そもそも Scene Builder の入手方法

まず Scene Builder の入手方法

Oracle のページ(ここやここ)に行くと、Oracle ではもう古い版しか配布していないことが分かる。
(Java SE 8u40以降は Scene Builder の最新版のバイナリ配布を止めている)

で、現時点では OpenJFX として開発・公開が続いているが OpenJFX Wiki に行くと、ソースコードからのビルドを解説されて初学者としては挫折しそうになる。

ただ Gluon という会社が http://gluonhq.com/labs/scene-builder/#download でバイナリ配布をしてくれているので、そこから入手するのが早いです。

そこらへんの経緯や議論は
辺りを見てみて下さい。

NetBeans / Eclipse と Scene Builder の組み合わせ方法

NetBeans, Eclipse ともに
に手順や解説があります(IntelliJ IDEA についても書いてあるが使ってないので省略)。

Eclipse の場合には e(fx)clipse というプラグインを Help → Eclipse Marketplace 経由からインストールすれば良いし(ただ公式のインストール方法の方が最新版が手に入る様子)、
NetBeans なら設定に最初から Scene Builder との連携が想定されています。
(NetBeans 8.0 + 上の Gulon 配布の Scene Builder バイナリの組み合わせがそのままだと動かないという記事がありましたが、最新版の 8.1 では NetBeans で Scene Builder へのパスを設定するだけで問題なく使えました)

Controller は手書きしないとダメ?

そんなことは無いです。
新規時の一方方向ですが、Eclipse + e(fx)clipse, NetBeans 共に、fxml ファイルから Controller を次のように生成できます。
  • Eclipse + e(fx)clipse:
    fxml のエディタ画面内でコンテキストメニューを開いて、 Source → Generate Controller
    (公式の解説はこちらのページ)
  • NetBeans:
    fxml のファイルを選択してコンテキストメニューを開いて「コントローラの作成」
ただ、初回作成時の一方方向なので、途中で fxml に変更が入った場合には Controller 側は手作業で変更する必要があります。
そういう意味でも、手作業で Controller を作っている入門ページや「Connecting SceneBuilder edited FXML to Java code」といったページで Controller と fxml の連携の部分を良く理解しておいた方が良いです。

Scene Builder のまとまった解説(書)はない?

にある Documentation 群や

JavaFX Scene Builderメモ(Hishidama's JavaFX Scene Builder Memo) http://www.ne.jp/asahi/hishidama/home/tech/java/fx/sb/index.html

辺りでしょうか…

他に良いものがあれば私も知りたいです。

JavaFX をはじめてみた

Java8 の勉強を兼ねて JavaFX をかじったので、
その中での最初の一歩 or 入門として役立ったページのメモ

JavaFXでHelloWorld - Qiita

JavaFX8で簡単なアプリケーションを簡単に作る方法 - seraphyの日記

JavaFX: Getting Started with JavaFX Release 8 - Contents

この辺りがまとまっていてなかなか助かりました。
(多少、現時点では古い部分もあるけれども、一通りの概要を知るには今でも十分)

他にも
  • eclipse + e(fx)clipse や NetBeans で空のプロジェクトを作ってみる
  • NetBeans で新規プロジェクト作成時にある JavaFX のサンプルプロジェクトや Oracle 配布のデモ(リンク先の Demos and Samples に含まれるもの。 NetBeans でプロジェクトとして取り込み可能)
    そういう意味で eclipse オンリーな人も、NetBeans を入れるとサンプルとしては参考になる点が多いです
なんてのも非常に役立ちました。

ただ、ちょっと古い入門/解説ページを呼んでいると「ダイアログ(ボックス)が UI ライブラリに無い」的な話題があったりして(Java 8u40 から追加されている)、そういうところなるべく最新の情報も気にした方が良いです。

Titanium / Appcelerator で開発したアプリの X509TrustManager 問題の対応

今更ながらアプリをアップデートしようと思い
Google からメールで指摘された X509TrustManager の問題について対処方法を調べたのでメモ

Appcelerator の公式 blog による対処方法がこちら

Update on recent Google Security Alerts
http://www.appcelerator.com/blog/2016/03/update-on-recent-google-security-alerts/

要は

  • Titanium 5.0.2.GA 以降では問題ない
  • 4_1_X, 4_0_X and 3_5_X のユーザは branch で対応したから、その SDK をインストールして
というコメント。
ちなみに2点目の対処方法はそれぞれ
Branch CI Build
4_1_X [appc] ti sdk install -b 4_1_X 4.1.1.v20160311104258
4_0_X [appc] ti sdk install -b 4_0_X 4.0.1.v20160311104206
3_5_X [appc] ti sdk install -b 3_5_X 3.5.2.v20160311103211
とのこと。

私の場合、3.5.X のまま放っておいたのでとりあえず 3_5_X での対応をインストールして対処してみようと思う。
(この build の SDK を使って開発したものを実際に Google Play で公開依頼していないので、本当にそれで問題ないかは未検証)

ちなみに、この問題についての経緯は次のページを参照。

android - You are using an unsafe implementation of X509TrustManager - Stack Overflow
http://stackoverflow.com/questions/35490107/you-are-using-an-unsafe-implementation-of-x509trustmanager