1 JavaFXのプロパティとバインディングの使用
このチュートリアルでは、JavaFXアプリケーションでのプロパティとバインディングの使用方法について学習します。
このチュートリアルでは、関連するAPIについて説明し、コンパイルして実行できる実践的な例を示します。
概要
長い間、Javaプログラミング言語では、オブジェクトのプロパティを表すためにJavaBeansコンポーネント・アーキテクチャが使用されていました。このモデルはAPIと設計パターンの両方で構成され、Javaアプリケーション開発者と開発ツールによって一様に広く理解されています。このリリースでは、JavaFXへのプロパティのサポート、つまり、実証済のJavaBeansモデルをベースとし、拡張と改善が行われたサポートが導入されます。
JavaFXプロパティは、変数間の直接関係を表すための強力なメカニズムであるバインディングとともに使用されることが多くあります。オブジェクトがバインディングに参加している場合、1つのオブジェクトに対して行われた変更が別のオブジェクトに自動的に反映されます。これは、様々なアプリケーションで役立ちます。たとえば、請求書追跡プログラムでバインディングを使用すると、個別の請求書が変更されたときに、全請求書の合計が自動的に更新されるようにできます。あるいは、グラフィカル・ユーザー・インタフェース(GUI)でバインディングを使用して、その表示がアプリケーションの基盤データと自動的に同期されるようにできます。
バインディングは、1つ以上の依存性と呼ばれるソースからアセンブルされます。バインディングは、変更に対する依存性のリストを保持し、変更が検出されると自動的に自身を更新します。
バインディングAPIは、2つの大きなカテゴリに分類されます。
-
高レベルAPI: 最も一般的なユースケース向けのバインディングを作成できる単純な方法を提供します。その構文は、特にNetBeans IDEのようにコード補完が提供される環境では、簡単に習得して使用できます。
-
低レベルAPI: さらなる柔軟性を提供し、高レベルAPIでは不十分な場合に熟練の開発者によって使用されます。低レベルAPIは、高速な実行と小さいメモリー・フットプリントを実現するために設計されました。
このチュートリアルでは、この後、これらのAPIについて説明し、コンパイルして実行できる実践的なコード例を示します。
プロパティについての理解
概要で説明したように、JavaFXプロパティのサポートは、JavaBeansコンポーネント・アーキテクチャによって確立された有名なプロパティ・モデルに基づいています。この項では、その意味を概説してから、どのようにプロパティがJavaFXに適用されるのかについて説明します。
Javaプログラミング言語は、オブジェクト内でのデータのカプセル化を可能にしますが、定義するメソッド対して特定の命名規則を適用することを強制しません。たとえば、コード内で、姓と名をカプセル化するPerson
クラスを定義するとします。ただし、命名規則がないため、別のプログラマはこれらのメソッドに対してread_first()
、firstName()
、getFN()
などの異なる名前を選択する可能性がありますが、これらはすべて完全に有効な選択肢です。ただし、これらの名前が他の開発者にとってわかりやすいかどうかは保証されません。
JavaBeansコンポーネント・アーキテクチャは、プロジェクト間で一貫性を持たせるためにいくつかの単純な命名規則を定義することによって、この問題に対処しました。JavaBeansプログラミングでは、これらのメソッドの完全なシグネチャは、public void setFirstName(String name)
、public String getFirstName()
、public void setLastName(String name)
およびpublic String getLastName()
になります。この命名パターンは、人間であるプログラマとNetBeans IDEなどの編集ツールのどちらにとっても容易に識別可能です。JavaBeansの用語では、Person
オブジェクトにはfirstName
およびlastName
プロパティが含まれていると言われます。
JavaBeansモデルは、複雑なプロパティ・タイプのサポートに加え、イベント配信システムのサポートも提供します。また、多数のサポート・クラスも含まれており、それらはすべて、java.beans
パッケージの下でAPIとして使用できます。このため、JavaBeansプログラミングの習得には、必須の命名規則とそれに対応するAPIの学習が含まれます。(JavaBeansに関する一般的な背景情報については、JavaチュートリアルのJavaBeansレッスンを参照してください)。
同様に、JavaFXプロパティを理解するには、いくつかの新しいAPIと命名規則の学習が必要です。JavaFXでは、(独自のカスタム・クラスにプロパティを実装せずに)プロパティを含むクラスを使用することのみを目的とすることも可能ですが、例1-1では、JavaFXプロパティのパターンを形成する新しいメソッドの命名規則についても確認できます。これは、Bill
という名前のクラスを定義しており、これはamountDue
という名前の単一のプロパティを実装します。
例1-1 プロパティの定義
package propertydemo; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; class Bill { // Define a variable to store the property private DoubleProperty amountDue = new SimpleDoubleProperty(); // Define a getter for the property's value public final double getAmountDue(){return amountDue.get();} // Define a setter for the property's value public final void setAmountDue(double value){amountDue.set(value);} // Define a getter for the property itself public DoubleProperty amountDueProperty() {return amountDue;} }
amountDue
オブジェクトは、javafx.beans.property.DoubleProperty
クラスのインスタンスで、外部からカプセル化するためにprivate
とマークされています。これは、JavaとJavaBeansの両方のアプリケーション開発における標準手法です。ただし、オブジェクトのタイプは標準のJavaプリミティブではなく、Javaプリミティブをカプセル化していくつかの機能を追加する新しいラッパー・クラスです(javafx.beans.property
の下にあるすべてのクラスには、その設計の一部として監視可能性とバインディングの組込みサポートが含まれています)。
プロパティ・メソッドの命名規則は、次のとおりです。
-
getAmountDue()
メソッドは、amountDue
プロパティの現在の値を返す標準ゲッター・メソッドです。規則では、このメソッドはfinal
として宣言されます。このメソッドの戻り型は、DoubleProperty
ではなくdouble
です。 -
setAmountDue(double)
メソッド(final
)は、コール元にプロパティ値の設定を許可する標準セッター・メソッドです。セッター・メソッドはオプションです。このパラメータの型もdouble
です。 -
最後に、
amountDueProperty()
メソッドはプロパティのゲッター・メソッドを定義します。これは新しい規則で、メソッド名にプロパティ名(この例ではamountDue
)が含まれ、その後にPropertyという単語が続きます。戻り型は、プロパティ自体の型と同じです(この例では、DoubleProperty
)。
JavaFXを使用してGUIアプリケーションをビルドする際、APIの特定のクラスにプロパティがすでに実装されていることがわかります。たとえば、javafx.scene.shape.Rectangle
クラスには、arcHeight
、arcWidth
、height
、width
、x
およびy
のプロパティが含まれています。これらの各プロパティには、前述した規則に従った対応するメソッドがあります。たとえば、getArcHeight()
、setArcHeight(double)
およびarcHeightProperty()
は、ともに特定のプロパティが存在することを(開発者とツールの両方に対して)示します。
また、例1-2に示すように、プロパティの値が変更されたときに通知されるように、変更リスナーを追加することもできます。
例1-2 ChangeListenerの使用
package propertydemo; import javafx.beans.value.ObservableValue; import javafx.beans.value.ChangeListener; public class Main { public static void main(String[] args) { Bill electricBill = new Bill(); electricBill.amountDueProperty().addListener(new ChangeListener(){ @Override public void changed(ObservableValue o,Object oldVal, Object newVal){ System.out.println("Electric bill has changed!"); } }); electricBill.setAmountDue(100.00); } }
この例を実行すると、「Electric bill has changed」というメッセージが出力され、変更リスナー通知が機能していることがわかります。
高レベル・バインディングAPIの使用
高レベルAPIは、独自のアプリケーションでバインディングの使用を最も迅速かつ簡単に開始できる方法です。これは、Fluent APIとBindings
クラスの2つの部分で構成されます。Fluent APIは様々な依存オブジェクトにメソッドを公開するのに対し、Bindings
クラスは静的なファクトリ・メソッドを提供します。
Fluent APIの使用を開始するために、2つの整数をバインドして、それらの値が常に加算されるようにする単純な用途について考えてみます。例1-3では、num1
(依存)、num2
(依存)およびsum
(バインディング)の3つの変数が関連しています。依存の型は両方ともIntegerProperty
で、バインディング自体はNumberBinding
です。
例1-3 Fluent APIの使用
package bindingdemo; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.binding.NumberBinding; public class Main { public static void main(String[] args) { IntegerProperty num1 = new SimpleIntegerProperty(1); IntegerProperty num2 = new SimpleIntegerProperty(2); NumberBinding sum = num1.add(num2); System.out.println(sum.getValue()); num1.set(2); System.out.println(sum.getValue()); } }
このコードは、2つの依存をバインドし、その合計を出力してから、num1
の値を変更して再び合計を出力します。結果は3と4であり、バインディングが機能していることがわかります。
例1-4に示すように、Bindings
クラスを使用して、同じことを実行することもできます。
例1-4 Bindingsクラスの使用
package bindingdemo; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.binding.NumberBinding; import javafx.beans.binding.Bindings; public class Main { public static void main(String[] args) { IntegerProperty num1 = new SimpleIntegerProperty(1); IntegerProperty num2 = new SimpleIntegerProperty(2); NumberBinding sum = Bindings.add(num1,num2); System.out.println(sum.getValue()); num1.setValue(2); System.err.println(sum.getValue()); } }
例1-5は、2つのアプローチを組み合せています。
例1-5 両方のアプローチの組合せ
package bindingdemo; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.binding.NumberBinding; import javafx.beans.binding.Bindings; public class Main { public static void main(String[] args) { 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()); num1.setValue(2); System.err.println(total.getValue()); } }
例1-5では、Fluent APIからmultiply
メソッドを呼び出し、Bindings
クラスからadd
を呼び出すようにコードを変更しています。高レベルAPIでは、算術演算を定義するときに複数の型を組み合せることができます。結果の型は、Javaプログラミング言語と同じルールに従って定義されます。
-
いずれか1つのオペランドが
double
である場合、結果はdouble
になります。 -
いずれか1つのオペランドが
float
である場合、結果はfloat
になります。 -
いずれか1つのオペランドが
long
である場合、結果はlong
になります。 -
それ以外の場合、結果は整数です。
次の項では、追跡可能性について説明し、無効化リスナーと変更リスナーの違いについて説明します。
Observable、ObservableValue、InvalidationListenerおよびChangeListenerの説明
バインディングAPIは、値が変更されたとき、または無効化が行われたときにオブジェクトに通知することを可能にする、一連のインタフェースを定義します。Observable
およびObservableValue
インタフェースは変更の通知を起動し、InvalidationListener
およびChangeListener
インタフェースはそれらを受信します。これら2つの違いは、ObservableValue
は値をラップしてその変更を登録済のChangeListener
に通知するのに対し、Observable
(値をラップしない)はその変更を登録済のInvalidationListener
に通知することです。
JavaFXのすべてのバンディングおよびプロパティの実装では、変更が行われても値が即時に再計算されない遅延評価がサポートされています。再計算は、値がその後要求された場合に、後から行われます。
例1-6では、請求書合計(バインディング)がその依存のいずれか1つで初めて変更を検出したときに、無効としてマークされます。ただし、バインディング・オブジェクトが自身を再計算するのは、合計が再び要求された場合のみです。
例1-6 InvalidationListenerの使用
package bindingdemo; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.binding.NumberBinding; import javafx.beans.binding.Bindings; import javafx.beans.InvalidationListener; import javafx.beans.Observable; class Bill { // Define the property private DoubleProperty amountDue = new SimpleDoubleProperty(); // Define a getter for the property's value public final double getAmountDue(){return amountDue.get();} // Define a setter for the property's value public final void setAmountDue(double value){amountDue.set(value);} // Define a getter for the property itself public DoubleProperty amountDueProperty() {return amountDue;} } public class Main { public static void main(String[] args) { Bill bill1 = new Bill(); Bill bill2 = new Bill(); Bill bill3 = new Bill(); NumberBinding total = Bindings.add(bill1.amountDueProperty().add(bill2.amountDueProperty()), bill3.amountDueProperty()); total.addListener(new InvalidationListener() { @Override public void invalidated(Observable o) { System.out.println("The binding is now invalid."); } }); // First call makes the binding invalid bill1.setAmountDue(200.00); // The binding is now invalid bill2.setAmountDue(100.00); bill3.setAmountDue(75.00); // Make the binding valid... System.out.println(total.getValue()); // Make invalid... bill3.setAmountDue(150.00); // Make valid... System.out.println(total.getValue()); } }
1つの請求書の値を変更すると、バインディングが無効になり、無効化リスナーが起動します。ただし、バインディングがすでに無効になっている場合、別の請求書が変更されても、無効化リスナーは再び起動しません。(例1-6では、total.getValue()
の呼出しによってバインディングが無効から有効に変更されています。)これは、依存性リストに含まれている請求書が後から変更されると、無効化リスナーが再び起動するためです。バインディングがまだ無効な場合は、これは発生しません。
ChangeListener
を登録すると、ObservableValue
の実装で遅延評価がサポートされていても、即時の再計算が強制されます。遅延評価される値については、無効な値が実際に変更されたかどうかは、それが再計算されるまでわかりません。このため、無効化イベントは即時実装と遅延実装の両方に対して生成できますが、変更イベントの生成には即時評価が必要です。
低レベル・バインディングAPIの使用
高レベルAPIのみでは要件を満たすのに不十分である場合は、かわりにいつでも低レベルAPIを使用できます。低レベルAPIは、高レベルAPIによって提供されるよりも優れた柔軟性(またはより優れたパフォーマンス)を必要とする開発者向けです。
例1-7は、低レベルAPIを使用する基本的な例を示しています。
例1-7 低レベルAPIの使用
package bindingdemo; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.binding.DoubleBinding; public class Main { public static void main(String[] args) { final DoubleProperty a = new SimpleDoubleProperty(1); final DoubleProperty b = new SimpleDoubleProperty(2); final DoubleProperty c = new SimpleDoubleProperty(3); final DoubleProperty d = new SimpleDoubleProperty(4); DoubleBinding db = new DoubleBinding() { { super.bind(a, b, c, d); } @Override protected double computeValue() { return (a.get() * b.get()) + (c.get() * d.get()); } }; System.out.println(db.get()); b.set(3); System.out.println(db.get()); } }
低レベルAPIを使用するには、バインディング・クラスの1つを拡張して、そのcomputeValue()
メソッドを、バインディングの値を返すようにオーバーライドする必要があります。例1-7では、DoubleBinding
のカスタム・サブクラスを使用してこれを行っています。super.bind()
の呼出しによって依存がDoubleBinding
まで渡され、デフォルトの無効化動作が保持されます。通常、バインディングが無効かどうかをチェックする必要はありません。この動作はベース・クラスによって提供されます。
これで、低レベルAPIの使用を開始するのに十分な情報が得られました。