Swingに慣れた身として、JavaFXに独特なものとしてバインディングがある。バインディングとは、「sum=a+b」という変数sumがあった場合、変数aや変数bが変更されるとsumの値も連動して自動的に更新されるしくみである。用途として、GUIの操作に連動してリアルタイムに画面出力を変えることなどが考えられる。今回はバインディングの使い方についてみていく。
■ バインディングの基本
バインディングには、バインディングとプロパティの2クラスが関係する。
バインディング・クラスは、他の変数に連動して自動更新されるクラスである。「sum=a+b」の例で言うと、sumにあたる。バインディング・クラスは内部に依存物リスト(dependency list)というもの保持し、この依存物リストに登録された変数が変更されると自動的に自身を更新するという仕組みである。
プロパティ・クラスは、バインディング・クラスの依存物リストとして登録可能なクラスである。「sum=a+b」の例で言うと、aやbにあたる。依存物リストにはプリミティブ型(intやdoubleなど)は登録できず、バインディングとプロパティのみ登録できる。
以下に、利用可能なバインディング・クラスと、プロパティ・クラスを示す。なお、全てのバインディングはNumberBindingクラスを継承する。
◇バインディング・クラス
分類 |
クラス |
内容 |
ブール値 |
BooleanBinding |
boolean型のバインディング |
数 |
IntegerBinding |
int型のバインディング |
LongBinding |
long型のバインディング |
FloatBinding |
Float型のバインディング |
DoubleBinding |
Double型のバインディング |
文字 |
StringBinding |
文字列型のバインディング |
コレクション |
ListBinding |
リスト型のバインディング |
MapBinding |
マップ型のバインディング |
SetBinding |
集合型のバインディング |
汎用 |
ObjectBinding |
オブジェクト型のバインディング |
◇プロパティ・クラス
分類 |
クラス |
内容 |
ブール値 |
SimpleBooleanProperty |
boolean型のプロパティ |
数 |
SimpleIntegerProperty |
int型のプロパティ |
SimpleLongProperty |
long型のプロパティ |
SimpleFloatProperty |
Float型のプロパティ |
SimpleDoubleProperty |
Double型のプロパティ |
文字 |
SimpleStringProperty |
文字列型のプロパティ |
コレクション |
SimpleListProperty |
リスト型のプロパティ |
SimpleMapProperty |
マップ型のプロパティ |
SimpleSetProperty |
集合型のプロパティ |
汎用 |
SimpleObjectProperty |
オブジェクト型のプロパティ |
■ バインディングの使い方
続いて、各バインディング・クラスを利用する方法を見ていく。バインディングを行う方法は2通りの存在する。1つ目は既存のクラスをそのまま利用する方法(高レベルAPIの利用)、2つ目は既存クラスを継承した新しいクラスを定義する方法(低レベルAPIの利用)である。
■ 高レベルAPIの使い方
以下にバインディング(高レベルAPI)を利用したサンプルコードを示す。サンプルで3つの整数値プロパティの値を合計するバインディングを作成している。
◇サンプルコード
package application_fx;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class TestBindingHighLevel {
/**
* バインディングのテスト
* @param args
*/
public static void main(String[] args)
{
// プロパティを宣言
IntegerProperty num1 = new SimpleIntegerProperty( 1 );
IntegerProperty num2 = new SimpleIntegerProperty( 2 );
IntegerProperty num3 = new SimpleIntegerProperty( 3 );
// バインディングを作成
NumberBinding tmp = num1.add( num2 );
NumberBinding sum = Bindings.add( tmp , num3 );
// 初期値を出力
System.out.printf( "%d + %d + %d = %d\n" , num1.getValue() , num2.getValue() , num3.getValue() , sum.getValue() );
// プロパティティを変更させた結果を出力
num1.set( 10 );
System.out.printf( "%d + %d + %d = %d\n" , num1.getValue() , num2.getValue() , num3.getValue() , sum.getValue() );
}
}
◇実行結果
1 + 2 + 3 = 6
10 + 2 + 3 = 15
◇解説
- 整数型のプロパティを3つ作成し、それぞれに1,2,3の整数値を代入している(17行目~19行目)
- 1つめのバインディングでは、1と2を足すように設定(依存リストにnum1,num2を追加)している(22行目)
- 2つめのバインディングでは、1つめのバインディングに3を足すように設定(依存リストにバインディングとnum3を追加)している(23行目)
- プロパティやバインディングの値はgetValue関数で取得する(26行目)
- プロパティの値を変更すると、その計算結果であるバインディングの値も変更されている(29行目)
■ 低レベルAPIの使い方
もう1つのバインディング実装方法であるBindingクラスを継承する方法(低レベルAPIを利用)を見ていく。継承したクラスでは、依存リストはsuper.bindクラスで登録し、computeValue関数に計算方法を実装し、オーバーライドする。高レベルAPIでは記述しづらい、複雑な計算をしたい場合に利用する。
以下にバインディング(低レベルAPI)を利用したサンプルコードを示す。サンプルでは4つのDouble型プロパティの積を求めるバインディングを作成している。
◇ サンプルコード
package application_fx;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public class TestBindingLowLevel
{
public static void main( String[] args )
{
// プロパティを宣言
final DoubleProperty a = new SimpleDoubleProperty( 1.0 );
final DoubleProperty b = new SimpleDoubleProperty( 2.0 );
final DoubleProperty c = new SimpleDoubleProperty( 3.0 );
final DoubleProperty d = new SimpleDoubleProperty( 4.0 );
// バインディングを作成(低レベルAPIを利用)
DoubleBinding db = new DoubleBinding()
{
// インスタンスイニシャライザ・ブロック
{
// 依存物リストへプロパティを登録
super.bind(a, b, c, d);
}
/**
* 計算方法を設定(すべてのプロパティの積を取得)
*/
@Override
protected double computeValue() {
return a.getValue() * b.getValue() * c.get() * d.get();
}
};
// 初期値を出力
System.out.println( db.getValue() );
// プロパティティを変更させた結果を出力
b.set( 10.0 );
System.out.println( db.getValue() );
}
}
◇実行結果
24.0
120.0
◇解説
- バインディングの作成では、DoubleBinding型を継承した匿名クラスを作成している(17行目~33行目)
- インスタンス作成時に実行されるブロックで、依存物リストにプロパティを登録する(20行目~24行目)
- computeValue関数に計算式を登録する(26行目~32行目)
- 新たにプロパティを設定することで、バインディングが再計算される(39行目~40行目)
■ InvalidationListenerとChangeListener
バインディングの依存物リストが変更されるタイミングで、計算以外のことを行いたい場合はイベントリスナーを設定する。リスナーは2種類あり、別々のタイミングで呼び出される。
クラス |
内容 |
InvalidationListener |
バインディングの値が無効になる度に呼び出される。有効/無効については、サンプルコードで確認する。
Observableが引数として渡される |
ChangeListener |
依存リスト内の値が変化する度に呼び出される。
ObservableValueが引数として渡される |
バインディングで再計算が発生するタイミングは、プロパティが変更された直後とは限らない。計算のタイミングは
- 依存物リストに登録されたプロパティが変更された直後
- バインディングの値が利用される(getValue関数などが呼ばれる)直前
の間となる。ただし、ChangeListenerを登録している場合、依存リストが変更されると即時に再計算される。
以下にリスナーの使い方と、有効/無効の切り替えタインミングを確認するサンプルコードを示す。
◇サンプルコード
package application_fx;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class TestBindingListener {
public static void main(String[] args)
{
// プロパティを宣言
IntegerProperty a = new SimpleIntegerProperty( 1 );
IntegerProperty b = new SimpleIntegerProperty( 2 );
// バインディングを設定
NumberBinding sum = Bindings.add( a , b );
// バインディングにリスナーを登録
InvalidationListener listener
= new InvalidationListener()
{
@Override public void invalidated( Observable o )
{ System.out.println("The binding became invalid. " ); };
};
sum.addListener( listener );
// ちなみにラムダ式で記述すると以下の通り
//InvalidationListener listener = o -> System.out.println("The binding is now invalid." + o );
//sum.addListener( listener );
// プロパティaを変更。バインディングが無効状態になる
a.set( 10 );
// プロパティbを変更。バインディングは引き続き無効状態
b.set( 20 );
// バインディングを出力。バインディングは有効状態になる
System.out.println( sum.getValue() );
// プロパティaを変更。バインディングが無効状態になる
a.set( 100 );
// バインディングを出力。バインディングは有効状態になる
System.out.println( sum.getValue() );
}
}
◇実行結果
The binding became invalid.
30
The binding became invalid.
120
◇解説
依存物プロパティが変更された直後は無効状態と呼ばれ、バインディングの再計算が行われていない。この無効状態になった際に呼び出されるのが、InvalidationListener関数である。それに対して、バインディングの再計算が行われた状態を有効状態という。
サンプルの状態の変化を図示すると、以下のようになる。
■ 注意事項
バインディングをさらにバインディングすることも可能である。ただPG経験上、バインディングを多用しすぎるとプログラムの動作が見づらくなり、デバッグなども困難になると思われるので注意が必要かと思う。
■ 参照
- JavaFXのプロパティとバインディングの使用
- JavaDoc - パッケージjavafx.beans.propertyの階層
- JavaDoc - パッケージjavafx.beans.bindingの階層