JavaFXとバインディング¶
はじめに¶
JavaFXでは、コントロール等の持つ属性(ラベルのテキストなどの値や、幅・高さ、ディセーブル等)を「プロパティ」という概念を導入して他のオブジェクトの属性と「バインディング」し、片方の属性が変更されるとバインディングした他方の属性を連動して変更されるようにする仕組みが提供されています。
Swingまでの仕様・設計では、オブジェクトの属性の変更を連動させたいときはリスナーを設定してコールバックを受ける方法で実現していました。そのためにはリスナーオブジェクトを生成し(クラスにリスナーインタフェースをimplementsするか、リスナーインタフェースをimplemetsする内部クラスを作るか、リスナーインスタンスを匿名クラスでインスタンス化する)、リスナーメソッドで値を取得して連動させる箇所にセットするという手間をかけていました。
JavaFXでは、その仕組みが簡単にかけるようになりました、というところです。このプロパティという概念は馴染みがないと最初違和感、異質感が半端ないですが、馴染むとそれなりに受け入れて使えるようになります。
基本¶
バインディングは、あるオブジェクトのプロパティと、別なプロパティを結び付ける仕組みです。
プロパティの種類¶
JavaFXがAPIで提供するプロパティは次となります。
プロパティの型 | 読み込み専用プロパティの型 | プロパティの値の型 |
---|---|---|
BooleanProperty | ReadOnlyBooleanProperty | boolean |
IntegerProperty | ReadOnlyIntegerPropety | int |
FloatProperty | ReadOnlyFloatProperty | float |
DoubleProperty | ReadOnlyDoubleProperty | double |
StringProperty | ReadOnlyStringProperty | String |
ObjectProperty<T> | ReadOnlyObjectProperty<T> | T extends Object |
ListProperty<E> | ReadOnlyListProperty<E> | ObservableList<E> |
MapProperty<K,V> | ReadOnlyMapProperty<K,V> | ObservableMap<K,V> |
SetProperty<E> | ReadOnlySetProperty<E> | ObservableSet<E> |
プロパティの操作¶
プロパティに値をセットする¶
2つのメソッド、void set(T value)
とvoid setValue(T value)
があります。
プリミティブの値のプロパティでは、setメソッドの引数はプリミティブ型、setValueメソッドの引数はラッパー型となっています。
- IntegerProperty
void set(int value) void setValue(Number v)
プロパティの値を取得する¶
2つのメソッド、T get()
とT getValue()
があります。
プリミティブの値のプロパティでは、getメソッドの戻り値はプリミティブ型、getValueメソッドの戻り値はラッパー型となっています。
- IntegerProperty
int get() Integer getValue()
バインディング¶
バインディングのカスタマイズ¶
- IntegerPropertyなどのサブクラスを作成
- Bindingsクラスのstaticメソッドを使用
- fluent interface APIを使用(IntegerExpressionクラス等)
やりたいこと別¶
型の違うプロパティをバインディングしたい¶
数値から文字列へのバインディング¶
例えば、スライダー(Slider)の値をラベルに表示したいとします。スライダーの値はDoubleProperty型で、ラベル(Label)の値はStringProperty型です。これを直接バインドするとコンパイルエラーとなってしまいます。
Slider slider;
Label label;
:
label.textProperty().bind(slider.valueProperty()); // コンパイルエラー
数値型のプロパティのasStringメソッド¶
数値型のプロパティには、StringBindingを返却するasStringメソッドが用意されています。次にシグニチャを示します。
public StringBinding asString()
public StringBinding asString(String format)
public StringBinding asString(Locale locale, String format)
引数なしのasStringは、デフォルトの文字列(数値型プロパティの値をtoString()したもの)を生成します。
label.textProperty().bind(slider.valueProperty().asString());
書式を指定して文字列化する場合は、次のコードとなります。
label.textProperty().bind(slider.valueProperty().asString("%6.2f"));
Bindings.convertメソッドを使う¶
Bindingsクラスのメソッドconvertは、各種型のプロパティから文字列表現を生成するStringExpressionインスタンスを生成します。
シグニチャを次に示します。
public static StringExpression convert(ObservableValue<?> observableValue)
JavaFXの実装では、プロパティの実の値にtoString()を呼び出しています。
コードは次となります。
label.textProperty().bind(Bindings.convert(slider.valueProperty()));
Bindings.formatメソッドを使う¶
Bindingsクラスのメソッドformatは、第1引数に書式を指定し、第2引数にプロパティ(ObservableValue型)を指定すると、第2引数のプロパティが変化すると対応する文字列が生成されます。シグニチャを次に示します。
public static StringExpression format(String format, Object... args)
コードは次となります。
label.textProperty().bind(Bindings.format("%6.2f", slider.valueProperty()));
ラベルの文字列プロパティにNumberStringConverterを使ってスライダーの値プロパティをバインド¶
label.textProperty().bindBidirectional(slider.valueProperty(), new NumberStringConverter());
BindingsクラスのbindBidirectionalメソッドを用い、NumberStringConverterを第3引数に指定¶
Bindings.bindBidirectional(label.textProperty(), slider.valueProperty(), new NumberStringConverter());
- 注記
最初、バインドする数値のプロパティがDoubleProperty型だからという理由でDoubleStringConverterを使ったのですが、コンパイルエラーになってしまいました。DoubleProperty型は、Property<java.lang.Number>をimplementsしているので、NumberStringConverterでないと合致しないためと思われます。
BindingsクラスのcreateStringBindingメソッドでStringへのBindingインスタンスを生成¶
label.textProperty().bind(
Bindings.createStringBinding( () -> Double.toString(slider.getValue()), slider.valueProperty()),
);
createStringBindingメソッドのシグニチャは次です。
public static StringBinding createStringBinding(Callable<String> func, Observable... dependencies)
第1引数に、プロパティが変化したときに実行する処理(戻り値型がString)を記述したCallable(ラムダ式でも可)を指定、 第2引数に変化を監視するプロパティを指定します。
真偽値から文字列へのバインディング¶
Booleanプロパティの真偽値に応じた文字列を表示したいとします。
真偽値型のプロパティのasStringメソッド¶
真偽値型のプロパティには、StringBindingを返却するasStringメソッドが用意されています。次にシグニチャを示します。
public StringBinding asString()
引数なしのasStringは、デフォルトの文字列(真偽値型プロパティの値をtoString()したもの)を生成します。
label.textProperty().bind(checkBox.selectedProperty().asString());
結果は"true"、"false"が表示されます。
BindingsクラスのcreateStringBindingメソッドを使う¶
Bindingsユーティリティクラスには、StringExpressionを返すcreateStringBindingsメソッドがあります。
public static StringBinding createStringBinding(Callable<String> func, Observable... dependencies);
label.textProperty().bind(
Bindings.createStringBinding( ()-> checkBox.selectedProperty().get() ? "On" : "Off", checkBox.selectedProperty())
);
when, then, otherwiseを使う¶
label.textProperty().bind(
Bindings.when(checkBox.selectedProperty()).then("On").otherwise("Off")
);
基本系は、Bindings.when(条件).then(値1).otherwise(値2)
となります。値1と値2は同じ型とします。
条件には、ObservableBooleanValue型の値を指定します。
直接 new When(..)
とすることもできますが、Bindings.when(..)
とするのがよいとされています。
値を計算した結果でバインディングしたい¶
数値のプロパティに定数を加減算してバインディング¶
ある数値プロパティに定数を足す、または引いて別な数値プロパティに結び付けます。
@FXML
private AnchorPane rootPane;
@FXML
private Canvas canvas;
@Override
public void initialize(URL url, ResourceBundle rb) {
canvas.widthProperty().bind(rootPane.widthProperty().subtract(200));
canvas.heightProperty().bind(rootPane.heightProperty().subtract(120));
}
活性化・非活性化を連動したい¶
チェックボックスのチェック有無でラベルの活性化・非活性化を行いたい場合などです。
チェックボックスにチェックが付くと自動でテキストフィールドを活性化¶
@FXML CheckBox editCheckBox;
@FXML TextField editField;
:
@Override
public void initialize(URL url, ResourceBundle rb) {
editField.disableProperty().bind(Bindings.not(editCheckBox.selectedProperty()));
}
TextFieldのdisabledPropertyをtrueにすると、TextFieldが非活性化します。
チェックボックスにチェックが付くとCheckBoxのselectedPropertyがtrueになります。チェックが付いたときにTextFieldを活性化し、チェックが外れたときにTextFieldを非活性化するので、論理を反転させるためBindings.notを入れています。