• 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 24/04/2019

Interagissez avec vos composants

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

Dans le précédent chapitre nous avons appris à lier notre interface graphique et nos données mais sans interaction : le détail de nos objets ne s'affiche pas dans le volet de droite si nous sélectionnons une ligne dans le tableau, il n'est pas possible de créer une nouvelle personne ni d'en modifier une existante et encore moins en supprimer une. Voilà donc le synopsis de ce chapitre, à la fin, tout ceci sera implémenté.

Afficher le détail de nos objets Personne

Nous allons débuter en finalisant notre affichage afin que celui-ci soit entièrement fonctionnel. Comment procédé, vous devez en avoir une vague idée puisque vous maîtrisez la programmation événementielle. Oui, nous devons mettre en place un système un écouteur sur la sélection de ligne de notre tableau afin que celui-ci récupère la ligne sélectionnée et affiche toutes les informations de l'objet dans le volet de droite.
Pour commencer, nous allons définir une méthode qui renseigne ces informations dans la classe  PersonneMapping  . La voici :

//Méthode qui va mettre les valeurs de notre objet dans les composants
public void initializeDescription(Personne p) {
	//On réinitialise par défaut
	nomValeur.setText("");
	prenomValeur.setText("");
	dateValeur.setText("");
	sexeValeur.setText("");
	
	//Si un objet est passé en paramètre, on modifie l'IHM
	if(p != null) {
		//ATTENTION : les accesseurs retournent des objets Property Java FX
		//Pour récupérer leurs vrais valeurs vous devez utiliser la méthode get()
		nomValeur.setText(p.getNom().get());
		prenomValeur.setText(p.getPrenom().get());
		//Sur les deux champs si dessous, en plus de get()
		//vous devez utiliser toString() car ce sont des objets particuliers
		dateValeur.setText(p.getDateDeNaissance().get().toString());
		sexeValeur.setText(p.getSexe().get().toString());
	}
}

Aucune difficulté particulière ici si ce n'est que notre objet Personne ne retourne pas des String ou des  LocalDate  directement mais des objets  Property  Java FX, il faut donc utiliser un accesseur supplémentaire pour accéder à la vrai valeur. Maintenant que nous avons cette méthode, nous allons pouvoir l'utiliser dans un écouteur de notre tableau. Nous ajoutons donc une action à faire lors de la sélection d'une ligne grâce à une ligne supplémentaire dans la méthode  initialize()  de la classe  PersonneMapping   :

private void initialize() {
    // Initialise le tableau.
    nomColonne.setCellValueFactory(cellData -> cellData.getValue().getNom());
    prenomColonne.setCellValueFactory(cellData -> cellData.getValue().getPrenom());
    
    //Nous récupérons le model de notre tableau (vous connaissez maintenant)
    //où nous récupérons l'item sélectionné et où nous y attachons un écouteur
    //Qui va utiliser notre méthode de mise à jour d'IHM
    personneTable.getSelectionModel().selectedItemProperty().addListener(
            (observable, oldValue, newValue) -> initializeDescription(newValue));
}

Expliquons un peu plus la mise en place de l'écouteur. Nous récupérons donc le modèle de notre tableau puis, via la méthode   selectedItemProperty()  , nous obtenons un objet  ReadOnlyObjectProperty<Personne>  (notre objet Personne sous forme de property Java Fx, avec des mécanismes d'écouteurs). Cet objet peut être écouté grâce à sa méthode  addListener(ChangeListener< ? Super T> listener)  . Cette interface est une interface fonctionnelle car elle n'a qu'une méthode, changed() qui prend trois paramètres : un observable, l'ancienne valeur et la nouvelle valeur. Nous ne nous servons que de la dernière valeur car nous nous contentons de mettre à jour de l'affichage et non des données du modèle.
Vous pouvez maintenant tester, vous devriez avoir ceci :

Chargement des données dans l'IHM
Chargement des données dans l'IHM

Suppression d'une personne

L'ajout d'une action à un bouton peut se faire dans SceneBuilder directement, il suffit de coder une méthode dans notre classe  PersonneMapping  et de l'annoter avec   @FXML  . Il nous sera alors possible de choisir cette méthode lors du clic sur le bouton.

Important : Je ne l'ai pas mentionné avant mais l'annotation  @FXML  est facultative pour les méthodes présente dans notre classe sont publique, mais il convient de renseigner cette annotation afin de facilité la lecture du code à postériori et ne pas confondre des méthodes utilisées dans le code et celles utilisées avec FXML.

Nous allons donc créer une méthode qui supprime une entrée de notre liste, elle est très simple, regardez plutôt :

@FXML
public void supprimerPersonne() {
	int index = personneTable.getSelectionModel().getSelectedIndex();
	//Si aucune ligne n'est sélectionnée, index vaudra -1
	if (index > -1) {
		personneTable.getItems().remove(index);
	}
}

Vous pouvez maintenant aller dans SceneBuilder et mapper cette méthode au bouton « Supprimer » dans l'onglet « Code » en sélectionnant cette méthode dans la liste déroulante ci-dessous :

Mapping de la méthode de suppression et le bouton
Mapping de la méthode de suppression et le bouton

Vous aurez remarqué que nous n'avons pas nommé notre bouton, c'est parce que celui-ci n'est pas appelé depuis notre code, nous n'interagissons pas avec lui dans notre code (pour modifier son statut, sa taille, sa couleur, …).
Sauvegardez votre projet SceneBuilder, rendez-vous dans Eclipse, mettez à jour votre projet (clic sur le projet puis F5) et vous pouvez maintenant relancer l'application. Vous obtiendrez normalement ceci après suppression de la première entrée du tableau :

La suppression en action !
La suppression en action !

Nous avons bien implémenté la fonctionnalité mais, étant tatillon, en tant qu'utilisateur j'aimerais assez être prévenu que la suppression n'est pas possible car aucune ligne n'est sélectionnée dans le tableau. Nous pouvons facilement rajouter un message à l'utilisateur avec une popup. Voici comment faire :

@FXML
public void supprimerPersonne() {
	int index = personneTable.getSelectionModel().getSelectedIndex();
	//Si aucune ligne n'est sélectionnée, index vaudra -1
	if (index > -1) {
		personneTable.getItems().remove(index);
	}
	else {
		Alert probleme = new Alert(AlertType.ERROR);
		probleme.setTitle("Erreur");
		probleme.setHeaderText("Veuillez sélectionnez une ligne dans le tableau");
		probleme.showAndWait();
	}
}

Bon, inutile de commenter ceci, c'est très simple. Voici ce que nous donne ce code :

Ajout d'une popup d'information
Ajout d'une popup d'information

Bon. Jusqu'ici nous avons fait le plus simple. Les deux dernières fonctionnalités à implémenter vont demander plus de travail vous vous en doutez. Nous allons devoir concevoir des fenêtre ou boîte de dialogue modale pour créer ou ajouter une personne ainsi que tous les traitements et les liens entre FXML et application. Allez, c'est parti !

Dernière ligne droite : ajout et édition et fermeture de l'application

Nous allons commencer par le plus simple, la fermeture de l'application grâce au menu que nous avons créé au tout début de cette partie. Nous allons donc créer une classe pour mapper notre menu et notre application.

Voici la classe que j'ai créé pour piloter nos menu. Vous pourrez remarqué que j'ai déjà créé un squelette de méthode pour le menu « Nouveau » :

package fr.testfx.personnes.view;

import fr.testfx.personnes.MainClass;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;

public class PersonneMenuMapping {
    //Objet servant de référence à notre classe principale
    //afin de pouvoir récupérer le Stage principal.
	//et ainsi fermer l'application
    private MainClass main;
    
    //Méthode qui sera utilisée dans l'initialisation de l'IHM
    //dans notre classe principale
    public void setMainApp(MainClass mainApp) {
        this.main = mainApp;
    }
    
	//Fermer l'application
	@FXML
	public void fermer() {
		//On affiche un message car on est poli.
		Alert bye = new Alert(AlertType.INFORMATION);
		bye.setTitle("Au revoir !");
		bye.setHeaderText("See you soon...");
		bye.setContentText("Et merci d'avoir suivi ce cours");
		bye.showAndWait();
		
		//Et on clos le stage principal, donc l'application
		this.main.getStage().close();
	}
	
	@FXML
	public void nouveau() {
		
	}	
}

Ensuite, nous n'avons plus qu'à renseigner le champs « Controller > Controller class » dans SceneBuilder et spécifier les actions de chaque point de menu et les sélectionnant dans l'onglet « Hiérarchy » puis en choisissant la méthode adéquate dans « Code > On Action ». Personnellement, étant un grand fan de raccourcis clavier, j'en ai profité pour ajouter des accélérateurs sur nos deux points de menu.

Ajout d'accélérateur pour nos menus
Ajout d'accélérateur pour nos menus

Il ne nous reste plus qu'à modifier la méthode qui initialise le conteneur principal dans notre classe principale afin de lui dire que maintenant elle doit utiliser un contrôleur pour ses actions. Il n'y a que deux lignes de code à rajouter... Voici la méthode en question avec les modifications :

private void initialisationConteneurPrincipal() {
	//On créé un chargeur de FXML
	FXMLLoader loader = new FXMLLoader();
	//On lui spécifie le chemin relatif à notre classe
	//du fichier FXML a charger : dans le sous-dossier view
	loader.setLocation(MainClass.class.getResource("view/ConteneurPrincipal.fxml"));
	//*
	try {
		//Le chargement nous donne notre conteneur
		conteneurPrincipal = (BorderPane) loader.load();
		System.out.println(conteneurPrincipal);
		//On définit une scène principale avec notre conteneur
		Scene scene = new Scene(conteneurPrincipal);
		//Que nous affectons à notre Stage
		stagePrincipal.setScene(scene);
		
		//Initialisation de notre contrôleur
		PersonneMenuMapping controleur = loader.getController();
		//On spécifie la classe principale afin de pour récupérer le Stage
		//Et ainsi fermer l'application
		controleur.setMainApp(this);
		
		stagePrincipal.show();
		
	} catch (IOException e) {
		e.printStackTrace();
	}
}

Vous pouvez maintenant tester et voir que tout fonctionne.

Fermeture de l'application
Fermeture de l'application

Passons maintenant à la création d'une boîte de dialogue « maison » qui va accueillir les champs à renseigner pour créer ou modifier une nouvelle personne. Nous allons traiter les deux fonctionnalités en même temps, enfin pas tout à fait, disons que nous allons nous servir de la même popup mais, avant de continuer, nous allons refaire le point sur ce que nous devons faire pour que cette soit opérationnelle :

  • créer une popup customisée contenant tous les champs nécessaire à l'édition et la création d'une personne ainsi que deux boutons, un qui valide la saisie un autre qui annule;

  • donner un nom à tous les champs de formulaire ;

  • ajouter une méthode qui va gérer l'affichage de la popup et la lier avec son contrôleur (dans  MainClass  ).

  • créer une classe qui va mapper les champs de l'IHM avec l'application pour pouvoir faire les contrôles et les traitements adéquats.

  • dans cette classe, créer une méthode qui va contrôler la validité de nos champs ;

  • toujours dans cette classe, créer une méthode pour la validation et une méthode pour l'annulation ;

  • spécifié dans SceneBuilder que cette classe est le contrôleur de l'IHM et renseigner les méthodes à utiliser sur nos boutons.

  • Et enfin modifier la méthode liée au menu « Nouveau » de notre barre de menu.

Toujours dans le package contenant nos vues, je vous invite à créer un nouveau fichier FXML avec le nom de votre choix avec un  AnchorPane  en tant qu'élément racine (comme nous l'avons fait pour notre toute première fenêtre en fait).
Ensuite, ajoutez-y un  GridPane  de cinq lignes, des Labels pour le noms des champs de notre formulaire puis des  TextField  pour le nom et le prénom, un  DatePicker  pour la date de naissance, une liste déroulante pour le sexe et enfin deux boutons en bas, un pour valider et un autre pour annuler.
Pour que tout soit aligné dans l'IHM, j'ai fait en sorte que mes champs de formulaire prennent tout l'espace disponible dans la grille, comme nous l'avons déjà fait mais pour centrer les boutons c'est un peu différents : vous devez modifier l'alignement de la ligne du tableau (propriété Valignment dans l'onglet layout à  CENTER  ) et des deux colonnes (propriété Halignment à  CENTER  également). Ce qui me donne au final :

Popup personnalisée pour l'ajout d'une personne
Popup personnalisée pour l'ajout d'une personne

Pensez également à donner une propriété fx:id à vos champs de formulaires.

Comme nous l'avons déjà fait précédemment, nous allons maintenant créer une classe qui va faire le lien entre notre IHM et notre programme. Créez une nouvelle classe  PersonneDialogueMapping  , dans le même package que le fichier FXML. C'est cette classe qui va gérer les événements de la popup : annulation ou validation, voici son code :

package fr.testfx.personnes.view;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import fr.testfx.personnes.MainClass;
import fr.testfx.personnes.model.Personne;
import fr.testfx.personnes.model.Sexe;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class PersonneDialogueMapping {

	private Stage stageDialogue;
	@FXML
	private TextField nomFormulaire;
	@FXML
	private TextField prenomFormulaire;
	@FXML
	private DatePicker dateFormulaire;
	@FXML
	private ComboBox<Sexe> sexeFormulaire;
	
	private MainClass main;	
	private Personne personne;
	
	public void setMainClass(MainClass m) {
		main = m;
		stageDialogue = main.getStage();
	}
	
	//On initialise ici les valeurs de la liste déroulante
	//avant de sélectionner la valeur de la personne
	public void initialize() {
		sexeFormulaire.getItems().setAll(Sexe.values());
	}
	
	//Afin de récupérer le stage de la popup
	//et pouvoir la clore
	public void setStage(Stage s) {stageDialogue = s;}
	
	public void setPersonne(Personne p) {
		personne = p;
		nomFormulaire.setText(personne.getNom().get());
		prenomFormulaire.setText(personne.getPrenom().get());
		dateFormulaire.setValue(personne.getDateDeNaissance().get());
		sexeFormulaire.getSelectionModel().select(personne.getSexe().get());
	}
	
	//Méthode de contrôle de la validité des données saisies
	private boolean controlerFormulaire() {
		boolean isOk = true;
		List<String> messageErreur = new ArrayList<>();
		if (nomFormulaire.getText() == null || nomFormulaire.getText().isEmpty()) {
			isOk = false;
			messageErreur.add("Le champ \"Nom\" est obligatoire");
		}
		if (prenomFormulaire.getText() == null || prenomFormulaire.getText().isEmpty()) {
			isOk = false;
			messageErreur.add("Le champ \"Prénom\" est obligatoire");
		}	
		if (dateFormulaire.getValue() == null || dateFormulaire.getValue().toString().isEmpty()) {
			isOk = false;
			messageErreur.add("Le champ \"Date\" est obligatoire");
		}
		
		if(!isOk) {
			Alert erreur = new Alert(AlertType.ERROR);
			erreur.setTitle("Erreur ! ");
			StringBuilder sb = new StringBuilder();
			messageErreur.stream().forEach((x) -> sb.append("\n" + x));
			erreur.setHeaderText(sb.toString());
			erreur.showAndWait();
		}		
		return isOk;
	}
	
	@FXML
	public void annuler() {
		//On ferme la boîte de dialogue
		stageDialogue.close();
	}
	
	//sauvegarde de la personne, que ce soit une édition ou une création
	public void sauvegarder() {
		if(controlerFormulaire()) {
			personne.setNom(new SimpleStringProperty(nomFormulaire.getText()));
			personne.setPrenom(new SimpleStringProperty(prenomFormulaire.getText()));
			
			//Afin de pouvoir gérer la modification de date à la souris
			//ou en modifiant le texte du composant directement
			//On récupère la date au format texte pour la réinjecter 
			//dans le composant
			dateFormulaire.setValue(
					dateFormulaire	.getConverter()
									.fromString(
											//Date du composant au format texte
											dateFormulaire.getEditor().getText()
									)
								);
			
			personne.setDateDeNaissance(new SimpleObjectProperty<LocalDate>(dateFormulaire.getValue()));
			personne.setSexe(new SimpleObjectProperty<Sexe>(sexeFormulaire.getValue()));

			//S'il s'agit d'une création, on ajoute la personne dans le tableau
			if(stageDialogue.getTitle().startsWith("Création")) {
				main.getListDePersonne().add(personne);
			}

			//On ferme la boîte de dialogue
			stageDialogue.close();
		}
	}
}

Les commentaires se suffisent à eux-même.

L'étape suivante est la création d'une méthode, dans notre classe principale, qui affiche cette popup. Elle sera affichée soit lors de l'édition d'une personne soit lors de la création de celle-ci donc lorsque nous cliquons sur « éditer » dans le volet affichant les informations d'une personne, soit en cliquant sur le sous-menu « Nouveau » du menu « Fichier ». Afin de simplifier le traitement de ces deux fonctionnalité, nous utiliserons systématiquement un objet Personne pour charger les informations dans les champs de formulaire : cette méthode prendra donc un paramètre de type Personne. Afin de savoir facilement dans notre code s'il s'agit d'une création ou d'une édition, au moment de la sauvegarde notre regarderons le titre de notre popup qui sera différent en fonction de ce que l'on souhaite faire : le titre sera donc le deuxième paramètre de cette méthode. La suite, vous la connaissez : chargement du fichier FXML, récupération du contrôleur, etc.

Voici la méthode à rajouter dans la classe principale :

//Méthode qui va va afficher la popup d'édition
//ou de création d'une personne et initialiser son contrôleur
public void affichePersonneDialogue(Personne personne, String titre) {
    try {
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(MainClass.class.getResource("view/PersonneDialogue.fxml"));
        AnchorPane page = (AnchorPane) loader.load();
        
        // Création d'un nouveau Stage qui sera dépendant du Stage principal
        Stage stageDialogue = new Stage();
        stageDialogue.setTitle(titre);
        stageDialogue.initModality(Modality.WINDOW_MODAL);
        
        //Avec cette instruction, notre fenêtre modifiée sera modale
        //par rapport à notre stage principal
        stageDialogue.initOwner(stagePrincipal);
        Scene scene = new Scene(page);
        stageDialogue.setScene(scene);
        
        // initialisation du contrôleur
        PersonneDialogueMapping controller = loader.getController();
        //On passe la personne avec laquelle nous souhaitons travailler
        //une existante ou une nouvelle
        controller.setPersonne(personne);
        controller.setMainClass(this);
        
        // Show the dialog and wait until the user closes it
        stageDialogue.showAndWait();
        //return controller.isOkClicked();
    } catch (IOException e) {
    	e.printStackTrace();
    }
}

 Avant de mapper les méthodes que nous venons de faire et les classes dans le champs « Controller » de SceneBuilder, voici la dernière méthode qui permettra, elle, de créer une nouvelle personne :

@FXML
public void nouveau() {
	//On affiche la popup avec une personne inexistante
    this.main.affichePersonneDialogue(new Personne(), "Création d'une personne");	    
}

Rappelez-vous, celle-ci se trouve dans la classe qui gère les menus. Tout est maintenant en place. Pensez bien à enregistrer les modification dans SceneBuilder et à rafraîchir votre projet Eclipse avant de lancer l'application. C'est chose faite ? Alors vous devriez avoir la même chose que moi :
Voici quelques captures d'écran montrant l'édition et la création à l'oeuvre :

Modification d'une personne
Modification d'une personne
Mise à jour de l'interface après modification
Mise à jour de l'interface après modification
Ajout d'une nouvelle personne
Ajout d'une nouvelle personne
Après la création d'une personne
Après la création d'une personne

En résumé

  • Pour récupérer la valeur d'un objet de type  property  , il faut utiliser la méthode   get()  ou  toString()  .

  • Nous pouvons utiliser les lambdas pour redéfinir les méthodes écouteurs pour la gestion des événements graphiques.

  • Les classes qui font le mapping entre une IHM et le code source doivent être dans le même package que les fichier FXML.

  • Les méthodes que nous mappons à nos composants doivent être annotés avec @FXML pour que SceneBuilder puisse les voir.

  • Pensez à rafraichir régulièrement Eclipse en cas de modifications du fichier FXML dans SceneBuilder.

  • Il est très fortement déconseillé d'utiliser une classe Java pour plusieurs fichier FXML.

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