使用JavaFX2.0的屬性和綁定
(原文:斯科特霍梅爾/甲骨文高級技術專家)
原文地址:http://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm
本教程通過一些可以編譯和運行的例子描述了JavaFX的屬性和綁定。關於JavaFX的安裝,請參閱JavaFX安裝指南。
概述
很多年以來,Java語言一直使用JavaBean來表示對象的屬性,這種模式既包含API,也包含設計模式,它已經廣泛地被Java應用程序開發者所熟知,開發工具也一直使用這種模式。 這個版本將屬性支持引入到了JavaFX中,它基於經過驗證的JavaBean的模式,但做了擴展和改進。
JavaFX的屬性經常通過綁定來綜合,這是一種表達變量間關係的強大的機制。當對象被綁定後,一個對象的改變會自動被反射到另一個對象。對應用程序來說,這是非常有用的。例如,綁定可以用在帳單票據跟蹤程序中。在這種情況下,當一個獨立的帳單改動後,總帳單將會自動改變。另外,綁定還可以應用到圖形用戶界面(GUI)中,用來將應用程序的基礎數據的改變同步顯示出來。
綁定集成了一個或多個資源,也稱爲“依賴關係”,綁定觀察者保存依賴關係的列表,當檢測到變化時自動更新列表。
綁定的API分爲兩大類:
1.高級別的API:提供了一種簡單的方法來創建最常見的綁定用例。它的語法很容易學習和使用,特別是在支持自動完成功能的環境中。比如NetBeans IDE中。
2.低級別的API:提供額外的靈活性,可供高級開發人員在高級別API不能滿足需要時使用。低級別的API是專爲快速執行和小內存佔用設計的。
本教程的所面部分將介紹這些API,並提供代碼示例,您可以編譯和運行它們。有關其他信息,請參閱JavaFX API文檔。
理解屬性
理解JavaFX的屬性需要學習一些新的API和命名約定。你完全有可能只對使用包含屬性的類感興趣(反對在你自已的類中實現屬性)。但例1將讓你熟悉一種新的來自屬性模式的命名規則。它定義了一個名爲Bill的類,實現了一個名爲amoutDue的屬性。
例1:定義一個屬性
import javafx.beans.property.DoubleProperty;
class Bill {
//定義一個變量來保存屬性
private DoubleProperty amountDue = new DoubleProperty();
//定義一個getter方法來獲取屬性的值
public final double getAmountDue(){return amountDue.get();}
//定義一個setter來設定屬性的值
public final void setAmountDue(double value){amountDue.set(value);}
//定義一個getter來訪問屬性
public DoubleProperty amountDueProperty() {return amountDue;}
}
amountDue對象是一個javafx.beans.property.DoubleProperty的類實例,被標以private來限制外部訪問。這在Java或JavaBean程序開發中是一種標準做法。但是請注意,該對象的不是一個標準的Java基本數據類型,而是封裝了Java基本數據類型,並增加了一些額外功能的封裝類。(javafx.beans.property包中的類都內置了觀察和綁定作爲設計的一部分)。
屬性方法命名規則如下:
l getAmountDue()是一個標準的getter方法,返回當前值amountDue屬性的值。按照慣例,它被聲明爲final。注意,此方法返回類型是雙精度數值而不DoubleProperty類型。
l setAmountDue(double)方法(也被聲明爲final)是一個標準的setter方法,用來設置屬性的值。setter方法是可選的,其參數的類型也是雙精度數值。
l 最後,amountDueProperty()方法定義了屬性的getter。這是一種新的命名約定,方法中包含了屬性的名稱(在本例中是amountDue),然後加上“Property”。 返回類型是屬性本身的類型(本例中是DoubleProperty)。
在用JavaFX構建GUI應用程序時,你會注意到某些API裏的類已經實現了屬性。例如:javafx.scene.shape.Rectangle類包含的屬性arcHeight,arcWidth,height,width,x,y。對於這些屬性,都有與前面提到的命名規則相對應的方法。例如,getArcHeight(),setArcHeight(double),arcHeightProperty()。 它們共同說明(對開發人員和工具),給定的屬性存在。
例2:使用ChangeListener
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);
}
}
運行該示例將在標準輸出中打印消息“帳單已經改變”,證明更改偵聽的起作用了。
使用高級別綁定API
高級別API是在你的應用程序中使用綁定的最快和最簡單的方式。 它包括兩個部分:Fluent API和綁定類。Fluent API將方法向各種依賴對象公開,而綁定類提供靜態工廠方法。
要開始使用Fluent API,考慮一個簡單的用例。其中兩個整數是綁定的,以使他們的值觀總是加在一起。在例3中,涉及三個變量:num1(依賴者),num2(依賴者)和sum(綁定)。依賴類型都IntegerProperty,綁定類型是NumberBinding。
例3:使用Fluent API
package bindingdemo;
import javafx.beans.property.IntegerProperty;
import javafx.binding.NumberBinding;
public class Main {
public static void main(String[] args) {
IntegerProperty num1 = new IntegerProperty(1);
IntegerProperty num2 = new IntegerProperty(2);
NumberBinding sum = num1.add(num2);
System.out.println(sum.getValue());
num1.set(2);
System.out.println(sum.getValue());
}
}
此代碼綁定兩個依賴,打印其總和。改變值num1並再次打印總和,結果是“3”和“4”,這證明了綁定起作用了。
也可以使用綁定類來做同樣的事,如例4所示。
例4:使用綁定類
import javafx.beans.property.IntegerProperty;
import javafx.binding.NumberBinding;
import javafx.binding.Bindings;
public class Main {
public static void main(String[] args) {
IntegerProperty num1 = new IntegerProperty(1);
IntegerProperty num2 = new IntegerProperty(2);
NumberBinding sum = Bindings.add(num1,num2);
System.out.println(sum.getValue());
num1.setValue(2);
System.err.println(sum.getValue());
}
}
例5使用了兩種方法。
例5:使用兩種方法
import javafx.beans.property.IntegerProperty;
import javafx.binding.NumberBinding;
import javafx.binding.Bindings;
public class Main {
public static void main(String[] args) {
IntegerProperty num1 = new IntegerProperty(1);
IntegerProperty num2 = new IntegerProperty(2);
IntegerProperty num3 = new IntegerProperty(3);
IntegerProperty num4 = new IntegerProperty(4);
NumberBinding total = Bindings.add(num1.multiply(num2),num3.multiply(num4));
System.out.println(total.getValue());
num1.setValue(2);
System.err.println(total.getValue());
}
}
例5修改代碼來調用Fluent API的multiply和綁定類的add方法。你也應該知道,高級別API允許你定義算術運算時混合類型。結果類型的定義爲採用與Java編程語言相同的規則:
1. 如果操作數是雙精度,則結果也是雙精度。
2. 如果不是雙精度而是浮點型,其結果是一個浮點型。
3. 如果不是浮點型而是長整型,則結果是長整型。
4. 其他情況,結果是一個整數。
下一節探討觀察者,並演示失效監聽與改變監聽的不同。
探索ObservableValue,InvalidationListener和ChangeListener
屬性和綁定類都實現了ObservableValue<T>接口,ObservableValue包裝了值並允許它觀察改變。JavaFX綁定和屬性實現都支持懶惰計算,這意味着當變化發生時,該值不立即重新計算。重新計算稍後發生,也就時當再次請求時。
在例6中,帳單總數(一個綁定)在它檢測到其中的一個依賴的第一時間被標記爲無效。無論如何,只有當帳單總數被再次請求時,綁定對象才被重新計算。
例6:使用InvalidationListener
import javafx.beans.property.DoubleProperty;
import javafx.binding.NumberBinding;
import javafx.binding.Bindings;
import javafx.beans.value.InvalidationListener;
import javafx.beans.value.ObservableValue;
class Bill {
// Define the property
private DoubleProperty amountDue = new DoubleProperty();
// 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(ObservableValue o) {
System.out.println("The binding is now invalid.");
}
});
//第一次調用使綁定失效
bill1.setAmountDue(200.00);
//綁定現在無效
bill2.setAmountDue(100.00);
bill3.setAmountDue(75.00);
//綁定現在有效
System.out.println(total.getValue());
//再次使用失效
bill3.setAmountDue(150.00);
//使它有效
System.out.println(total.getValue());
}
}
通過改變單一帳單的值,綁定變爲無效,無效監聽器觸發。但是,如果綁定已經無效,無效監聽器將會再次被觸發,即使其他帳單發生了變化。(在例6中,調用total.getValue()使綁定從無效變爲有效)。我們知道這個是因爲隨後對依賴列表中任一帳單的改變導致了無準備監聽器的再次觸發。如果綁定仍然無效,則觸發不會發生。
ObservableValue,InvalidationListener和ChangeListener的方法簽名如下:
ObservableValue
l public void addListener(ChangeListener listener)
l public void addListener(InvalidationListener listener)
l public T getValue()
l public void removeListener(ChangeListener listener)
l public void removeListener(InvalidationListener listener)
InvalidationListener
l public void invalidated(ObservableValue observable)
ChangeListener
l public void changed(ObservableValue observable, T oldValue, T newValue)
請注意,註冊一個ChangeListener將強制執行急迫計算,即使ObservableValue的實現支持懶惰計算。對於一個懶惰計算值,在它被重新計算前,是不可能知道一個無效值是否已經被改變的。出於這個原因,產生改變事件需要急迫計算。而失效的事件,可以通過急迫實現產生,也可以通過懶惰實現產生。
使用低級別綁定API
如果高級別API不夠滿足您的需求,你總是可以使用低級別的API來代替。低級別的API是爲那些需要更多靈活性(或更好性能)的開發者準備的,這些特性在高級API中沒有提供。
示例7展示了使用低級別的API基本的例子。
例7:使用低級別API
import javafx.beans.property.DoubleProperty;
import javafx.binding.DoubleBinding;
public class Main {
public static void main(String[] args) {
final DoubleProperty a = new DoubleProperty(1);
final DoubleProperty b = new DoubleProperty(2);
final DoubleProperty c = new DoubleProperty(3);
final DoubleProperty d = new DoubleProperty(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調用繼承自綁定類的一個方法並且覆寫了computeValue()方法,返回當前綁定的值。例7通過這一個自定義的DoubleBinding子類完成這一工作。調用super.bind()向上將依賴傳遞給DoubleBinding,以便默認無效行爲被保留。它通常沒有必要爲你檢查綁定是否無效,這些行爲通過基類來提供。
現在你已經有足夠的信息來使用低級別API了,本文的後續版本將擴展這部份內容來說明如何展現各種不周的優化策略以使你自定義的綁定更有效率。