• 40 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 20/03/2019

Liez un modèle à une vue

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce chapitre nous allons voir comment visualiser des donner dans notre IHM. Certains paradigmes Java sont devenu une référence avec Java FX, notamment la notion de Java Beans mais il y a aussi des nouveautés qui permettent de lier nos objets à notre vue ce qui permet que cette dernière soit automatiquement mise à jour si nous modifions des objets de notre couche « modèle » : on parle de binding d'objet.
Nous utiliserons une classe Personne qui représentera notre modèle de donnée et nous lierons tout ceci à notre interface graphique. Nous n'interagirons pas encore dans ce chapitre mais ça ne saurait tarder.

Java beans : rappel

Vous le savez déjà, Java a une convention de nommage de ses méthodes de classes etc. Un Java Bean est un objet Java qui doit respecter certaines contrainte, à savoir :

  • avoir un constructeur par défaut, sans paramètre ;

  • avoir des getters/setters pour toutes ses attributs ;

  • ces getters/setters doivent répondre à des contraintes de nommage : getXXX, setXXX, isXXXXXX représente le nom exact de l'attribut avec sa première lettre en majuscule. Par exemple, si une classe à un attribut appelé nom, nous devrions avoir :

    • getNom()  qui retourne le contenu de l'attribut « nom » ;

    • setNom()  qui permet de définir la valeur de l'attribut « nom » ;

    • isNom()  qui retourne un booléen.

  • peut être sérialisable ;

  • ne doit pas avoir de champs «  public  » ;

  • doit être une classe «  public  » ;

Les Java Beans ne font appel à aucune nouvelle notion mais sont contraints par un certain nombre de règles. Pourquoi ? Le meilleur argument vient du fait que ce genre d'objet est souvent utiliser via l'instrospection. De ce fait, avoir ces contraintes permet de grandement faciliter leur utilisation dynamique.

Je vous disais dans l'introduction que Java FX ajoute quelques contraintes supplémentaires à certains paradigmes, c'est le cas de ce type d'objet. Java FX arrive avec tout un tas de classes qui permettent de gérer au mieux ces propriétés d'objets et de facilité le lien avec l'interface graphique en la mettant à jour automatiquement. Toutes ces nouvelles classes et interfaces se trouvent dans le package   javafx.beans.property  et représentent tous les types de propriétés que vous pouvez utiliser et lier avec votre IHM. Il existe deux interfaces génériques :

  • Property<T>  : contient toutes les méthodes qu'il est possible d'utiliser sur un objet accessible en écriture, donc où nous pouvons utiliser un setter ;

  • ReadOnlyProperty<T>  : vous l'aurez deviné, dans celle-ci il y les méthodes d'accessibilité via des getters.

Ces deux interfaces implémentent également les mécanismes d'observation sur les objets (changement de valeur etc). Dans le package il existe une multitude de classes qui vont vous permettre de gérer vos propriétés de classe selon vos besoins. Il existe des classes abstraites pour les types de bases du langage à savoir :   BooleanProperty  ,  DoubleProperty  ,  FloatProperty  ,  IntegerProperty  ,  LongProperty  ,  ObjectProperty 
Toutes seront utilisées comme réceptacle de nos données et nous utiliserons leurs classes filles comme implémentations, par exemple nous pourrons avoir ce genre de propriété :

IntegerProperty age = new SimpleIntergerProperty(10) ;
ObjectProperty<LocalDate> dateDeNaissance = new SimpleObjectProperty<>(LocalDate.of(1979, 1, 1) ;

Il y a aussi des objets permettant de gérer des collections embarquant les mécanismes d'observation. Ces Collections se trouvent dans le package javafx.collections sont voici un descriptif :

  • ObservableList<E>  : liste permettant de tracer les modifications apportées à ses éléments ;

  • ListChangeListener<E>  : interface recevant les notifications lors de changement dans un objet de type  ObservableList<E>  ;

  • ObservableMap<K, V>  et  MapChangeListener<K, V  > : même but que les deux précédent mais pour des objets de type  Map<K, V>  ;

  • FXCollections   : objet contenant tout un tas de méthodes statiques permettant d'obtenir des objets mentionné ci-dessus et bien d'autres encore.

Maintenant que ces rappels sont fait, voici l'objet métier (modèle du pattern MVC) que nous allons utiliser tout au long de cette partie.

package fr.testfx.personnes.model;

public enum Sexe {
	MASCULIN("Masculin"),
	FEMININ("Féminin"),
	INCONNU("Inconnu");
	
	private String name = "";
	
	Sexe(String n){name = n;}
	public String toString() {return name;}
}


package fr.testfx.personnes.model;

import java.time.LocalDate;

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

public class Personne {
	private ObjectProperty<LocalDate> dateDeNaissance = new SimpleObjectProperty<>();
	private ObjectProperty<Sexe> sexe = new SimpleObjectProperty<>();
	private StringProperty nom = new SimpleStringProperty();
	private StringProperty prenom = new SimpleStringProperty();
	public Personne() {
		sexe.set(Sexe.INCONNU);
		nom.set("");
		prenom.set("");
		dateDeNaissance.set(LocalDate.of(0, 1, 1));
	}

	public Personne(String n, String p, LocalDate ddn, Sexe s) {
		nom.set(n);
		prenom.set(p);
		dateDeNaissance.set(ddn);
		sexe.set(s);;
	}
	public ObjectProperty<LocalDate> getDateDeNaissance() {return dateDeNaissance;}
	public void setDateDeNaissance(ObjectProperty<LocalDate> dateDeNaissance) {this.dateDeNaissance = dateDeNaissance;}
	public ObjectProperty<Sexe> getSexe() {return sexe;}
	public void setSexe(ObjectProperty<Sexe> sexe) {this.sexe = sexe;}
	public StringProperty getNom() {return nom;}
	public void setNom(StringProperty nom) {this.nom = nom;}
	public StringProperty getPrenom() {return prenom;}
	public void setPrenom(StringProperty prenom) {this.prenom = prenom;}
	public String toString() { return "#Nom : " + nom.get() + " - prénom : " + prenom.get() + "#";}
}

C'est donc un Java Bean classique mais qui utilise les classes permettant de mieux gérer les propriétés d'objet de Java FX. Afin de ne pas complexifier cette initiation a Java FX, nous allons afficher et manipuler des données qui seront codées directement dans une classe et stocker dans une collection.
Retournez dans la classe principale de notre projet et ajoutez-y ce code :

private ObservableList<Personne> listDePersonne = FXCollections.observableArrayList();
	
public MainClass() {
	listDePersonne.add(new Personne("Proviste", "Alain", LocalDate.of(1970, 1, 1), Sexe.MASCULIN));
	listDePersonne.add(new Personne("D'Arc", "Jeanne", LocalDate.of(1431, 5, 30), Sexe.FEMININ));
	listDePersonne.add(new Personne("Caisse", "Jean", LocalDate.of(1950, 3, 3), Sexe.MASCULIN));
}
	
public ObservableList<Personne> getListDePersonne(){return listDePersonne;}

Nous créons ici une liste grâce aux fonctionnalités mentionnées plus tôt, nous ajoutons ensuite des données dedans dans le constructeur de notre classe, que je viens de créer et enfin nous ajoutons également un accesseur afin de pouvoir récupérer cette liste. Toujours en respectant les contraintes expliqué dans le rappel.

Ceci fait, il nous reste deux étapes : donner un nom à nos composants graphiques dans SceneBuilder, créer une classe qui fera le lien entre ces noms et les propriétés de notre objet Personne.

Mapper vos composants graphiques et votre couche métier

Ici nous allons nommer tous les champs de l'interface graphique qui seront potentiellement manipulé depuis le code source de l'application. Ouvrez de nouveau SceneBuilder puis sélectionnez le Label qui contiendra le nom de notre objet puis allez dans l'onglet « Code » et renseignez le champ « fx:id » (Java FX Identifier), moi je l'ai appeler « nomValeur ». Renommez ainsi tous les «   fx:id  » des champs qui prendront des valeurs.
Pour l'objet  TableView  il faut renommer l'objet entier puis chaque colonne en les sélectionnant. Moi, j'ai renommer l'objet entier en  personneTable  , la colonne «  NOM   » en  nomColonne  et la dernière en  prenomColonne  .

Nous devons maintenant lié tout ces champs avec une classe Java qui se chargera de remplir les champs automatiquement. Par contre, il y a une contrainte, cette classe DOIT IMPERATIVEMENT se trouver dans le même package que le fichier FXML concerné. Nous allons créer une classe  PersonneMapping  dans le package contenant notre vue (le fichier FXML).

Voici le code source de cette classe :

package fr.testfx.personnes.view;

import fr.testfx.personnes.MainClass;
import fr.testfx.personnes.model.Personne;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;

public class PersonneMapping {
	@FXML
    private TableView<Personne> personneTable;
    @FXML
    private TableColumn<Personne, String> nomColonne;
    @FXML
    private TableColumn<Personne, String> prenomColonne;
    @FXML
    private Label nomValeur;
    @FXML
    private Label prenomValeur;
    @FXML
    private Label dateValeur;
    @FXML
    private Label sexeValeur;
    
    //Objet servant de référence à notre classe principale
    //afin de pouvoir récupérer la liste de nos objets.
    private MainClass main;

    //Un constructeur par défaut
    public PersonneMapping() { }

    //Méthode qui initialise notre interface graphique
    //avec nos données métier
    @FXML
    private void initialize() {
        // Initialize the Personne table with the two columns.
        nomColonne.setCellValueFactory(cellData -> cellData.getValue().getNom());
        prenomColonne.setCellValueFactory(cellData -> cellData.getValue().getPrenom());
    }

    //Méthode qui sera utilisée dans l'initialisation de l'IHM
    //dans notre classe principale
    public void setMainApp(MainClass mainApp) {
        this.main = mainApp;
        // On lie notre liste observable au composant TableView
        personneTable.setItems(main.getListDePersonne());
    }
}

Vous aurez sans doute remarqué que tous les champs que nous avons renommé dans SceneBuilder sont présent ici et annoté avec  @FXML  . Cette annotation permet de spécifié à quoi le fichier FXML pourra accéder. Ensuite nous déclarons un constructeur par défaut, convention oblige puis nous voyons une méthode   initialize()  . Cette méthode est appelée automatiquement après le chargement du fichier FXML, donc après le chargement de l'interface graphique.
À ce stade, tous les composants de l'interface sont initialisés et utilisables : nous pouvons donc les remplir en utilisant la méthode   setValueFactory()  qui va renseigner toutes les lignes de la colonne concernée avec la données que nous lui avons fourni, ici, le nom de notre personne pour la colonne nom et le prénom pour la deuxième colonne. Dans notre exemple nous affichons des chaînes de caractères dans notre tableau, si nous avions eut des nombres il aurait fallu ajouter en plus la méthode  asObject()  après notre accesseur.

Nous avons presque terminé, il ne nous reste plus qu'à mettre notre dernier point de colle : lié notre classe c-dessus avec l'application et spécifier dans le fichier FXML ou trouver cette classe automatiquement. Voici maintenant le code complet de notre méthode  initlisationContenu()  dans notre classe  MainClass.

private void initialisationContenu() {
	FXMLLoader loader = new FXMLLoader();
	loader.setLocation(MainClass.class.getResource("view/PersonView.fxml"));
	try {
		//Nous récupérons notre conteneur qui contiendra les données
		//Pour rappel, c'est un AnchorPane...
		AnchorPane conteneurPersonne = (AnchorPane) loader.load();
		//Qui nous ajoutons à notre conteneur principal
		//Au centre, puisque'il s'agit d'un BorderPane
		conteneurPrincipal.setCenter(conteneurPersonne);
		
		//Nous récupérons notre mappeur via l'objet FXMLLoader
		PersonneMapping controleur = loader.getController();
		//Nous lui passons notre instance de classe
		//pour qu'il puisse récupérer notre liste observable
		controleur.setMainApp(this);
		
	} catch (IOException e) {
		e.printStackTrace();
	}
}

Rouvrez votre fichier FXML et allez dans l'onglet « Controller » et sélectionnez notre classe dans la liste déroulante, comme ceci :

Lier votre IHM avec votre code
Lier votre IHM avec votre code

Si la liste déroulante n'est pas mise à jour, fermez SceneBuilder, sélectionnez votre projet et faites un F5 dans Eclipse puis relancez SceneBuilder : la liste devrait être à jour. Vous pouvez maintenant lancer l'application qui devrait vous donner :

données chargées dans l'application
Données chargées dans l'application

En résumé

  • Le mapping de données utilise des Java Beans.

  • Les Java Beans impose une liste de contrainte.

  • Le lien entre l'IHM et la couche métier se fait grâce à la propriétéfx:id des composants, leurs noms et l'annotation  @FXML  dans le code source.

  • Le nom fx:id doit être le même que le nom de la variable annotée côté code source.

  • Le lien final se fait dans la propriété Controller du fichier FXML en spécifiant la classe java qui fait le mapping de l'IHM.

Exemple de certificat de réussite
Exemple de certificat de réussite