~ / TIS3 / AL / tps / 1-statemachine /

Machine à états avec Java/Swing

© 2007—2015 - Renaud Blanch

Le but de ce TP est d'apprendre à programmer une interaction en utilisant des machines à états avec la boîte à outils Swing de Java. Vous allez réaliser l'interaction pour un visualisateur du graphe d'une fonction.

Squelette d'application

Récupérez les sources du squelette de l'application. Celles-ci fournissent dans le répertoire graphser/src/packages : grapher.fc et grapher.ui (respectivement le noyau fonctionnel et l'interface graphique de notre programme). Explorez le code pour comprendre ce qu'il fait.

Créez un nouveau projet nommé Grapher à partir de ces sources (New Java Project with Existing Sources) en ajoutant le répertoire téléchargé grapher/src à ce projet. Exécutez la classe principale grapher.ui.Main de ce projet pour vérifier que le code fonctionne bien.

Interaction graphique

Il s'agit de créer une classe Interaction dans le paquet grapher.ui qui permettra l'interaction à la souris avec le graphique (voir les interfaces des Listeners proposées par MouseListener et MouseMotionListener réunies en MouseInputListener ; et MouseWheelListener).

Créez la classe Interaction.

Interaction implémente l'interface MouseMotionListener (correspond à MouseListener + MouseMotionListener). Les listeners permettent d’écouter et de réagir suite aux actions de l'utilisateur (clic, touche du clavier, etc.) Pour écouter des événements dont on ne sait pas quand ils vont se produire (clic sur un bouton, redimensionnement d'une fenêtre, changement d'une valeur, etc.) on utilise toujours des listeners. Les listeners sont des interfaces (c'est-à-dire un contrat). Ces interfaces fournissent des méthodes qui peuvent être implémentées différemment selon les cas et les besoins, pour répondre aux événements.

Faites en sorte que chaque évènement souris affiche une trace dans la console permettant d'identifier le type de l'évènement ainsi que la position correspondante du curseur de la souris.

Jouez un peu avec la souris pour bien se rendre compte de quand les méthodes sont appelées.

Faites en sorte que le drag, bouton gauche enfoncé, permette de déplacer le repère (en appelant la méthode translate(int dX, int dY) de la classe Grapher).

Changez le curseur (en appelant la méthode setCursor héritée par la classe Grapher) pour qu'il se transforme en une petite main à la place du curseur par défaut pendant le déplacement (voir la classe Cursor).

Ajoutez le support au clic gauche qui doit permettre de zoomer de 5%, centré sur le curseur (en appelant la méthode de la classe Grapher zoom(Point center, int dz) avec 5 pour valeur de dz).

Faites de même pour le clic droit qui doit permettre de dézoomer de 5%, centré sur le curseur (en appelant la méthode zoom(Point center, int dz) avec -5 pour valeur de dz).

Ajoutez enfin le support de la molette qui doit permettre de zoomer ou dézoomer, centré sur le curseur.

Pour aller plus loin

Ajoutez le support au drag, bouton droit enfoncé, qui doit permettre de sélectionner une zone rectangulaire, marquée par un rectangle pointillé, pour zoomer sur cette sélection lorsque le bouton est relâché.

Pour cette fonctionnalité, on a besoin de dessiner un rectangle et de mettre à jour l'affichage pour afficher effectivement le rectangle qu'on aura dessiné.

Rappel : pour mettre à jour l'affichage, on appelle la méthode repaint du Grapher qui prévient le gestionnaire de fenêtre qu'il va falloir redessiner cette fenêtre. Cela va provoquer, par la suite (de façon asynchrone) l'appel de la méthode paintComponent par le système graphique lui-même, une fois qu'il aura repris la main. On n'appelle jamais la méthode paintComponent nous-même, c'est le système qui s'en charge ! C'est dans la méthode paintComponent que l'affichage est rafraîchit et que les changements sont pris en compte.

Problème : si on dessine d'abord le rectangle puis qu'on fait un appel à repaint() ensuite, le rectangle va être effacé, car repaint() redessine toute la fenêtre. Si on fait appel à repaint() puis qu'on dessine le rectangle ensuite, le rectangle ne pas être affiché car l'affichage n'est mis à jour que lors d'un appel à repaint(). Conclusion : il va falloir dessiner le rectangle lors de l'exécution de la méthode repaint(). Un listener capte les évènements provenant de la souris mais ne dessine rien lui-même (cela n'aurait aucun effet, comme expliqué ci-dessus). Au lieu de cela, le listener modifie l'état du composant à afficher et appelle repaint() pour demander la mise à jour de l'affichage.

Certains évènements souris ne sont pas utiles dans ce TP. Pourtant, nous avons dû implémenter toutes les méthodes de l'interface MouseInputListener, allourdissant inutilement notre code. Dans ce genre de cas, Java/SWING propose d'utiliser des adaptateurs (adaptors, i.e. des classes concrètes avec des méthodes ne faisant rien) plutôt que des écouteurs (listeners, i.e. des interfaces avec seulement les prototypes des méthodes). La classe adaptatrice implémente déjà chaque méthode de l'interface, et il suffit de spécialiser les méthodes nécessaires.

Remplacez le listener MouseInputListener par l'adaptateur MouseInputAdapter. Supprimez (commentez) les méthodes inutiles pour la gestion des interactions de ce TP.

mise à jour : 23 février 2021