JavaFX系結


JavaFX係結同步兩個值:當依賴變數更改時,其他變數更改。

要將屬性系結到另一個屬性,請呼叫bind()方法,該方法在一個方向系結值。 例如,當屬性A係結到屬性B時,屬性B的更改將更新屬性A,但不可以反過來。

係結選項

JavaFX提供了許多系結選項,以便在域物件和GUI控制元件中的屬性之間進行同步。

我們可以在JavaFX的Properties API中使用以下三種系結策略:

  • Java Bean上的雙向系結
  • Fluent API的高階系結
  • 使用javafx.beans.binding定義係結物件進行低階系結。

雙向系結

雙向系結系結相同型別的屬性,並同步兩側的值。當使用bindBidirectional()方法雙向系結時,需要兩個屬性都必須是可讀/寫的。
以下程式碼顯示如何在firstName屬性和字串屬性變數之間進行雙向系結

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Main {
    public static void main(String[] args) {
        User contact = new User("Jame", "Bind");
        StringProperty fname = new SimpleStringProperty();
        fname.bindBidirectional(contact.firstNameProperty());

        contact.firstNameProperty().set("new value");
        fname.set("新系結名稱值");

        System.out.println("firstNameProperty = " + contact.firstNameProperty().get());
        System.out.println("fname = " + fname.get());

    }// @ W WW .yI iB  AI.c  o M
}

class User {

    private SimpleStringProperty firstName = new SimpleStringProperty();
    private SimpleStringProperty lastName = new SimpleStringProperty();

    public User(String fn, String ln) {
        firstName.setValue(fn);
        lastName.setValue(ln);
    }

    public final String getFirstName() {
        return firstName.getValue();
    }

    public StringProperty firstNameProperty() {
        return firstName;
    }

    public final void setFirstName(String firstName) {
        this.firstName.setValue(firstName);
    }

    public final String getLastName() {
        return lastName.getValue();
    }

    public StringProperty lastNameProperty() {
        return lastName;
    }

    public final void setLastName(String lastName) {
        this.lastName.setValue(lastName);
    }
}

上面的程式碼生成以下結果。

firstNameProperty = 新系結名稱值
fname = 新系結名稱值

高階別系結

我們也可以使用JavaFX流利的API來系結屬性。API使用類似英語的方法名稱對屬性執行操作。 例如,multiply()divide()subtract()isEqualTo()isNotEqualTo()concat()。 請注意,方法名稱中沒有getset。當連結API在一起時可以寫程式碼,就像類似於寫英文句子,例如,width().multiply(height()).divide(2)

以下程式碼顯示如何建立表示計算矩形面積的公式的屬性。

它通過使用javafx.beans.binding.IntegerExpression介面中的fluent API來執行高階系結。

該程式碼使用multiply()方法,該方法返回包含計算值 - NumberBinding

這個系結是延遲評估求值的,這意味著乘法不會執行計算,除非通過get()getValue()方法呼叫屬性的值。

import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class Main {
  public static void main(String[] args) {
    // Area = width * height
    IntegerProperty width = new SimpleIntegerProperty(10);
    IntegerProperty height = new SimpleIntegerProperty(10);
    NumberBinding area = width.multiply(height);
    System.out.println(area.getValue());
  }
}

上面的程式碼生成以下結果。

100

低階別系結

當對NumberBinding類進行子類化時,使用低階別系結,例如Double型別的DoubleBinding類。

DoubleBinding類的子類中,我們覆蓋它的computeValue()方法,以便可以使用運算子(例如*- )來制定複雜的數學方程計算。
高階系結使用如multiply()subtract()等方法,而低階系結使用諸如*- 等運算子。

以下程式碼顯示如何為公式建立低階別系結,來計算矩形的面積。

import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;

public class Main {
  public static void main(String[] args) {
    DoubleProperty width = new SimpleDoubleProperty(2);
    DoubleProperty height = new SimpleDoubleProperty(2);
    DoubleBinding area = new DoubleBinding() {
      {
        super.bind(width, height); // initial bind
      }

      @Override
      protected double computeValue() {
        return width.get() * height.get();
      }
    };
    System.out.println(area.get());
  }
}

上面的程式碼生成以下結果。

4.0

UI控制元件和域模型之間的系結

在JavaFX中,UI控制元件和域模型之間的資料系結很容易。以下程式碼顯示如何建立登入對話方塊並繫結使用者域物件。
首先,我們定義域物件,它是描述使用者名和密碼的JavaFX JavaBean

class User {
   private final ReadOnlyStringWrapper userName;
   private StringProperty password;
   public User() {
       userName = new ReadOnlyStringWrapper(this, "userName", "ABC");
       password = new SimpleStringProperty(this, "password", "");
   }

   public final String getUserName() {
       return userName.get();
   }
   public ReadOnlyStringProperty userNameProperty() {
       return userName.getReadOnlyProperty();
   }

   public final String getPassword() {
       return password.get();
   }
   public StringProperty passwordProperty() {
       return password;
   }
}

我們建立了兩個UI控制元件,一個用於使用Text控制元件顯示使用者名,另一個是 PasswordField 欄位控制元件,它將輸入值系結到域物件(User)中的 password 欄位。

Text userName = new Text();
userName.textProperty().bind(user.userNameProperty());

PasswordField passwordField = new PasswordField();
passwordField.setPromptText("Password");
user.passwordProperty().bind(passwordField.textProperty());

在更改偵聽器中為passwordField欄位文字值屬性設定BooleanProperty存取許可權。

passwordField.textProperty().addListener((obs, ov, nv) -> {
    boolean granted = passwordField.getText().equals(MY_PASS);
    accessGranted.set(granted);
    if (granted) {
        primaryStage.setTitle("");
    }
});

enter鍵點選事件中存取BooleanProperty accessGranted

        // user hits the enter key
        passwordField.setOnAction(actionEvent -> {
            if (accessGranted.get()) {
                System.out.println("granted access:"+ user.getUserName());
                System.out.println("password:"+ user.getPassword());
                Platform.exit();
            } else {
              primaryStage.setTitle("no access"); 
            }
        });

完整的原始碼如下所示。

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.PasswordField;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

// @  w  Ww .yi  iB  A i. C o M
public class Main extends Application {
    private final static String MY_PASS = "passwd";// 初始密碼
    private final static BooleanProperty accessGranted = new SimpleBooleanProperty(false);

    @Override
    public void start(Stage primaryStage) {
        User user = new User();
        Group root = new Group();
        Scene scene = new Scene(root, 320, 100);
        primaryStage.setScene(scene);

        Text userName = new Text();
        userName.textProperty().bind(user.userNameProperty());

        PasswordField passwordField = new PasswordField();
        passwordField.setPromptText("Password");
        user.passwordProperty().bind(passwordField.textProperty());

        // user hits the enter key
        passwordField.setOnAction(actionEvent -> {
            if (accessGranted.get()) {
                System.out.println("granted access:" + user.getUserName());
                System.out.println("password:" + user.getPassword());
                Platform.exit();
            } else {// @  WW  w .yIIB  A  i.c OM
                primaryStage.setTitle("no access");
            }
        });

        passwordField.textProperty().addListener((obs, ov, nv) -> {
            boolean granted = passwordField.getText().equals(MY_PASS);
            accessGranted.set(granted);
            if (granted) {
                primaryStage.setTitle("");
            }
        });
        VBox formLayout = new VBox(4);
        formLayout.getChildren().addAll(userName, passwordField);
        formLayout.setLayoutX(12);
        formLayout.setLayoutY(12);

        root.getChildren().addAll(formLayout);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

class User {
    private final ReadOnlyStringWrapper userName;
    private StringProperty password;

    public User() {
        userName = new ReadOnlyStringWrapper(this, "userName", "ABC");
        password = new SimpleStringProperty(this, "password", "");
    }

    public final String getUserName() {
        return userName.get();
    }

    public ReadOnlyStringProperty userNameProperty() {
        return userName.getReadOnlyProperty();
    }

    public final String getPassword() {
        return password.get();
    }

    public StringProperty passwordProperty() {
        return password;
    }
}

上面的程式碼生成以下結果。

在上面的輸入框中輸入密碼:passwd 完成後,得到以下輸出結果 -

granted access:ABC
password:passwd