JavaFXのプロパティバインド

# JavaFXの概要
Java7から採用されたデスクトップUIです。
FXMLによるGUI構成の記述、CSSによるデザインの分離、Prismとよばれるハードウェアアクセラレーション対応の描画エンジン、WebKitベースのブラウザを搭載といったのが特徴ですが、Java界隈以外の人にはまったく知られていない感じがしますね。

![JavaFXアーキテクチャ](https://docs.oracle.com/javase/jp/8/javafx/get-started-tutorial/img/jfxar_dt_001_arch-diag.png)

グラフを含めてGUI部品は揃っていますし、アニメーションのためのタイムラインもあります。
しかし、ダイアログがなかったり、画面遷移のためのAPIもなかったり、入力値に対するバリデーション機能といったものがありませんので、足らない部分は自分で作り込む必要は出てきます。これらの話しはまた書きたくなったら書くとして、本記事ではJavaFXの基本とプロパティバインドについて書きます。

# JavaFXの作業手順
画面の作り方は以下のどちらかを使い分けます。

* FXMLで画面構成
* APIを使って動的に生成
 
![scene.png](https://qiita-image-store.s3.amazonaws.com/0/114529/ea611a4d-aac0-85f1-7de5-6f7ad38cf000.png)

FXMLはエディターで記述ができますが、Scene Builderというツールを使うのが楽です。メンテナンスや多人数開発を考えると直接記述はお勧めしません。
動的に生成するにはAPIを使うことになりますが、コンポーネント単位でScene Builderで作りこんでAPIで埋め込むといった手順を踏むべきでしょう。

FXMLには関連づくクラスを記載します。するとFXML読み込み時に自動的に生成されます。作りは見ていませんがコンテナ管理されているかもです。

```xml:Scene.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane"
            prefHeight="200"
            prefWidth="320"
            xmlns:fx="http://javafx.com/fxml/1"
            xmlns="http://javafx.com/javafx/8"
            fx:controller="com.exsample.javafxsample.FXMLController">
    <children>
        <Label fx:id="label" layoutX="77.0" layoutY="51.0" minHeight="16" minWidth="69" text="Label" />
        <Button layoutX="79.0" layoutY="108.0" onAction="#onAction" text="Button" />
        <TextField fx:id="nameField" layoutX="79.0" layoutY="75.0" />
    </children>
</AnchorPane>
```

Javaにはアノテーションという便利な仕組みがあるので、GUI部品との関連付けは一発ですね。

```java:FXMLController.java
package com.exsample.javafxsample;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class FXMLController implements Initializable {
    
    @FXML
    private Label label;
    @FXML
    private TextField nameField;
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
    }    

    @FXML
    private void onAction(ActionEvent event) {
        label.setText(nameField.getText());
    }
    
}
```

```java:MainApp.java
package com.exsample.javafxsample;

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MainApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
        Scene scene = new Scene(root);
        scene.getStylesheets().add("/styles/Styles.css");
        stage.setTitle("JavaFX and Maven");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}
```

# 是非覚えてほしいプロパティバインド

アノテーションでGUI部品に対してプロパティの読み込みや設定ができるのですが、どんなアプリケーションでもモデルとビューは切り分けたいものです。
で、モデルを分離したときにGUI部品に対していちいちSet/Getするのは格好が悪いのでやめましょう。プロパティバインドという仕組みがあります。モデルの値が変わればビューも変わりますし、UIの入力があればモデルの値も変わります。

ちなみにこのモデルは遅延バインドするためのコードになっています。
モデルがJAXBでXMLと相互変換したり、O/Rマッピングする場合は遅延バインドしましょう。

```Modle.java
package com.exsample.javafxsample;

import java.util.Objects;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Model {

    private StringProperty valueProperty = null;
    private String value;

    public StringProperty valueProperty() {
        if (Objects.isNull(valueProperty)) {
            valueProperty = new SimpleStringProperty(value);
        }
        return valueProperty;
    }

    public String getValue() {
        if (Objects.nonNull(valueProperty)) {
            return valueProperty.get();
        }
        return value;
    }

    public void setValue(String value) {
        if (Objects.nonNull(valueProperty)) {
            valueProperty.set(value);
        } else {
            this.value = value;
        }
    }

}
```

コントローラクラスを書き換えます。
ボタンの役割は無視するとして、テキスト入力するとラベルを更新します。

```java:FXMLController.java
public class FXMLController implements Initializable {
    
    private final Model model = new Model();
    @FXML
    private Label label;
    @FXML
    private TextField nameField;
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        label.textProperty().bind(model.valueProperty());
        model.valueProperty().bind(nameField.textProperty());
    }    

    @FXML
    private void onAction(ActionEvent event) {
    }
    
}
```

実行例です。

![application.png](https://qiita-image-store.s3.amazonaws.com/0/114529/448d2d8b-e880-ba37-fe40-69d58aa8257c.png)