CentraleSupélecDépartement informatique
Plateau de Moulon
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
Génie logiciel orienté objet - Exercice : Interface graphique en Java avec Swing

Exercice

Swing est une bibliothèque Java pour la réalisation d'interfaces graphiques, elle s'appuie sur une bibliothèque de plus bas niveau nommée AWT (Abstract Window Toolkit). La documentation de ces bibliothèques se trouve sur le site d'Oracle, vous êtes fortement encouragés à aller la consulter.

Vous aurez besoin d'importer la plupart des classes que vous allez utiliser, faites le au fur et à mesure.

1. Création et affichage d'une fenêtre

  • Créer un nouveau projet gloo.testswing sous Eclipse, créer le module correspondant. Dans ce projet, ajouter, dans le paquetage gloo.testswing, une classe TestSwing qui contiendra la fonction main() de votre programme.

  • Conformémént aux indications données ici (les threads seront vus ultérieurement), modifier votre classe pour qu'elle implémente l'interface Runnable, demander à Eclipse d'ajouter la méthode run() (quick fix sur le nom de la classe → add unimplemented methods), appeler dans main() la fonction invokeLater() de la classe SwingUtilities avec une nouvelle instance de votre classe comme argument. SwingUtilities est souligné en rouge, et le quick fix Import 'SwingUtilities' (javax.swing) n'est pas sufisant pour résoudre le problème : le paquetage javax.swing figure dans le module java.desktop, il faut donc indiquer que ce module est requis (instruction requires) dans votre fichier module-info.java.

Après sauvegarde de vos fichiers suite à ces modifications, demandez de l'aide à votre encadrant si des erreurs subsistent.



La classe principale pour la réalisation d'une interface graphique est la classe JFrame qui permet de créer une fenêtre.

  • Créer dans la fonction run() une instance de la classe JFrame ayant pour titre Graphisme avec Swing.

  • Pour fixer la taille de la fenêtre, vous appellerez sur votre instance la méthode :
void setPreferredSize(Dimension preferredSize); // permet d'indiquer la taille par défaut de la fenêtre
Vous aurez besoin d'une instance de la classe Dimension. Cette classe (voir sa documentation) possède un constructeur qui prend en argument un entier pour la largeur et un pour la hauteur.

  • Pour que votre application se termine quand on ferme la fenêtre, vous utiliserez la méthode suivante :
void setDefaultCloseOperation(int operation); // Utilisez JFrame.EXIT_ON_CLOSE comme argument


  • Enfin, pour que la fenêtre s'affiche, vous utiliserez les méthodes suivantes. Attention, ces méthodes doivent être appelées à la fin de votre code.
void pack();                      // permet de mettre en forme la fenêtre
void setVisible(boolean visible); // permet d'afficher (ou de masquer) la fenêtre.


  • Vérifier que votre fenêtre s'affiche correctement.

Il est possible d'ajouter différents composants (boutons, menus déroulants, cases à cocher, zones de texte...) dans une fenêtre. Pour le projet intégré, vous aurez besoin d'y ajouter une zone de dessin. Pour cela, il faut utiliser une classe qui hérite de la classe JPanel (qui représente un composant vide, voir sa documentation) et redéfinir la méthode de dessin paint() utilisée par tous les composants graphiques.

  • Définir une classe MonDessin qui hérite de JPanel.

  • Redéfinir (Menu SourceOverride/implement Methods…) dans cette classe la méthode void paint(Graphics g) de manière à dessiner un rectangle de couleur rouge et de taille 570 x 292. Cette méthode reçoit en argument un objet de type Graphics (voir sa documentation), on peut voir cet objet comme le crayon servant à dessiner sur notre surface. Cette méthode paint() de votre classe MonDessin sera appelée automatiquement et l'objet Graphics reçu en argument sera lui aussi créé automatiquement, vous devez juste définir dans cette méthode le comportement à adopter lorsque la fenêtre est dessinée.

  • Pour dessiner votre figure, vous utiliserez les méthodes suivantes de la classe AWT Graphics :
void setColor(Color color);      // permet de changer la couleur du crayon
                                 // La classe Color définit des constantes pour les principales couleurs
void fillRect(int x, int y, int width, int height); // permet de dessiner un rectangle plein


  • Vous devez ensuite créer dans votre fonction run() une instance de votre classe MonDessin. Vous associerez cette instance à votre instance de JFrame avec la méthode add(Component) qui peut prendre l'instance de MonDessin en argument.

Nous souhaitons maintenant afficher une image. Pour cela, nous allons utiliser la classe ImageIO.

  • Télécharger l'image suivante dans le répertoire contenant votre projet (à côté des sous dossiers src et bin déjà présents) : image.

  • Définir un attribut de type Image dans votre classe MonDessin. Créer un constructeur pour votre classe MonDessin. Utiliser la méthode ImageIO.read(File file) pour charger votre image dans le constructeur de MonDessin. La classe File possède un constructeur qui prend en argument une chaine de caractère indiquant le chemin d'accès au fichier (le nom suffit si le fichier est dans le répertoire de votre projet).

  • Eclipse va vous signaler une erreur : Unhandled exception type IOException. Choisir l'option Surround with try/catch proposée.

  • Modifier enfin votre méthode paint pour afficher l'image par dessus le rectangle rouge dans la zone de dessin, en utilisant la méthode drawImage(Image img, int x, int y, ImageObserver observer) de la classe Graphics. Vous laisserez le dernier paramètre à null.

2. Gestion des évènements claviers

Nous voulons que l'interface réagisse lorsque l'utilisateur appuie sur une touche du clavier. Par exemple, nous voulons que le rectangle change de couleur (rouge/orange/vert) chaque fois que l'utilisateur appuie sur la touche , ou .

Le principe de mise en œuvre est le suivant : une classe qui implémente l'interface KeyListener (dans le paquetage java.awt.event, voir sa documentation) et qui est associée à la fenêtre au premier plan recevra les événements claviers et pourra ainsi modifier ce qu'affiche la fenêtre en fonction des événements reçus.

Ne pas confondre une interface au sens Java ou UML (type définissant un ensemble de méthodes abstraites) avec une interface homme-machine (IHM) permettant les échanges entre un utilisateur et une application.



  • Ajouter à la classe MonDessin une méthode permettant de modifier la couleur du rectangle ; cette méthode recevra donc comme unique argument la nouvelle couleur à utiliser. C'est uniquement dans la méthode paint(Graphics g) que vous utiliserez cette nouvelle couleur, il faudra donc mémoriser dans un attribut la couleur reçue.

Attention : vous ne devez pas vous même appeler la méthode paint(Graphics g) pour prendre en compte la nouvelle couleur : pour forcer le JPanel à se redessiner afin que cette nouvelle couleur soit prise en compte, appelez la méthode repaint() sur votre objet MonDessin qui se chargera d'appeler la méthode paint(Graphics g) avec le bon argument.



  • Créer une nouvelle classe GestionClavier qui implémente KeyListener.

Pour implémenter une interface, on utilise le mot clef implements au lieu de extends pour l'héritage. Il faut également définir toutes les méthodes de l'interface.



Les méthodes à définir sont keyPressed(KeyEvent e), keyReleased(KeyEvent e) et keyTyped(KeyEvent e), seule la deuxième nous sera utile. La classe KeyEvent contient des méthodes permettant d'avoir des informations sur la touche qui a déclenché l'évènement (nous utiliserons getKeyCode()) ainsi que des constantes correspondant aux touches du clavier (par exemple KeyEvent.VK_V).

  • Ajouter en attribut à GestionClavier un objet de type MonDessin que vous lui transmettrez comme argument du constructeur.

  • Implémenter la reconnaissance du caractère tapé et le changement de couleur associé dans la méthode keyReleased().

  • Dans votre fonction run(), après la création de l'objet de type MonDessin, créer une instance de GestionClavier et associer la à votre instance de JFrame en utilisant la méthode addKeyListener(KeyListener).

  • Vérifier le bon fonctionnement.

Voici le schéma global des classes avec les opérations utiles :


3. Bonne conception

Dans une application informatique il est nécessaire de distinguer les objets représentant les données manipulées par l'application (on parle d'objets métiers) des objets chargés d'afficher ces données (les objets de l'IHM, objets frontières). Cette approche est justifiée par les points suivants :

  • il est souvent utile d'afficher des données métiers de différentes façons (exemple classique : les différents graphiques, pour une même série de données, proposés par les tableurs) ;
  • une application doit souvent pouvoir s'exécuter sur différentes plate-formes (Windows®, Unix®, iOS®, Android®...) ;
  • l'IHM doit être adaptée aux spécificités culturelles des utilisateurs (langue, façon d'afficher ou d'écrire les nombres...).

Pour notre exemple de dessin, nous allons considérer1 que la couleur d'affichage du rectangle est une donnée métier, ainsi que l'image affichée. Par contre, les touches à utiliser pour choisir la couleur dépendent de la langue de l'utilisateur. Un Anglais utilisera par exemple le à la place du pour choisir la couleur verte.
Par ailleurs, le déroulement des échanges entre un utilisateur et un système informatique doit en général respecter des règles qui peuvent par exemple imposer un certain ordre dans les échanges ou vérifier que certaines conditions sont remplies pour permettre le passage à l'étape suivante. Le ou les objets qui s'occupent de ces contrôles sont appelés contrôleurs et servent d'intermédiaires entre les objets de l'IHM et les objets métiers.

Nous utilisons ici une interprétation restrictive du patron d'architecture MVC (Model-View-Controller) : dans le cadre d'une mise en œuvre classique de ce patron, les objets d'affichage accèdent en lecture aux objets métiers sans passer par le contrôleur.



Dans notre exemple de dessin, nous imposerons que les couleurs changent en respectant l'ordre d'un feu tricolore.

  • Créer 3 paquetages boundary, control et entity.

  • Réorganiser votre code de façon à avoir :
    • dans boundary, une classe MaFenetre qui hérite de JFrame et qui servira aussi d'écouteur clavier ;
    • dans boundary, une classe MonAfficheur qui hérite de JPanel et qui s'occupe de l'affichage ;
    • dans entity, une classe MaCouleur qui mémorise la couleur courante et y donne accès en lecture et en modification ;
    • dans entity, une classe MonImage qui donne accès à l'image (mémorisée dans une BufferedImage) et à ses dimensions ;
    • dans control, une classe MonControleur qui sert d'intermédiaire.

Les objets IHM connaissent le contrôleur : la fenêtre l'informe des demandes de changement de couleur, et l'afficheur lui demande la couleur courante, l'image et ses dimensions.

Le contrôleur connait MonImage et MaCouleur 2 : il sert d'intermédiaire pour les demandes de l'afficheur. Il vérifie que le changement de couleur demandé est valide avant de demander ce changement à l'objet couleur.

  • Écrire les constructeurs des classes pour établir ces liens (c'est le constructeur de la fenêtre qui crée l'afficheur).

  • Compléter le reste du code pour avoir une application fonctionnelle.


Architecture de l'application :


 

1 Ce choix est ici purement didactique, les objets métiers représentent, dans le cas général, des données liées au domaine applicatif ; voir par exemple le cas du projet intégré.

2 Donc, ici, tous les objets métiers ; dans le cas général, le contrôleur ne doit en connaître que quelques-uns ; voir par exemple le cas du projet intégré.



© 2025 CentraleSupélec