ControlsFX / Validationを使用する

##環境

- JDK 1.8.0_121
- ControlsFX 8.40.12

##ControlsFXにおけるバリデーション

JavaFXアプリケーションに入力チェック機能を実装するためにControlsFXのバリデーション機能を使ってみます。以下のような見た目になります。

![0.png](https://qiita-image-store.s3.amazonaws.com/0/168976/7e5730aa-f2f8-4b7c-7e74-75aa9db47b35.png)

エラーアイコンにカーソルを合わせるとエラーメッセージがツールチップ表示されます。（ちょっと気づきにくい感じはします）

![0e.png](https://qiita-image-store.s3.amazonaws.com/0/168976/6350e3c8-98ec-efa6-419f-28668979b864.png)


## 準備
ControlsFXを使用するのでGradleやMavenのdependencyに[ControlsFX](https://mvnrepository.com/artifact/org.controlsfx/controlsfx)を追加します。直接インポートする場合は[こちら](http://fxexperience.com/controlsfx/)からダウンロードしてください。


## Validator
`Validator`は入力内容をチェックするための基本的な仕組みを提供しており、自分で条件を定義することもできます。以下の４つのメソッドを使ってバリデーターを作成できます。

|Validator\<T>::       |動作                          |
|:-------------------|:-------------------------|
|createEmptyValicator|空入力をERRORにする|
|createEqualsValidator|リストにない入力をERRORにする|
|createRegexValidator|正規表現に合わない入力をERRORにする|
|createPredicateValidator|指定したPredicate\<S>に合わない入力をERRORにする|



`Predicate<S>`は**コントロールのvalue**を受け取って`boolean`を返す関数型インタフェースです。好きな条件をラムダ式を使って実装できるのですっきり書けますし応用が利きます。また、引数にはエラーレベル（`Severity.ERROR`か`Severity.WARNING`）やエラーメッセージを指定します。
[Validator (ControlsFX): API Document](https://controlsfx.bitbucket.io/org/controlsfx/validation/Validator.html)

複数のバリデーターを一つのコントロールにセットするには`Validator<T>::combine`を使ってバリデーターを合成する必要がありますがここでは触れません。（「～の形式で入力してください」を空チェックの兼用とするなど、一つで済ませる方法もあります）

バリデーションのコード例は次で触れます。

## ValidationSupport
`Valicator`は`ValidationSupport`クラスに登録して使用します。あとは入力内容が変更される度に自動的にバリデーションが行われるので、以下の例だけでバリデーションが全て機能します。

バリデーターの登録には`ValidationSupport::registerValidator`を使用します。登録時に入力必須アイコンの表示可否を指定できます。


```java:initialize内などで
ValidationSupport support = new ValidationSupport();
support.registerValidator(textField1, false, Validator.createEmptyValidator(
                "Text is required",
                Severity.WARNING));
support.registerValidator(textField2, Validator.createEqualsValidator(
                "A or B or C is required",
                Arrays.asList("A", "B", "C")));
support.registerValidator(textField3, Validator.createRegexValidator(
                "Number is required",
                Pattern.compile("^[0-9]+$"), Severity.ERROR));
support.registerValidator(checkBox, Validator.createEqualsValidator(
                "Check is required",
                Collections.singletonList(true)));
support.registerValidator(comboBox, Validator.createEqualsValidator(
                "First item is required",
                Collections.singletonList(comboBox.getItems().get(0))));
support.registerValidator(colorPicker, Validator.createEqualsValidator(
                "WHITE or BLACK is required",
                Arrays.asList(Color.WHITE, Color.BLACK)));
support.registerValidator(slider, Validator.createPredicateValidator(
                (Double value) ->  value > 0.0,
                "Positive number is required"));

// スタティックインポートを使用して
// import static org.controlsfx.validation.Validator.*;
// とすると毎回 Validator. を書かなくて済みます
```

![5.png](https://qiita-image-store.s3.amazonaws.com/0/168976/a8c0857f-7519-1e01-4a16-de24672fe341.png)



`ValidationSupport`クラスはバリデーション結果全体に関するプロパティを持っているので、結果全体に追従する仕組みを実装するのに便利です。

```java
// 全ての結果がvalidな場合だけ有効なボタン
Button buttonValidated= new Button();
buttonValidated.disableProperty().bind(support.invalidProperty());

// エラーメッセージを全て表示するListView
ListView<ValidationMessage> listView = new ListView<>();
support.validationResultProperty().addListener((o, oldValue, newValue) ->
                listView.getItems().setAll(newValue.getMessages()));
```
[ValidationSupport (ControlsFX): API Document](https://controlsfx.bitbucket.io/org/controlsfx/validation/ValidationSupport.html)


## CSSによってエラー表示を変更したい場合
上記の例はデフォルトアイコンによるエラー表示のみ行っていますがこれを変更できます。`StyleClassValidateionDecoration`を使用すると警告時には`warning`、エラー時には`error`というスタイルクラスをコントロールに追加してくれます。実装者は`Scene`に適用するCSSファイルにエラー時のスタイルを記述する必要があります。アイコン表示との併用もできます。


```java
// CSSのみの場合
support.setValidationDecorator(new StyleClassValidationDecoration());
// アイコンとCSSを併用する場合
support.setValidationDecorator(new CompoundValidationDecoration(
    new GraphicValidationDecoration(), 
    new StyleClassValidationDecoration()));
```

```css:Main.css
.warning{
    -fx-border-color: orange;
    -fx-border-width: 2;
}
.error{
    -fx-border-color: red;
    -fx-border-width: 2;
}
```

```java:FXMLを読み込むところ等で
scene.getStylesheets().addAll(getClass().getResource("Main.css").toExternalForm());
```

以下はアイコンとCSSを併用する場合の画面表示です。

![5e.png](https://qiita-image-store.s3.amazonaws.com/0/168976/23e54a97-77cf-4deb-23b0-1e270d6f7a90.png)

CSSのみの場合ツールチップを表示する方法がないのでエラー内容を伝える工夫が必要です。


## アイコンやツールチップをデザイン変更したい場合
アイコンやツールチップのデザイン変更は、`GraphicValidationDecoration`を継承して対応するメソッドをオーバーライドする方法があります。

|GraphicValidationDecoration::|説明            |
|:----------------------------|:------------|
|createErrorNode|エラーアイコン|
|createWarningNode|警告アイコン|
|createRequireDecorations|入力必須アイコン|
|createTooltip|ツールチップ|

オリジナルのソースを参考にして一部書き換える感じでやると簡単かと思います。
[GraphicValidationDecoration (ControlsFX): API Document](https://controlsfx.bitbucket.io/org/controlsfx/validation/decoration/GraphicValidationDecoration.html)
[bitbucket: GraphicValidationDecoration](https://bitbucket.org/controlsfx/controlsfx/src/e47b7589efc24aa9be2321a93ff11ccfbc3aaf57/controlsfx/src/main/java/org/controlsfx/validation/decoration/GraphicValidationDecoration.java?at=default&fileviewer=file-view-default)

```java:継承したクラスをセットするコード
support.setValidationDecorator(new YourGraphicValidationDecoration());
```


## カスタムコントロールをバリデーションするときの注意
ControlsFXのバリデーターはあなたが作成したカスタムコントロールのどのプロパティをチェックするべきかを知りません。JavaFX標準ではないコントロールをバリデーションする場合は次のようなコードで教えてあげる必要があります。[公式の説明はこちら](https://controlsfx.bitbucket.io/org/controlsfx/validation/ValidationSupport.html)

```java
// YourControlコントロールについて、someプロパティの値をバリデーションして欲しいとき
ValueExtractor.addObservableValueExtractor( 
        c -> c instanceof YourControl, 
        c -> ((YourControl)c).someProperty());
```

## 参考
[ControlsFX: sample](http://fxexperience.com/controlsfx/)
[ソース: GIT](https://github.com/monolith52/Snippets/tree/master/src/main/java/validation)
[FXML,CSS: GIT](https://github.com/monolith52/Snippets/tree/master/src/main/resources/validation)