La propriété
cellValueFactory est donc quelque chose de très important pour la colonne : c'est via cette fabrique que l'on peut extraire les objets de types
T qui peuplent dans la colonne à partir des objets de type
V qui sont présents dans la table.
Prenons par exemple la classe
Truc suivante :
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| public class Truc {
////////////////////////////////////////////////////////////////////////////
// Propriétés JavaFX.
private final BooleanProperty visible = new SimpleBooleanProperty();
public final boolean isVisible() {
return visible.get();
}
public final void setVisible(boolean value) {
visible.set(value);
}
public final BooleanProperty visibleProperty() {
return visible;
}
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;
}
////////////////////////////////////////////////////////////////////////////
// Propriétés JavaBeans observables.
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public final void addPropertyChangeListener(String property, PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(property, listener);
}
public final void removePropertyChangeListener(String property, PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(property, listener);
}
private final void firePropertyChange(String property, Object oldValue, Object newValue) {
propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
}
private boolean opaque = false;
public boolean isOpaque() {
return opaque;
}
public void setOpaque(boolean value) {
final boolean oldValue = opaque;
opaque = value;
firePropertyChange("opaque", oldValue, opaque);
}
private String comment;
public String getComment() {
return comment;
}
public void setComment(String value) {
final String oldValue = comment;
comment = value;
firePropertyChange("comment", oldValue, comment);
}
////////////////////////////////////////////////////////////////////////////
// Autres
public boolean administrator;
public String email;
} |
Cette classe dispose de 3 ensembles bien distincts de propriétés :
- Des propriétés JavaFX :
- visible - un booléen.
- name - une chaine de texte.
- Des propriétés JavaBeans observables - c'est-à-dire que ces propriétés Java standard qui lèvent des évènements de type PropertyChangeEvent lorsqu'elles sont modifiées. Il est de plus possible d'enregistrer des écouteurs de type PropertyChangeListener sur notre objet de type Truc pour recevoir ces évènements.
- opaque - un booléen.
- comment - une chaine de texte.
- Des membres en accès direct - on aurait aussi pu mettre des getters couplés à des setters simples qui ne lèvent pas de PropertyChangeEvent en cas de modification.
- administrator - un booléen.
- email - une chaine de texte.
Nous allons maintenant essayer d'afficher ces différentes propriétés dans une
TableView<Truc>. Compte tenu de la nature de nos différentes propriétés, nous aurons des colonnes de type
TableColumn<Truc, Boolean> et
TableColumn<Truc, String>.
[...]
Il nous faut aussi bien sur rajouter les colonnes appropriées :
1 2 3 4 5 6 7 8
| // Construction de la table.
final TableColumn<Truc, Boolean> visibleColumn = new TableColumn<>("Visible");
final TableColumn<Truc, String> nameColumn = new TableColumn<>("Nom");
final TableColumn<Truc, Boolean> opaqueColumn = new TableColumn<>("Opaque");
final TableColumn<Truc, String> commentColumn = new TableColumn<>("Commentaires");
final TableColumn<Truc, Boolean> administratorColumn = new TableColumn<>("Administrateur");
final TableColumn<Truc, String> emailColumn = new TableColumn<>("Mél");
tableView.getColumns().setAll(visibleColumn, nameColumn, opaqueColumn, commentColumn, administratorColumn, emailColumn); |
[...]
Propriétés JavaFX
Quand on utilise des propriétés JavaFX, c'est bien simple : il n'y a pas grand-chose à faire ! Nous pouvons utiliser la classe j
avafx.scene.control.cell.PropertyValueFactory<V, T> pour créer une fabrique de propriété utilisant la réflexion. Et tout fonctionne directement !
1 2 3 4
| // Propriété visible.
visibleColumn.setCellValueFactory(new PropertyValueFactory<>("visible"));
// Propriété name.
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name")); |
Si les propriétés
visible et
name sont modifiées ailleurs que dans la table, alors cette dernière se met à jour automatiquement avec les nouvelles valeurs.
Propriétés JavaBeans observables
Au premier abord on peut penser qu'il suffit d'utiliser la classe
PropertyValueFactory sur les propriétés
opaque et
comment et si on essaie on verra que les valeurs correctes s'affichent. Mais en fait cela est trompeur : les valeurs initiales sont bien affichées ; cependant, en cas de modification hors de la table, cette dernière ne se met pas à jour ! Cela peut être gênant…
Nous allons devoir spécifier nos propres fabriques de valeurs pour ces deux propriétés et nous allons utiliser les builders présents dans l'API JavaFX qui permettent de créer un adaptateur entre les propriétés JavaBeans observables et les propriétés JavaFX :
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
| // Propriété opaque.
opaqueColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Truc, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Truc, Boolean> p) {
final Truc t = p.getValue();
try {
return JavaBeanBooleanPropertyBuilder.create().bean(t).name("opaque").build();
} catch (NoSuchMethodException ex) {
Logger.getLogger(MainFX4.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
});
// Propriété comment.
commentColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Truc, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Truc, String> p) {
final Truc t = p.getValue();
try {
return JavaBeanStringPropertyBuilder.create().bean(t).name("comment").build();
} catch (NoSuchMethodException ex) {
Logger.getLogger(MainFX4.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
}); |
En utilisant cet adaptateur, la table est prévenue de toutes modifications extérieures des propriétés
opaque et
comment et le contenu des cellules de la ligne change en conséquence.
Autres
Nous allons créer des propriétés pour mettre en place une Fr Façade destinée à convertir la valeur en provenance de l’objet vers une propriété JavaFX mais, ici il n’y a pas de secrets : ces membres ou propriétés ne sont pas observables ; il est totalement impossible d’être tenu au courant d’une modification externe. De la meme manière, si la table est éditable, une modification apportée sur la colonne n’est pas reportée sur l’objet.
Si on a à faire à des getters, on peut tenter l’utilisation de la classe
PropertyValueFactory. Sinon, il va falloir procéder a mano :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // Propriété administrator.
administratorColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Truc, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Truc, Boolean> p) {
final Truc t = p.getValue();
return new SimpleBooleanProperty(t.administrator);
}
});
// Propriété email.
emailColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Truc, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Truc, String> p) {
final Truc t = p.getValue();
return new SimpleStringProperty(t.email);
}
}); |
Nous avons enfin l'affichage complet de nos colonnes :
[...]
Booléens
L'API JavaFX propose la classe prête à l'emploi
javafx.scene.control.cell.CheckBoxTableCell qui peut être utilisée pour représenter des valeurs booléennes sous forme de case à cocher. De plus, ici, nous n'aurons même pas besoin d'écrire une nouvelle classe ou un nouveau callback puisque CheckBoxTableCell propose directement une fabrique statique. L'appel à la méthode
forTableColumn() permet de générer le callback qui sera chargé de créer les cellules de ces colonnes. Il nous suffit donc d'insérer dans notre code :
1 2 3
| visibleColumn.setCellFactory(CheckBoxTableCell.forTableColumn(visibleColumn));
opaqueColumn.setCellFactory(CheckBoxTableCell.forTableColumn(opaqueColumn));
administratorColumn.setCellFactory(CheckBoxTableCell.forTableColumn(administratorColumn)); |
Pour que notre affichage se dote de case à cocher dans les colonnes contenant des valeurs booléennes :
Édition
[...]
Pour qu’une valeur soit éditable dans la table, il faut que la table soit marquée comme éditable bien sur, mais aussi que la colonne le soit. Commençons donc par insérer les lignes suivantes dans notre code :
1 2 3 4 5 6 7
| visibleColumn.setEditable(true);
nameColumn.setEditable(true);
opaqueColumn.setEditable(true);
commentColumn.setEditable(true);
administratorColumn.setEditable(true);
emailColumn.setEditable(true);
tableView.setEditable(true); |
Et… et c’est tout en ce qui concerne les propriétés
visible et
opaque ! Il est en effet possible, dans la table, de cocher et décocher les cases [...]
Ce qui démontre que notre objet t reçoit bien les nouvelles valeurs booléennes qui ont été éditées dans la table.
Par contre, les colonnes contenant les valeurs des propriétés
name,
comment et
email ne semblent pas vouloir être éditées : si on essaie de cliquer ou de double-cliquer sur le contenu de la cellule, rien ne se passe. Le problème se situe en fait au niveau de la cellule par défaut qui est produite : elle ne supporte pas l’édition. Ici aussi, l’API JavaFX vient à notre rescousse, nous allons utiliser la classe prête à l’emploi
javafx.scene.control.cell.TextFieldTableCell. Cette classe permet d’utiliser un champ de saisie texte lors du passage en mode édition. Comme précédemment, il suffit d’appeler la méthode statique
forTableColumn() pour disposer d’un callback prêt a l’emploi qui servira de fabrique de cellules. Insérons les lignes suivantes dans notre code :
1 2 3
| nameColumn.setCellFactory(TextFieldTableCell.<String>forTableColumn());
commentColumn.setCellFactory(TextFieldTableCell.<String>forTableColumn());
emailColumn.setCellFactory(TextFieldTableCell.<String>forTableColumn()); |
Au lancement, aucun changement n'est discernable, la table est totalement identique à ce que nous avions précédemment.
Cependant si on double clique sur une des colonnes contenant du texte, désormais, la cellule va basculer en mode édition.
Les valeurs éditées dans les colonnes contenant les propriétés
name et
comment sont immédiatement reportées vers l'objet t une fois la validation effectuée [...]
Les propriétés
administrator et
email vont demander un peu plus de travail ! Bien qu'il soit possible d'éditer le contenu de la table, les modifications ne sont pas reportées vers l'objet
t.
En ce qui concerne la propriété
email, nous allons utiliser un callback sur la colonne. En effet, il nous suffit de placer un callback sur la propriété
onEditCommit de la colonne : cette propriété est appelée lorsque l'utilisateur valide son édition. Nous pouvons donc récupérer la nouvelle valeur et la stocker dans notre objet
t :
1 2 3 4 5 6 7 8
| emailColumn.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Truc, String>>() {
@Override
public void handle(TableColumn.CellEditEvent<Truc, String> event) {
System.out.printf("Mél %s -> %s", event.getOldValue(), event.getNewValue()).println();
final Truc t = event.getRowValue();
t.email = event.getNewValue();
}
}); |
Désormais, la propriété
email est correctement mise à jour en cas d'édition !
On serait tenté de faire quelques choses de similaire avec la propriété
administrator. Cependant, si on le faisait, on se rendrait compte que le callback n'est jamais appelé. Le concepteur de
CheckBoxTableCell a en effet estimé qu'il n'était pas utile que ce composant passe en mode édition lors de son activation. Après tout lors d'un clic, le changement de valeur est immédiatement reporté vers la propriété source dans la colonne.
Nous allons donc pallier à ce problème en établissant un écouteur dans le code qui retourne une valeur observable à partir de notre objet
t.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // Propriété administrator.
administratorColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Truc, Boolean>, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Truc, Boolean> p) {
final Truc t = p.getValue();
final BooleanProperty result = new SimpleBooleanProperty(t.administrator);
result.addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue) {
t.administrator = newValue;
}
});
return result;
}
}); |
Désormais, en cas de modification de cette valeur dans la table, le membre correspondant dans l'objet est également correctement modifié !
Partager