FAQ JavaFXConsultez toutes les FAQ

Nombre d'auteurs : 4, nombre de questions : 507, dernière mise à jour : 2 novembre 2016  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum JavaFX de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.

Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommairePropriétés (26)
précédent sommaire suivant
 

Une propriété est une valeur, un champ, dans un objet qui a ceci de spécifique qu'elle est observable : c'est-à-dire qu'elle émet des événements dès qu'elle est invalidée ou que sa valeur change. Ces événements peuvent être reçus par des observateurs qui ont placé un ou des écouteurs sur cette propriété.

Il est également possible de lier de diverses manières une propriété sur une ou plusieurs autres : quand la ou les propriétés sources sont modifiées, la valeur de la propriété cible est automatiquement réévaluée. C'est ce qu'on appelle le binding.

Bien que n'ayant absolument rien de graphique à la base, l'API de propriété constitue l'un des cœurs sur lesquels repose l'API JavaFX. Il est donc très important de bien comprendre ces notions avant d'essayer d'aborder l'utilisation de SceneGraph ou des nœuds graphiques.

JavaFX conserve la notation propre aux propriétés Java Beans donc vous accéderez aux propriétés d'un objet généralement via des getters et des setters comme vous en avez l'habitude. Cependant, il est de convention de fournir également une méthode supplémentaire qui permet d’accéder directement à l’objet propriété et ce, de manière à faciliter les opérations de binding ainsi que tout ce qui repose sur le mécanisme de la réflexion.

Récapitulons, pour une propriété JavaFX, nous avons :

  • un getter - ex. : public String getName() ;
  • un setter - ex. : public void setName(String value). Optionnel : on n'en fournit pas dans le cas d'une propriété en lecture seule ;
  • un accesseur de propriété - ex. : public StringProperty nameProperty().

Mis à jour le 3 septembre 2014 bouye

Quand la valeur d'une propriété change, cette dernière est marquée comme étant désormais non valide. Elle invalide également de fait toute autre propriété qui est liée sur elle via le binding. Les nouvelles valeurs de ces propriétés ne sont cependant pas calculées immédiatement : pour économiser des ressources, le calcul des nouvelles valeurs n'aura lieu que lors du prochain accès à la valeur de la propriété via un des getters.

Mis à jour le 4 septembre 2014 bouye

Le binding est le fait de lier une propriété d'un objet à une ou plusieurs propriétés qu'on aurait définies ailleurs. Imaginons par exemple une propriété B que nous lions à une autre propriété A. La présence d'une liaison entre A et B fait que la valeur de B dépend de la valeur de A soit par une copie directe de la valeur, soit par un calcul ou une transformation quelconque qui va créer une nouvelle valeur à partir de celle de A.



L'API JavaFX propose deux types d’implémentation de binding :

  • le binding de haut niveau - qui permet de faire du binding très simplement en utilisant les nombreuses classes et méthodes utilitaires fournies dans l'API JavaFX. Ceci peut cependant s’avérer gourmand en ressources et mémoire, voire pénible à écrire lorsqu'on veut établir des binding complexes (ex. : une équation contenant de nombreux variables et opérandes et qui se résoudrait automatiquement lorsqu'on saisit les valeurs de ses différents membres) ;
  • le binding de bas niveau - qui permet de créer de nouveaux types de binding qui ne sont pas pris en charge par l'API JavaFX tout en conservant de bonnes performances.

Mis à jour le 3 septembre 2014 bouye vbrabant

Il s'agit d'un binding à sens unique sans transformation aucune : si une propriété B est liée sur une propriété A alors si la valeur de A change, la valeur de B change également et est égale à la valeur contenue dans A. Tant que la liaison perdure, il n'est pas possible de changer la valeur de B directement : tout essai lèverait une exception.

Mis à jour le 3 septembre 2014 bouye

Il s'agit d'un binding dans les deux sens : si une propriété B est liée bidirectionnellement sur une propriété A alors si la valeur de A change, la valeur de B change également. Cependant si la valeur de B change, alors la valeur de A sera modifiée en retour. Ici aussi il n'y a pas de transformation de la valeur : les deux valeurs sont tout le temps égales entre elles.

Mis à jour le 3 septembre 2014 bouye

Nous allons définir une propriété name contenant le nom d'une personne.

Commençons par définir le stockage de cette propriété en utilisant les classes fournies par l’API JavaFX : StringProperty et son implémentation concrète SimpleStringProperty.

Code Java : Sélectionner tout
private final StringProperty name = new SimpleStringProperty(this, "name");

Ici, nous avons utilisé un des constructeurs disponibles dans la classe SimpleStringProperty pour conserver une référence sur le bean parent (optionnel) et pour donner un nom textuel à la propriété (également optionnel). Actuellement, la propriété contient la valeur null, mais nous aurions tout aussi bien pu utiliser le constructeur qui permet de spécifier une valeur initiale.

Nous définissions maintenant le getter qui permet de récupérer la valeur de la propriété :

Code Java : Sélectionner tout
1
2
3
public final String getName() { 
    return name.get(); 
}

Ici, nous nous contentons de retourner la valeur contenue dans la propriété.

Ainsi que le setter qui permet de modifier la valeur de la propriété :

Code Java : Sélectionner tout
1
2
3
public final void setName(final String value) { 
    name.set(value); 
}

Ici, nous nous contentons d'affecter la valeur passée en paramètre à l'objet propriété.

Et enfin, nous définissons l’accesseur à la propriété qui nous permettra de la manipuler ou de faire du binding :

Code Java : Sélectionner tout
1
2
3
public final StringProperty nameProperty() { 
    return name; 
}

Ici, nous nous contentons de retourner directement une référence sur l'objet propriété.

On peut remarquer que toutes les méthodes publiques sont marquées final pour rendre impossible toute surcharge par une classe fille. Il s'agit là d'une pratique très importante pour conserver l’intégrité de la propriété !

Il est en effet possible de changer la valeur de la propriété soit en appelant le setter, soit en invoquant les méthodes set() ou setValue() sur l'objet propriété lui-même. De même, il est possible de récupérer la valeur de la propriété soit en appelant le getter, soit en invoquant les méthodes get() ou getValue() sur l'objet propriété lui-même.

Si une classe fille pouvait surcharger l'une de ces trois méthodes, il serait possible en utilisant cette méthode modifiée de retourner ou de changer des valeurs de manière totalement indépendante de l'objet propriété lui-même, ce qui par exemple, casserait ou rendrait tout binding obsolète. Bref on perdrait toute intégrité sur la valeur de la propriété.

Finalement, nous avons :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
private final StringProperty name = new SimpleStringProperty(this, "name"); 
  
public final String getName() { 
    return name.get(); 
} 
  
public final void setName(final String value) { 
    name.set(value); 
} 
  
public final StringProperty nameProperty() { 
    return name; 
}

Mis à jour le 4 septembre 2014 bouye

Nous allons maintenant définir une propriété en lecture seule : c'est-à-dire qu'elle aura bien un getter, mais pas de setter et que l'objet propriété retourné ne permettra pas non plus de modifier la valeur contenue.

Nous allons créer une propriété age qui retourne l'âge de la personne. Comme nous pouvons calculer automatiquement l'âge en fonction de la date actuelle et de la date de naissance de la personne, il s'agit là typiquement d'une valeur que l'utilisateur ne doit pas pouvoir modifier.

Nous allons utiliser ici une classe wrapper : un objet qui empaquète une propriété et permet d'en avoir une version en lecture seule.

Code Java : Sélectionner tout
private final ReadOnlyIntegerWrapper age = new ReadOnlyIntegerWrapper(this, "age");

Ici la valeur initiale est à 0.

Nous définissions maintenant le getter :

Code Java : Sélectionner tout
1
2
3
public final int getAge() { 
    return age.get(); 
}

Jusque-là, aucun changement, tout fonctionne comme précédemment.

Nous n'avons pas de setter, donc il n'y a rien à écrire de ce côté-là.

Et maintenant l’accesseur à la propriété :

Code Java : Sélectionner tout
1
2
3
public final ReadOnlyIntegerProperty ageProperty() { 
    return age.getReadOnlyProperty(); 
}

La propriété retournée par la méthode getReadOnlyProperty() est en lecture seule : cela veut dire qu'elle ne dispose pas de méthodes set() et setValue() permettant de modifier sa valeur. Elle ne peut pas non plus être "bindée" sur une autre propriété (mais on peut toujours "binder" une autre propriété sur elle).

La valeur contenue dans l'objet wrapper quant à elle peut sans problème être modifiée en interne par notre objet pour détenir l'âge correct une fois les calculs effectués en invoquant directement les méthodes set() et setValue() sur le wrapper.

Finalement, nous avons :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
private final ReadOnlyIntegerWrapper age = new ReadOnlyIntegerWrapper(this, "age"); 
  
public final int getAge() { 
    return age.get(); 
} 
  
public final ReadOnlyIntegerProperty ageProperty() { 
    return age.getReadOnlyProperty(); 
}

Mis à jour le 4 septembre 2014 bouye

Pour ce genre de propriétés, nous allons commencer exactement comme avec les propriétés en lecture seule, excepté que nous allons cette fois-ci créer un setter qui se chargera de faire les vérifications sur les valeurs saisies.

Créons une propriété childNumber qui est le nombre d'enfants à charge d'une personne. De toute évidence, ce nombre ne peut pas être négatif ; il peut être positif ou égal à 0 mais jamais négatif. Ce nombre peut cependant varier avec le temps : naissance, adoption, enfant qui devient majeur, perte de garde légale, décès…

Reprenons les bases de notre propriété en lecture seule :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
private final ReadOnlyIntegerWrapper childNumber = new ReadOnlyIntegerWrapper(this, "childNumber ");  
  
public final int getChildNumber () {  
    return childNumber.get();  
}  
  
public final ReadOnlyIntegerProperty childNumberProperty() {  
    return childNumber.getReadOnlyProperty();  
}

Nous allons maintenant lui adjoindre un setter qui fera les vérifications d'usage :

Code Java : Sélectionner tout
1
2
3
4
5
6
public final void setChildNumber(final int value) throws IllegalArgumentException { 
    if (childNumber < 0) { 
        throw new IllegalArgumentException("Le nombre d'enfants à charge ne peut pas être négatif !");  
    } 
    childNumber.set(value); 
}

Nous pouvons désormais modifier la valeur via le setter, mais pas la propriété qui reste en lecture seule, et faire les vérifications de sécurité nécessaires avant de valider la modification de valeur.

Finalement, nous avons :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private final ReadOnlyIntegerWrapper childNumber = new ReadOnlyIntegerWrapper(this, "childNumber ");  
  
public final int getChildNumber () {  
    return childNumber.get();  
}  
  
public final void setChildNumber(final int value) throws IllegalArgumentException { 
    if (childNumber < 0) { 
        throw new IllegalArgumentException("Le nombre d'enfants à charge ne peut pas être négatif !");  
    } 
    childNumber.set(value); 
} 
  
public final ReadOnlyIntegerProperty childNumberProperty() {  
    return childNumber.getReadOnlyProperty();  
}

Mis à jour le 4 septembre 2014 bouye

Pour des raisons de performances et de gain en mémoire, il peut être intéressant de différer l'initialisation d'une propriété. Cette méthode peut être appliquée sur les propriétés qui sont peu souvent utilisées et part du principe qu'on a plus souvent l'occasion d'invoquer un getter qu'un setter ou un accesseur.

Ici nous allons stocker la propriété countryHouse, c'est-à-dire la résidence secondaire ou la maison à la campagne d'une personne.

Commençons par définir notre propriété :

Code Java : Sélectionner tout
private OjectProperty<House> countryHouse;

Ici, nous n'avons pas initialisé notre propriété, la référence a donc une valeur égale à null.

Maintenant, nous allons créer un getter :

Code Java : Sélectionner tout
1
2
3
public final House getCountryHouse() { 
  return (countryHouse == null) ? null : coutryHouse.get(); 
}

Ici, si notre propriété n'est pas initialisée, nous retournons une valeur par défaut (qui est la valeur null). Sinon, nous retournons la valeur contenue comme précédemment.

Maintenant, nous allons créer un setter de manière similaire :

Code Java : Sélectionner tout
1
2
3
public final void setCountryHouse(final House value) { 
    countryHouseProperty().set(value); 
}

Ici, nous récupérons une référence sur l'objet propriété et nous affectons la nouvelle valeur dans la propriété.

Maintenant, écrivons l'accesseur à la propriété :

Code Java : Sélectionner tout
1
2
3
4
5
6
public final OjectProperty<House> countryHouseProperty() { 
    if (countryHouse == null) { 
        countryHouse = new SimpleObjectProperty<>(this, "countryHouse"); 
    } 
    return coutryHouse; 
}

Ici, nous effectuons réellement l'initialisation de notre propriété.

Finalement, nous avons :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private OjectProperty<House> countryHouse; 
  
public final House getCountryHouse() { 
  return (countryHouse == null) ? null : coutryHouse.get(); 
} 
  
public final void setCountryHouse(final House value) { 
    countryHouseProperty().set(value); 
} 
  
public final OjectProperty<House> countryHouseProperty() { 
    if (countryHouse == null) { 
        countryHouse = new SimpleObjectProperty<>(this, "countryHouse"); 
    } 
    return coutryHouse; 
}

Mis à jour le 4 septembre 2014 bouye

Le nombre de classes dédiées au stockage des propriétés dans l'API JavaFX est important et peut donc être déroutant ou même décourager le programmeur débutant au premier abord.
Voici donc un tableau récapitulatif des diverses classes à utiliser selon le type de la valeur stockée :

Valeur à stocker Type à stocker Propriété - base Propriété - concret Propriété - lecture seule Propriété - wrapper Valeur initiale par défaut
Valeurs booléennes* boolean BooleanProperty SimpleBooleanProperty ReadOnlyBooleanProperty ReadOnlyBooleanWrapper false
Nombres entiers* int IntegerProperty SimpleIntegerProperty ReadOnlyIntegerProperty ReadOnlyIntegerWrapper 0
Nombres flottants* float FloatProperty SimpleFloatProperty ReadOnlyFloatProperty ReadOnlyFloatWrapper 0.0f
Nombres entiers longs* long LongProperty SimpleLongProperty ReadOnlyLongProperty ReadOnlyLongWrapper 0l
Nombres flottants en double précision* double DoubleProperty SimpleDoubleProperty ReadOnlyDoubleProperty ReadOnlyDoubleWrapper 0.0d
Chaines de caractères String StringProperty SimpleStringProperty ReadOnlyStringProperty ReadOnlyStringWrapper null
Objets quelconques T ObjectProperty<T> SimpleObjectProperty<T> ReadOnlyObjectProperty<T> ReadOnlyObjectWrapper<T> null
Listes observables ObservableList<T> ListProperty<T> SimpleListProperty<T> ReadOnlyListProperty<T> ReadOnlyListWrapper<T> null
Ensembles observables ObservableSet<T> SetProperty<T> SimpleSetProperty<T> ReadOnlySetProperty<T> ReadOnlySetWrapper<T> null
Tables de hachage observables ObservableMap<T> MapProperty<T> SimpleMapProperty<T> ReadOnlyMapProperty<T> ReadOnlyMapWrapper<T> [c]null[/i]
Il n'existe pas de classe de stockage dédiée pour les types byte, char et short.

*Étant donné que les valeurs sont stockées sous forme littérale, il n'est pas possible de stocker la valeur null. Si stocker une telle valeur est souhaitable, on se rabattra sur le type objet équivalent : par exemple ObjectProperty<Integer> au lieu de IntegerProperty.

Mis à jour le 4 septembre 2014 bouye

Si votre propriété Java Bean est observable, oui c'est possible. Le package javafx.beans.property.adapter contient des classes définissant des monteurs (builder) qui permettent de créer des adaptateurs entre le monde Java Bean et le monde JavaFX.

Une propriété Java Beans est observable quand il est possible d'enregistrer un écouteur de type java.bean.PropertyChangeListener sur cette propriété.

Par exemple, créons une classe Person, notre bean, disposant d'une propriété age :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
public class Person { 
  
  private int age = 30; 
  
  public int getAge() { 
    return age; 
  } 
  
  public void setAge(int age) { 
    this.age = age; 
  } 
}

Ici, la propriété age n'est pas observable : si on la modifie en appelant le setter, il n'y a aucune notification de sa modification. Nous pourrions donc rencontrer des cas de figure où des incohérences pourraient se produire ce qui casserait l’intégrité de la propriété.

Par contre, si on fait :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Person { 
  
  private int age = 30; 
  
  public int getAge() { 
      return age; 
  } 
  
  public void setAge(int age) { 
      int oldAge = this.age; 
      this.age = age; 
      listenerList.firePropertyChange("age", oldAge, age); 
  } 
  
  private PropertyChangeSupport listenerList = new PropertyChangeSupport(this); 
  
  public void addPropertyChangeListener(PropertyChangeListener listener) { 
      listenerList.addPropertyChangeListener(listener); 
  } 
  
  public void removePropertyChangeListener(PropertyChangeListener listener) { 
      listenerList.removePropertyChangeListener(listener); 
  } 
  
  public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { 
      listenerList.addPropertyChangeListener(propertyName, listener); 
  } 
  
  public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { 
      listenerList.removePropertyChangeListener(propertyName, listener); 
  } 
}

Dans ce cas, la propriété Java Bean age est devenue pleinement observable puisqu'il est possible d'y enregistrer un écouteur de type java.bean.PropertyChangeListener et puisque le code du setter se charge de lancer un événement de modification quand la valeur de la propriété change.

Il va falloir empaqueter la propriété Java Bean dans un wrapper pour la transformer en propriété JavaFX :

Code Java : Sélectionner tout
1
2
3
4
fianl Person person = new Person(); 
final JavaBeanIntegerProperty personAge = JavaBeanIntegerPropertyBuilder.create() 
                                    .bean(person).name("age") 
                                    .build();

Ici, si on modifie la propriété source Java Bean, son équivalent JavaFX est mis à jour et les événements JavaFX appropriés sont lancés. Si on modifie la propriété JavaFX, sa source Java Bean est mise à jour et l’événement Java est lancé.

Désormais, nous avons obtenu une propriété JavaFX pleinement fonctionnelle à partir d'une propriété Java Bean traditionnelle !

Mis à jour le 29 septembre 2014 bouye

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2017 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Responsables bénévoles de la rubrique Java : Mickael Baron - Robin56 -