Cours BUT Info

Ch. 2-4 — Programmation événementielle

Événements (source, cible, type), abonnement et lambdas, route capture/bubbling, Canvas et CSS JavaFX — puis MVC avec ObservableList, ListView et ListChangeListener.

Événementiel vs procédural

En programmation procédurale, le déroulement est contrôlé par une séquence d'instructions écrites : le programmeur écrit lui-même la boucle principale — initialisations, puis répéter « lire une commande / traiter une commande » jusqu'à la commande finir. En programmation événementielle, le déroulement est contrôlé par la survenue d'événements : pas de boucle principale visible, le programme se réduit aux initialisations et aux fonctions de réaction.

Ces fonctions de rappel (callbacks) sont enregistrées dans le widget à sa création (abonnement) et appelées quand un événement le concernant survient (notification). La boucle existe toujours, mais enfouie dans la toolkit : initialisation ; while () { attendre l'événement suivant E ; traiter E ; }. Les événements — liés aux périphériques (entrée/sortie du curseur, boutons, frappes clavier, gestes multitouch, sélection d'un item) ou aux applications (création/destruction de fenêtres, ré-affichage) — sont placés dans une queue FIFO que la boucle de gestion vide un par un.

L'objet événement : source, cible, type

Un événement JavaFX est un objet qui hérite de javafx.event.Event. Tout événement a trois propriétés :

  • une source (event source) — le nœud d'où l'événement semble provenir ; elle change au fil de la route ;
  • une cible (event target, interface EventTarget) — le nœud visé, identique du début à la fin de la traversée ;
  • un type (event type, classe EventType) — par exemple mouse pressed, mouse released, mouse moved.

La classe de l'événement dépend de la signification logique de l'interaction, pas seulement du geste physique : un clic sur un bouton émet un ActionEvent, un clic sur un rectangle un MouseEvent, un clic sur le bouton de fermeture de fenêtre un WindowEvent. Un même ActionEvent couvre d'ailleurs plusieurs déclenchements physiques : clic souris, appui sur la touche espace, touch sur une surface tactile.

Abonnement et notification

Deux catégories d'entités : l'abonneur — la source d'information, le lieu où sont gérés les événements — et un ou plusieurs abonnés. L'abonnement passe par addEventHandler(EventType, EventHandler), où le type précise l'événement géré :

bplus.addEventHandler(ActionEvent.ACTION, new MyClicHandler());
// ...
public class MyClicHandler implements EventHandler<ActionEvent> {
    public void handle(ActionEvent event) {
        System.out.println("Clic sur bouton");
    }
}

EventHandler<T extends Event> est une interface à une seule méthode, handle(T event) — plusieurs handlers peuvent s'abonner au même nœud, ils seront tous notifiés dans l'ordre.

Cinq façons d'écrire un handler

Le cours les déroule toutes sur le même exemple (incrémenter/décrémenter un label) : classe externe par bouton, classe interne (elle accède aux membres, même privés, de sa classe englobante), classe interne anonyme (déclarée et instanciée en même temps), l'Application elle-même qui implémente EventHandler et s'abonne avec this, et enfin l'expression lambda :

bplus.setOnAction(e -> {
    int currentValue = Integer.parseInt(label.getText());
    label.setText("" + (currentValue + 1));
});

Une lambda n'est possible que pour une interface à une seule méthode, et ne peut capturer que les variables du contexte englobant dont la valeur ne change pas (effectively final) : une variable de boucle for est refusée à la compilation.

Recommandations du cours

Privilégier les méthodes setOnXXX (setOnAction, setOnMouseClicked, setOnMouseDragged, setOnKeyPressed, setOnTouchPressed…) avec des lambdas quand le code est court ; ne pas utiliser de classes internes anonymes ; une classe interne ou l'implémentation de EventHandler si le code grandit, une classe externe encore au-delà. Et toujours privilégier la lisibilité.

La route d'un événement

Quand un événement est généré : sélection de la cible, construction de la route (la chaîne Stage → Scene → conteneurs → cible), puis traversée de la route en deux phases. À la capture, l'événement descend de la tête (Stage) à la queue (cible) et appelle les event filters ; au bubbling, il remonte de la cible vers la tête en appelant les event handlers. Un parent intercepte ainsi les événements destinés à ses enfants : un filtre posé sur le Stage voit tous les clics de la fenêtre.

event.consume() consomme l'événement et stoppe son traitement : les autres filtres ou handlers du nœud courant s'exécutent encore, mais l'événement n'atteint plus aucun autre nœud — c'est ainsi qu'une cible fournit une réponse spécifique avant la réponse par défaut du parent.

Dessiner et styliser

Canvas + GraphicsContext : tout est dessiné dans le contexte graphique — l'ensemble des paramètres (couleur des traits, de remplissage, police courante, changements de repère) appliqués au dessin du nouvel élément — obtenu par canvas.getGraphicsContext2D(). Primitives : fillRect, fillOval, fillArc, strokeRect, strokeOval, strokeArc, strokeLine, drawImage, fillText… avec l'origine du repère au coin supérieur gauche.

Le style reprend le principe des CSS du Web : des règles « sélecteur + couples propriété-valeur », aux propriétés préfixées -fx- (-fx-background-color, -fx-text-fill…). En ligne : bouton.setStyle("-fx-text-fill: red;") ; ou feuille externe : scene.getStylesheets().add("cours3/bouton.css").

MVC et ListView

MVC (Modèle-Vue-Contrôleur) : l'idée est de séparer les données d'une application de leur présentation. Le modèle informe la ou les vues de se mettre à jour, sans connaître aucune vue spécifique ; c'est un mécanisme d'abonnement (observateur) qui relie modèle et vues.

En JavaFX, le modèle d'une ListView est une ObservableList : list.getItems().add("Rome") met la vue à jour, sans toucher au widget. Pour réagir aux changements, on s'abonne avec addListener et un ListChangeListener, notifié dans onChanged(Change report). La sélection a son propre modèle : list.getSelectionModel() — setSelectionMode(SelectionMode.MULTIPLE) pour la sélection multiple, et un listener sur getSelectedItems(), une seconde ObservableList. Au total, une ListView possède deux modèles (ObservableList) et une vue.

A retenir

  • la boucle événementielle est enfouie dans la toolkit ; on écrit des callbacks, enregistrées par abonnement et appelées par notification ;
  • tout événement a une source (qui change), une cible (fixe) et un type ; la classe dépend de la signification logique (ActionEvent ≠ MouseEvent) ;
  • setOnXXX + lambda : la forme par défaut ; lambdas réservées aux interfaces à une seule méthode, captures effectively final ;
  • capture (filters) → cible → bubbling (handlers) ; consume() stoppe la route ;
  • MVC : le modèle (ObservableList) prévient, la vue (ListView) obéit — jamais l'inverse.

R2.02 - Cours 2 - Programmation événementielle.pdf

R2.02 · Cours 2 — Programmation événementielle · 33 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

R2.02 - Cours 3 - Composants et événements (12).pdf

R2.02 · Cours 3 — Composants et événements (1/2) · 31 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

R2.02 - Cours 4 - Composants et événements (22).pdf

R2.02 · Cours 4 — Composants et événements (2/2) : MVC et ListView · 16 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page