JavaFX : Binding properties

Cet article est un rapide aperçu des properties de JavaFX.

Properties

Les propriétés de JavaFX empaquettent les valeurs.

Chaque type de classe à sa propriété.

Par exemple :

  • SimpleObjectProperty
  • SimpleStringProperty
  • SimpleIntegerProperty
  • ...

Pour rester conventionnel, utilisez des accesseurs et mutateurs en utilisant les fonctions get et set.

Enfin, créer un accesseur pour la propriété. Nommez-le en utilisant la convention de nom nomDeLaProprieteProperty.

Voici un exemple avec la classe Person avec une propriété name.

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

public class Person {

 private final StringProperty name = new SimpleStringProperty();

 public final String getName() {
  return name.get();
 }

 public final void setName(String value) {
  name.set(value);
 }

 public final StringProperty nameProperty() {
  return name;
 }

}

Binding Simple

Dans cet exemple j'utilise Platform.runLater et des threads pour modifier les labels au bout de quelques secondes.

Platform.runLater permet de rester dans un thread JavaFX.

Dès que setName est appelé, le label est rafraichi.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

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

   @Override
   public void start(Stage primaryStage) throws Exception {

 VBox root = new VBox();

 Label nameLabel = new Label();

 Person person = new Person();

 //bind le texte du label et le nom
 nameLabel.textProperty().bind(person.nameProperty());

 root.getChildren().add(nameLabel);

 Scene scene = new Scene(root);
 primaryStage.setScene(scene);
 primaryStage.show();

 new Thread(() -> {
  try {
     // 1 seconde d'attente
     Thread.sleep(1000);
         } catch (InterruptedException e) {
     e.printStackTrace();
  }
     Platform.runLater(() -> person.setName("Pierre"));
 }).start();

 new Thread(() -> {
   try {
   // 2 secondes d'attente
     Thread.sleep(2000);
   } catch (InterruptedException e) {
      e.printStackTrace();
   }
   Platform.runLater(() -> person.setName("Paul"));
 }).start();

 }

}

Conversions

Parfois, deux types de propriétés sont incompatibles entre elles pour le binding. On ne peut pas faire un binding sur une SimpleStringProperty et une IntegerStringProperty.

Il faut donc utiliser un convertisseur pour que les deux propriétés puissent être reliées (bind) entre elles.

StringConverter<Number> converter = new NumberStringConverter();
Bindings.bindBidirectional(ageLabel.textProperty(), person.ageProperty(), converter);

Notez qu'il est possible de créer un convertisseur personnalisé si vous ne trouvez pas votre bonheur dans l'API de base.

Dans cet exemple, nous allons enrichir la classe Person en lui ajoutant une propriéte age. Nous utiliserons un entier (Integer).

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

public class Person {

 private final StringProperty name = new SimpleStringProperty();

 public final String getName() {
  return name.get();
 }

 public final void setName(String value) {
  name.set(value);
 }

 public final StringProperty nameProperty() {
  return name;
 }

 private final IntegerProperty age = new SimpleIntegerProperty();

 public final Integer getAge() {
  return age.get();
 }

 public final void setAge(int value) {
  age.set(value);
 }

 public final IntegerProperty ageProperty() {
  return age;
 }

}

Comme énoncé ci-dessus nous utiliserons un convertisseur.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.NumberStringConverter;

public class Main extends Application {

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

   @Override
   public void start(Stage primaryStage) throws Exception {

 VBox root = new VBox();

 Label nameLabel = new Label();
 Label ageLabel = new Label();

 Person person = new Person();

 nameLabel.textProperty().bindBidirectional(person.nameProperty());

 StringConverter<Number> converter = new NumberStringConverter();

 Bindings.bindBidirectional(ageLabel.textProperty(), person.ageProperty(), converter);

 //Solution simple pour éviter le zéro à l'initialisation
 ageLabel.visibleProperty().bind(Bindings.when(
    person.ageProperty().isEqualTo(0))
    .then(false)
           .otherwise(true));

 root.getChildren().add(nameLabel);

 root.getChildren().add(ageLabel);

 Scene scene = new Scene(root);
 primaryStage.setScene(scene);
 primaryStage.show();

 new Thread(() -> {
  try {
     // 1 seconde
     Thread.sleep(1000);
  } catch (InterruptedException e) {
     e.printStackTrace();
  }
  Platform.runLater(() -> {
   person.setName("Pierre");
   person.setAge(25);
         });
 }).start();

 new Thread(() -> {
  try {
     // 2 secondes
            Thread.sleep(2000);
  } catch (InterruptedException e) {
     e.printStackTrace();
  }
  Platform.runLater(() -> {
   person.setName("Paul");
   person.setAge(30);
  });
 }).start();

}

}

La ligne suivante permet de faire un binding avec la propriété visible du label.

ageLabel.visibleProperty().bind(Bindings.when(
    person.ageProperty().isEqualTo(0))
        .then(false)
        .otherwise(true));

Si l'âge est égal à zéro, le label ne sera pas visible.

Commentaires