Java+JavaFX+MVVMでストップウォッチアプリを作成してみた所感

# 概要

- [JXUGC #9 Xamarin.Forms Mvvm 実装方法 Teachathon を開催しました - Xamarin 日本語情報](http://ytabuchi.hatenablog.com/entry/2015/12/20/012007)
- [RxJava + MVVM パターンで作るストップウォッチアプリ](https://qiita.com/amay077/items/8464a22e3063642112ed)

　この2記事に触発され、私もJavaFXの練習がてら、ストップウォッチアプリを作成してみることにしました。もっとも、2つ目の記事は1つ目の記事に触発されて書かれたものですので、私の記事は**2番煎じ**ということになりますが……。

# 実装の方針

- Java＋**JavaFX**を利用してUI設計およびロジックを実装する
- JavaFXはデフォルトではMVCパターンを意識したファイル名になっているが、あえて**MVVMパターンによる開発**を目指す
- 普段はC#＋WPF＋MVVM＋ReactivePropertyで開発しているので、**言語仕様の違い**に気をつけながら開発する

# 開発ステップ

## ファイル名の変更

　Javaでは「**1ファイル1クラス**」といった原則がありますので、`*.java`のファイル名を決めることはクラス名を決めることに直結します。前述したようにMVVMパターンで実装するので、

- `Sample.fxml`から`MainView.fxml`
- `Controller.java`から`MainViewModel.java`
- `MainModel.java`も新規作成

するようにしました。

<img width="187" alt="cap1.png" src="https://qiita-image-store.s3.amazonaws.com/0/29802/29003255-ec51-672f-3c28-f7393d2293dd.png">

## UI設計

　C#＋WPFではVisual Studio上で[XAMLデザイナー](https://docs.microsoft.com/ja-jp/visualstudio/designers/creating-a-ui-by-using-xaml-designer-in-visual-studio)が使えますので、そこで設計すればよいだけの話でした。それだけに、[**Xamarin.Formsでプレビューがろくすっぽ効かなかった**](https://qiita.com/YSRKEN/items/0036c6506155b9bf8051)時はなかなかに苦痛でしたが……。  
　一方、Java＋JavaFXの場合、[JavaFX Scene Builder](http://pineplanter.moo.jp/non-it-salaryman/2017/04/29/javafx-scene-builder/)でUI設計を行います(EclipseもIntelliJもこのツールに対応)。ここで編集して保存すると、IDEが当該FXMLファイルを再読み込みして反映されるわけですね(逆もまた然り)。  
　XAMLもFXMLも、同じくXMLをベースに生み出された言語です。ただ、それぞれのコンテナ名やコントロール名は大きく異なります。一例を示しますと、

|FXML|XAML|意味|
|----|----|----|
|AnchorPane|対応なし|コンテナいっぱいまで中のコントロールを広げる|
|BorderPane|(DockPanelで代用可)|上・左・中央・右・下のどれかにコントロールを配置|
|FlowPane|WrapPanel|コントロール配置を折り返す機能が付いたコンテナ|
|GridPane|Grid|格子で区切った場所にコントロールを配置|
|HBox,VBox|StackPanel|縦や横にコントロールを並べて配置|
|TabPane|TabControl|タブ表示の中にコントロールを配置|
|ToolBar|ToolBarPanel|ツールバーを表示する|
|DatePicker|Calendar|カレンダーから日付を表示・選択する|
|Hyperlink|Link|リンク付きテキストを表示する|
|ImageView|Image|画像を表示する|
|MenuBar|Menu|メニューを配置|
|Menu|MenuItem|メニューの各項目(クリックすると子メニューを展開)|
|MenuItem|MenuItem|メニューの各項目(クリックすると別のアクションを起こす)|
|TextArea|TextBox|複数行テキスト入力(WPFではプロパティ設定で対応)|
|TextField|TextBox|1行テキスト入力|
|WebView|WebBrowser|Webブラウザだが、FXMLだとWebKitなのでより上等|

といった具合です。また、コントロールやコンテナのサイズ指定の意味合いがだいぶ異なっており、XAML脳だと**なかなかサイズが思い通りに決まらない**といったトラブルを招きます。詳しくは、次のWebページを参考にしてください。

- [2 ノードのサイズ指定と位置合せに関するヒント(リリース8)](https://docs.oracle.com/javase/jp/8/javafx/layout-tutorial/size_align.htm)
- [JavaFX 2.0でアプリケーション作成（その2） - torutkの日記](http://d.hatena.ne.jp/torutk/20120313/p1)
- [JavaFX 2.0でアプリケーション作成（その3） - torutkの日記](http://d.hatena.ne.jp/torutk/20120314/p1)
- [JavaFX 2.0でアプリケーション作成（その4） - torutkの日記](http://d.hatena.ne.jp/torutk/20120315/p1)

　ちなみに、Scene Builderには、[**コントロールをポン付けするとなんでもかんでもGridとMarginで配置してしまう**](https://qiita.com/YSRKEN/items/686068a359866f21f956)クソ仕様なんてありませんので、その辺はご安心くださいw

## Modelの設計、およびViewModelによるViewとの接続

　前述したように、FXMLはMVVMを意識した設計にはなっていません。そのせいか、デフォルトでは**コントロールに名前を付けてコードビハインドで直接アクセス**したり、**Controllerのメソッドでボタンを押した際のロジックを直接記述**するなどといった、[WinForms](https://ja.wikipedia.org/wiki/Windows_Forms)を彷彿とさせるコーディングになってしまいます。  
　ただ、都合がいいことに、JavaFXには標準で「`StringProperty`などの通知機構付きの型」と「`ChangeListener`などの通知機能」と「`bindBidirectional`などのData Binding機能」が搭載されています。これらを生かすと、

- Modelに`StringProperty`などのプロパティを配置
- ViewModelでコントロールのインスタンス(つまりView側)を定義し、`bindBidirectional`や`setOnAction`などでModelと紐付け(Data Binding)
- プロパティ同士は`ChangeListener`などを使えば変更を通知して連動できる

といった素敵環境が完成します。より楽したいなら[RxJava](https://qiita.com/toastkidjp/items/9d8487d43ed8211e8cdb)を使うべきなのだろうとは思いましたが、Data Bindingだけなら**別に外部ライブラリなしで全然書けるな**と思いました。  
　ちなみにJavaFXにおけるプロパティは、C#におけるプロパティと**意味合いが全然違う**のでご注意ください。また、Javaの本元(Oracle)は[「高レベルバインディングAPI」と「低レベルバインディングAPI」を使おう](https://docs.oracle.com/javase/jp/8/javafx/properties-binding-tutorial/binding.htm)と勧めてくるのですが、下のコード例を見れば分かるように、**ChangeListener一本で書いた方が楽**だと思います。

```sample.java
// 本家が勧めてくる「高レベル・バインディングAPI」とやら
IntegerProperty num1 = new SimpleIntegerProperty(1);
IntegerProperty num2 = new SimpleIntegerProperty(2);
IntegerProperty num3 = new SimpleIntegerProperty(3);
IntegerProperty num4 = new SimpleIntegerProperty(4);
NumberBinding total =
    Bindings.add(num1.multiply(num2),num3.multiply(num4));
System.out.println(total.getValue());

// こう書いた方が可読性が高いのでは？？
IntegerProperty num1 = new SimpleIntegerProperty(1);
IntegerProperty num2 = new SimpleIntegerProperty(2);
IntegerProperty num3 = new SimpleIntegerProperty(3);
IntegerProperty num4 = new SimpleIntegerProperty(4);
    int total;
    Runnable runner = () -> {
        total = num1.get() * num2.get() + num3.get() * num4.get();
    };
num1.addListener((ob, oldVal, newVal) -> setTotal());
num2.addListener((ob, oldVal, newVal) -> setTotal());
num3.addListener((ob, oldVal, newVal) -> setTotal());
num4.addListener((ob, oldVal, newVal) -> setTotal());
    System.out.println(total);
```

## ロジックの実装

　ここまで来ると、JavaFXみが薄れてきますね(Modelだけ考えればいいのがMVVMの強み)。[先人](https://qiita.com/amay077/items/8464a22e3063642112ed)の反省点に倣い、次の方針でロジックを実装しました。

- タイマーが起動していない状態でスタート/ストップボタンを押すと、押した瞬間の日時(A)を記録する。また、タイマーが起動し、ラップボタンが押せるようになる。この際、ラップ開始の日時Bの初期値をAと定義する(もしくは、Aを取得した直後にBも取得する)
- ラップボタンを押すと、日時Bと押した瞬間の日時(C)からラップタイムを計算し、ラップタイム一覧(D)にラップタイムを追加する。また、リストビューにそのタイムを追記する
- タイマーが起動した状態でスタート/ストップボタンを押すと、ラップタイム追記の他、一覧Dから最速ラップタイムと最遅ラップタイムを算出してダイアログ表示する
- タイマーはUIスレッドとは違うスレッドで動作しており、定期的(10ms間隔など)に計測タイムの更新を行う。ただ、UIスレッド以外が、UIやそれに関連したプロパティ(Data Bindingで紐付けされたものも含む)を弄ると実行時エラーになるので、`Platform.runLater`メソッドで**更新処理をUIスレッドに移譲する**のが大事

　参考：[JavaFXでスレッドを使って描画するときの注意 - Qiita](https://qiita.com/takaki@github/items/7dba9afaf11002c78719)

# まとめ

　Java＋JavaFXで、MVVMおよびData Bindingを生かしたアプリ開発をすることができました。ReactivePropertyのようなお助け補助ライブラリがない状況だったので少し面倒でしたが、予想より楽に書き上げることができて正直驚いています。JavaFXの、**Scene Builderとプロパティ機能が持つポテンシャル**は侮れませんね……！  
　また、Javaで開発する際に忘れてはいけないのが**ラムダ式とStream API**。前者はC#と同じような書き心地で、後者も**LINQほど便利ではない**にせよ役立ちました。

　ところで、JavaFXのプロパティ機能は、ReactivePropertyと違って`StringProperty`だの`IntegerProperty`だのと変数型が決め打ちなものばかりで、`Property<T>`型が[なぜかinterface](https://docs.oracle.com/javase/jp/8/javafx/api/javafx/beans/property/Property.html)なのは何故なのでしょう……？

# 参考資料

- [JavaFX 勉強メモ - Qiita](https://qiita.com/opengl-8080/items/51bef25aa05ecd929a3d)