JavaFX Tutorial - JavaFX Binding








The JavaFX binding synchronizes two values: when a dependent variable changes, the other variable changes.

To bind a property to another property, call the bind() method which binds values in one direction. For instance, when property A binds to property B, the change in property B will update property A, but not the other way around.

Binding options

JavaFX provides many binding options to synchronize between properties in domain objects and GUI controls.

we can use the following three binding strategies with in JavaFX's Properties API:

  • Bidirectional binding on a Java Bean
  • High-level binding with the Fluent API
  • Low-level binding with binding objects defined in javafx.beans.binding.*




Bidirectional Binding

Bidirectional binding binds the same type properties and synchronizes the a value on both side.

When binding bi-directionally with the bindBidirectional() method, it's required that both properties must be read/writable.

The following code shows how to do bidirectional binding between the firstName Property and a string property variable

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
//w  w w.  j  a v  a  2s.  c  o  m
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("New First Name");

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

  }
}
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);
  }
}

The code above generates the following result.





High-level Binding

We can also use JavaFX fluent API to bind properties. The fluent APIs perform operations on properties using English-like method names. For example, multiply(), divide(), subtract(), isEqualTo(), isNotEqualTo(), concat(). Be aware that there is no get or set in the method name. When chaining the fluent API together we can write code as if we are writing sentence, for example, width().multiply(height()).divide(2).

The following code shows how to create a property that represents the formula for calculating the area of a rectangle.

It does the high-level binding by using the fluent API from the javafx.beans.binding.IntegerExpression interface.

The code uses the multiply() method, which returns a NumberBinding containing the computed value.

This binding is lazy-evaluated, which means the multiplying doesn't occur unless we invoke the property's value via the get() or getValue() method.

import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
//  w  w  w.  j a  v  a 2s  .co  m
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());
  }
}

The code above generates the following result.

Low-Level Binding

We use the low-level binding when subclassing NumberBinding class, such as a DoubleBinding class for values of type Double.

Within the subclass of DoubleBinding class we override its computeValue() method so that we can use the operators such as * and - to formulate complex math equations.

High-level binding uses methods such as multiply(), subtract() while low-level binding uses operators such as * and -.

The following code shows how to create a low-level binding for the formula to calculate the area of a rectangle.

import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
//www  . j ava2 s .  c  om
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());
  }
}

The code above generates the following result.

Binding between UI Control and Domain model

Data binding between UI control and domain model is easy in JavaFX. The following code shows how to create a login dialog and bind User domain object.

First we define the domain object which is a JavaFX JavaBean describing the user'name and password.

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;
   }
}

The we created two UI controls, one is for desplaying the user name with Text control, and the other is a PasswordField controls which binds the input value to the password field in the domain object.

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

The BooleanProperty accessGranted is set in the change listener for passwordField's text value property.

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

The BooleanProperty accessGranted is accessed in the enter key hit event.

        
        // 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"); 
            }
        });

Full source code.

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.  j a  v a 2 s . co m*/
public class Main extends Application {
    private final static String MY_PASS = "asdf";
    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 {
              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;
   }
}

The code above generates the following result.

null