2016年7月22日金曜日

Java/JavaFX 版 Mutter Launcher を Github で公開中

ここ最近の Java/JavaFX の投稿は、
以前に Windows 向けに C/C++ と Win32API で作ったアプリケーション Mutter Launcher を Java/JavaFX に移植するという作業の中から出てきたノウハウでした。

ちなみにもともとの Windows版の Mutter Launcher はこんなもの。

その Java/JavaFX 版の Mutter Launcher は Github で公開しています。
完全移植ではなく、まだまだ作成途中ではありますが、
大分、実装がまとまってきたので、
この blog でも記事として投稿してみました。

ここ最近書いてきたような Java/JavaFX のサンプルとして、
あるいは単純に Java アプリケーションとして
もし興味ある方は覗いてみて下さい。

2016年7月21日木曜日

JavaFX の ListView でアイコンを表示/Cell のカスタマイズ


JavaFX の ListView で String 以外を構成要素にするの拡張的な内容になりますが、
ListView の表示が toString() では収まらない、文字列以外も表示したい、という際には、ListView のリストを構成する Cell をカスタマイズすることになります。
その Cell をカスタマイズした一例として…

やりたかったことは ListView にファイル名と対応するアイコンの表示。
ポイントは ListView の Cell のカスタマイズと Icon の描画方法。

といっても、ほとんど

JavaFX file listview with icon and file name - Stack Overflow
http://stackoverflow.com/questions/28034432/javafx-file-listview-with-icon-and-file-name

のマネです。

Item というクラスがファイル名などを持っていて、ListView<Item> として ListView の要素のクラスとなっているとします。
ListView#setCellFactory() で Cell をカスタマイズする ListCell を拡張したクラス(この場合 ItemFormatCellを返すようにします。
        listView.setCellFactory(new Callback<ListView<Item>, ListCell<Item>>() {
             @Override
             public ListCell<Item> call(ListView<Item> list) {
                 return new ItemFormatCell();
             }
         });

ItemFormatCell クラスで、実際に1つのセルの表示内容をカスタマイズすることになりますが、ここで updateItem() をオーバーライドして実装するのが定番のようです。
次の例では、

  • 空行には何も表示しない(setText(), setGraphic() で null 指定)
  • 対応する Item がある場合には、アイコン(setGraphic())+ファイル名(setText())を表示する

ということをやっています。
    final class ItemFormatCell extends ListCell<Item> {
        public ItemFormatCell() {    }
   
        @Override protected void updateItem(Item item, boolean empty) {
            final boolean bUseJLabel = false;
     
            super.updateItem(item, empty);
     
            if(empty || item == null){
                 setText(null);
                 setGraphic(null);
                 return;
            }
     
            // getItemName() はファイル名を返すメソッドとして実装
            setText(item.getItemName());
            setContentDisplay(ContentDisplay.LEFT); // Graphic は Text の左に表示
            // getIcon() は対応する Icon を返すメソッドとして実装
            // Windows では FileSystemView.getFileSystemView().getSystemIcon() を利用
            Icon icon = item.getIcon();
            BufferedImage bufferedImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
            icon.paintIcon(null, bufferedImage.getGraphics(), 0, 0);
            // 最終的に Icon を ImageView の形で Cell に表示
            setGraphic(new ImageView(SwingFXUtils.toFXImage(bufferedImage, null)));    
        }
    }

ListView の Cell のカスタマイズについては、Cell の javadoc も参照してみて下さい。


2016年7月20日水曜日

JavaFX の ListView で String 以外を構成要素にする

ListView では javadoc にもあるように、ListView<String> という形で String 型のものを要素として第一に想定していますが、
もちろん、それ以外の独自のクラスを指定することも可能です。
その際に一番簡単な方法は次の通り。

例として独自の Item クラスというのを ListView で扱いたい場合、

・ObservableList<Item> を ListView<Item> に setItems() で設定する

・リストに表示される文字列は Item クラスの toString に実装

実装イメージを書くとこんな感じになります。
// ListView 側
    @FXML
    private ListView<Item> itemListView;

(略)
        ObservableList<Item> items = FXCollections.observableArrayList();

        // items に項目を追加する処理
        itemListView.setItems(items);

// Item 側
public class Item{
    private String itemName;

    @Override
    public String toString() {
        return itemName;
    }

2016年7月16日土曜日

JavaFX の fxml 使用時の Application と Controller の相互参照方法

fxml を使って JavaFX のアプリを作っていると、
Application#start() で渡されるメインとなる Stage(primaryStage)と Controller とのやり取りが、どうしても欲しくなってきます。
その際に、正規のやりとりの口はないらしく、

などを参照すると、自力で相互参照を作らないといけないようです。
  • Application#start() → Controller の参照
    FXMLLoader#getController() で load した fxml に関連した Controller のインスタンスが取得できる
  • Controller → primaryStage の参照
    Controller 側で stage を設定できる public メソッドを用意しておいて、上で取得した Controller に対して primaryStage を設定する
という方法になります。
孫引きになりますが、サンプルソースはこんな感じになります。
// MyGui.fxml のロードと、関連した Controller インスタンスの取得
FXMLLoader loader = new FXMLLoader(getClass().getResource("MyGui.fxml"));
MyController controller = (MyController)loader.getController();

// primaryStage を controller 側へ設定
controller.setStageAndSetupListeners(primaryStage); 



2016年7月12日火曜日

JavaFX の ComboBox で Return/Enter キーの入力をひろう

JavaFX の ComboBox でキーイベント等をひろうの続きっぽい内容ですが、
ComboBox で Return/Enter キーの入力をひろうのに苦労したので、
そこのメモ。

検索すると

  1. OnAction のイベントハンドラーで処理できる
  2. ComboBox の getEditor() で取得できる内部の TextField のキーイベントの getCode() と KeyCode.ENTER を比較
なんて方法が出てくるがどちらもうまく行かなかった。

1. の方法は、それで処理されるときもあれば、処理されないときもあるという動作が不確定。

2. の方法は、確かに OnKeyTyped の際にはイベント通知が来るが(ちなみに OnKeyPressed はダメだった)、ログ出ししてみるとなぜか getCode() した結果が UNDEFINED 値(KeyCode.ENTER との比較が true にならない)。

ということで、最終的にイベントのログ出し結果等から私が書いたコードは次のようになった。
    comboBox.getEditor().setOnKeyTyped((event) -> {
        System.out.println("ComboBox onKeyTyped:" + event);
        if(event.getCode() == KeyCode.ENTER || event.getCharacter().equals("\n") || event.getCharacter().equals("\r")){
            executeSelectedItem();
        }
    });
今のところ、ログやデバックでの動作を見る限り getCharacter() の "\r" との比較で上手く動作しています。
それ以外の比較は、環境や動作が変わった場合の保険。
(event.getCode() 値が KeyCode.ENTER になるのが正しい動作だと思うので)

もっとエレガントで正しい書き方があれば知りたい…

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