reseau social

Interface SWT selon le modèle MVC

Un article de ToutProgrammer.com.

Utiliser le paradigme MVC pour développer une application permet de mieux organiser la gestion des évènements et de mutualiser son code. Bref que du bonheur.

Sommaire

[modifier] Introduction

SWT est une librairie Java proposée par le projet OpenSource Eclipse pour développer des applications avec des interfaces graphiques riches. Le site officiel de ce toolkit est à l'adresse: http://www.eclipse.org/swt/.

[modifier] MVC les bases

MVC ou Model/View/Controller.

Le paradigme MVC est un schéma de programmation qui propose de séparer une application en 3 parties:

  • Le modèle, qui contient la logique et l'état de l'application.
  • La vue, qui représente l'interface utilisateur.
  • Le contrôleur, qui gère la synchronisation entre la vue et le modèle. Il réagit aux actions de l'utilisateur en effectuant les actions nécessaires sur le modèle. Ce dernier notifie les vues lorsqu'il a été modifié.
Le modèle MVC 2

Exemple de séquence d'un traitement :

  • Le contrôleur et le modèle sont instanciés,
  • Les vues sont instanciées et s'abonnent au modèle pour être à l'écoute des changements,
  • L'utilisateur manipule l'interface homme/machine. Un évènement est envoyé. Cet évènement est récupéré par le contrôleur,
  • Le contrôleur effectue l'action demandée par l'utilisateur en appelant les méthodes nécessaires sur le modèle,
  • Le modèle étant modifié, il envoie une notification aux différentes vues du changement effectué,
  • Chaque vue interroge le modèle afin de connaitre son état,
  • L'utilisateur voit le résultat de son action.

[modifier] Les fonctions de l'application

L'application que nous allons mettre en oeuvre est composée d'une fenêtre et d'un icône qui apparait dans la barre des tâches.

Au lancement de l'application le fenêtre n'apparait pas. Si on double clique sur l'icône la fenêtre apparait. On peut également provoquer cette action en utilisant le menu contextuel associé à l'icône et en choisissant l'action 'Afficher détail'.

[modifier] Pré-requis

Pour lancer cette application vous devez utiliser la version Eclipse M8 ou supérieure. Le projet dans lequel est développé l'application doit contenir à la racine les DLLs : swt-awt-win32-3044.dll et swt-win32-3044.dll (versions pour Eclipse M8).

Eclipse

[modifier] Les packages

L'application est composée de trois packages :

  • model
  • controller
  • view

Leur nom est assez explicite. Avant de détailler pas à pas l'application voici la vue d'ensemble des classes :

Eclipse

La classe Main.java est le point d'entrée de l'application. Elle a pour rôle d'instancier le controleur le modèle et les vues. L'icône correspond à une vue, et la fenêtre correspond à une autre vue du même modèle métier.

[modifier] Point d'entrée : Main.java

  1. import controller.*;
  2. import model.*;
  3. import view.*;
  4.  
  5. public class Main {
  6.  
  7. public static void main(String[] args) {
  8.  
  9. DocumentModel document= new DocumentModel();
  10. ControllerPrincipal controller = new ControllerPrincipal();
  11.  
  12. SystrayView systray = new SystrayView();
  13. systray.setControllerPrincipal(controller );
  14. systray.setDocumentModel(document);
  15.  
  16. WindowView window = new WindowView();
  17. window.setControllerPrincipal(controller );
  18. window.setDocumentModel(document);
  19.  
  20. window.run();
  21.  
  22. }
  23.  
  24. }

La méthode run() met en attente la fenêtre. Tant que la fenêtre n'est pas fermée elle continue à fonctionner.

  1. public class WindowView extends ViewAbstract{
  2.  
  3. Shell shell = null;
  4. Display display;
  5. ...
  6. public void run()
  7. {
  8. while (!shell.isDisposed ()) {
  9. if (!display.readAndDispatch()) display.sleep();
  10. }
  11. display.dispose();
  12. }
  13. ...

[modifier] Instanciation des éléments graphiques

Avant de détailler le modèle MVC et le système de notification décrivons rapidement la mise en place des vues.

Deux vues doivent être créées: SystrayVuequi correspond à l'icône dans la barre des tâches et WindowViewqui correspond à la fenêtre.

L'icône qui doit apparaître dans la barre des tâches est facilement mise en place depuis la version M8 d'Eclipse. Le snippet Tray montre un exemple.

Pour créer l'icône il suffit de mettre dans le constructeur de la vue SystrayViewles lignes suivantes :

  1. public SystrayView()
  2. {
  3. // init icon & tray
  4. display = Display.getDefault();
  5. shell = new Shell (display);
  6. logo1 = new Image(display, "ok.ico");
  7. logo2 = new Image(display, "nop.ico");
  8.  
  9. Tray tray = display.getSystemTray();
  10. item = new TrayItem (tray, SWT.NONE);
  11. item.setImage(logo2);
  12. item.setToolTipText("Test MVC");
  13. ..

Comme on souhaite faire apparaitre un menu contextuel à cet icône on ajoute les lignes suivantes :

  1. // Tray menu
  2. final Menu menu = new Menu (shell, SWT.POP_UP);
  3.  
  4. MenuItem mi1 = new MenuItem (menu, SWT.PUSH);
  5. mi1.setText ("Lancement traitement");
  6. ..
  7. mi2 = new MenuItem (menu, SWT.PUSH);
  8. mi2.setText ("Afficher détail");
  9. ..
  10. MenuItem mi3 = new MenuItem (menu, SWT.PUSH);
  11. mi3.setText ("Fermer");
  12. ..
  13. // Affiche le menu si on clique sur l'icône
  14. item.addListener (SWT.MenuDetect, new Listener () {
  15. public void handleEvent (Event event) {
  16. menu.setVisible (true);
  17. }
  18. });

La vue WindowViewcorrespond à une fenêtre qui contient une champ texte non éditable. Elle est mise en place par son constructeur :

  1. public WindowView()
  2. {
  3. display = Display.getDefault();
  4.  
  5. shell = new Shell (display);
  6. shell.setText("TITRE");
  7. shell.setBounds(50, 50, 300, 100);
  8. ..
  9. titre = new CLabel(shell,SWT.NULL);
  10. titre.setText("blablabla..");
  11. titre.setSize(new org.eclipse.swt.graphics.Point(306,19));
  12. }

[modifier] ViewAbstract et ViewInterface

Les vues SystrayViewet WindowViewdérivent dans la vue abstraite ViewAbstract. Cette classe implémente l'interface InterfaceView.

  1. package view;
  2. import controller.*;
  3. import model.*;
  4.  
  5. public abstract class ViewAbstract implements ViewInterface{
  6.  
  7. DocumentModel document;
  8. ControllerPrincipal controller;
  9.  
  10. public void setDocumentModel(DocumentModel newdocument)
  11. {
  12. this.document= newdocument;
  13. document.addDocumentListener(this);
  14. }
  15. public void setControllerPrincipal(ControllerPrincipal newcontroller)
  16. {
  17. this.controller= newcontroller;
  18. }
  19. public ControllerPrincipal getControllerPrincipal()
  20. {
  21. return this.controller;
  22. }
  23. public DocumentModel getDocumentModel()
  24. {
  25. return this.document;
  26. }
  27. }

  1. package view;
  2. import controller.*;
  3. import model.*;
  4.  
  5. public interface ViewInterface {
  6. static int ACTION_FERMER = 0;
  7. static int ACTION_VERIFIER_DOCUMENT_LIBRE = 1;
  8. static int ACTION_CHANGERMODE = 2;
  9. static int ACTION_CACHERFENETRE = 3;
  10. public ControllerPrincipal getControllerPrincipal();
  11. public DocumentModel getDocumentModel();
  12. }

[modifier] Mise en place des listeners

Pour respecter le modèle MVC nous allons rediriger tous les évènements que l'on souhaite intercepter vers la classe Contrôleur. Pour cela nous allons créer une classe SwtControllerListenerqui va contenir toutes les actions à gérer.

  1. package controller;
  2.  
  3. import org.eclipse.swt.widgets.Listener;
  4. import org.eclipse.swt.widgets.Event;
  5.  
  6. import view.*;
  7.  
  8. public class SwtControllerListener implements Listener{
  9. private int actionCode;
  10. private ViewInterface view;
  11.  
  12. public SwtControllerListener(ViewInterface view, int actionCode) {
  13. this.view = view;
  14. this.actionCode = actionCode;
  15. }
  16.  
  17. public void handleEvent (Event event) {
  18. this.view.getControllerPrincipal().actionPerformed(this.view, this.actionCode);
  19. }
  20.  
  21. }

Lorsque l'on ajoute la capture d'un évênement sur un contrôle il devient possible de rediriger le traitement à la classe Controleur. Pour cela il suffit d'instancier la classe SwtControllerListeneren passant le code de l'action en paramêtre.

Par exemple si quand on double-clique sur l'icône de la barre des tâches on souhaite lancer une action on ajoutera après avoir créé l'objet tray la ligne suivante : item.addListener(int eventType, Listener listener). Exemple :

  1. Tray tray = display.getSystemTray();
  2. item = new TrayItem (tray, SWT.NONE);
  3. item.setImage(logo2);
  4. item.setToolTipText("Test MVC");
  5. item.addListener (SWT.DefaultSelection, new SwtControllerListener(this, ViewInterface.ACTION_CHANGERMODE));

Les actions sont centralisées dans le contrôleur qui appelle la fonction demandée :

  1. package controller;
  2. import model.*;
  3. import org.eclipse.swt.widgets.*;
  4. import view.*;
  5.  
  6. public class ControllerPrincipal {
  7.  
  8. public void actionPerformed(ViewInterface view, int actionCode) {
  9.  
  10. switch (actionCode) {
  11. case ViewInterface.ACTION_FERMER :
  12. fermer();
  13. break;
  14. case ViewInterface.ACTION_VERIFIER_DOCUMENT_LIBRE :
  15. verifier(view);
  16. break;
  17. ..
  18. default :
  19. break;
  20. }
  21.  
  22. }
  23.  
  24. void verifier(ViewInterface view)
  25. {
  26. DocumentModel document = view.getDocumentModel();
  27. System.out.println("Début vérifier");
  28. document.setStatus(DocumentInterface.STATUS_ENCOURS);
  29.  
  30. .. Traitement ..
  31.  
  32. document.setStatus(DocumentInterface.STATUS_TERMINE);
  33. System.out.println("Fin vérifier");
  34. }
  35.  
  36. void fermer()
  37. {
  38. Display.getDefault().dispose();
  39. }

[modifier] Ajout des abonnements

Dès que le modèle est modifié il notifie toutes les vues. Pour cela les vues doivent s'être abonnées au modèle. Cette action se fait au lancement de l'application lorsque l'on rattache le modèle à la vue :

  1. systray.setDocumentModel(document);

Derrière ce code on effectue l'abonnement :

  1. public abstract class ViewAbstract implements ViewInterface,DocumentInterface{
  2.  
  3. DocumentModel document;
  4. ControllerPrincipal controller;
  5.  
  6. public void setDocumentModel(DocumentModel newdocument)
  7. {
  8. this.document = newdocument;
  9. document.addDocumentListener(this);
  10.  
  11. }
  12. ..

  1. public class DocumentModel{
  2.  
  3. String status;
  4. boolean detailVeille;
  5.  
  6. private NotifyChangeDocument notifyChangeDocument;
  7. ..
  8. public void addDocumentListener(DocumentInterface documentListener)
  9. {
  10. notifyChangeDocument.addDocumentListener(documentListener);
  11. }

Tous les abonnements sont centralisés dans un attribut défini dans le modèle :

  1. private NotifyChangeDocument notifyChangeDocument;

C'est la classe NotifyChangeDocumentqui permet de notifier les vues abonnées.

[modifier] Gestion de la notification

Lorsque le modèle souhaite envoyer une notification il appelle la méthode fire* qui concerne le changement effectué :

Exemple si le mode d'affichage change on appelle la méthode fireModeChanged() :

  1. public class DocumentModel{
  2. ..
  3. public void setDetailVeille(boolean newDetailVeille)
  4. {
  5. detailVeille = newDetailVeille;
  6. notifyChangeDocument.fireModeChange();
  7. }
  8. ..

La classe NotifyChangeDocumentparcourt la liste des vues abonnées et appelle la méthode correspondant à ce changement.

  1. public class NotifyChangeDocument {
  2. ..
  3. protected void fireModeChange()
  4. {
  5. Vector v;
  6. synchronized(this) {
  7. v = (Vector) changeListeners.clone();
  8. }
  9. Iterator iter = v.iterator();
  10. while (iter.hasNext())
  11. ((DocumentInterface) iter.next()).modeChanged();
  12. }
  13. ..

Dans chaque vue la méthode prend en compte la notification et modifie la vue en conséquence.

Pour la vue systrayle changement de mode fait changer de logo.

  1. public class SystrayView extends ViewAbstract{
  2. ..
  3. public void modeChanged()
  4. {
  5. // Change d'icône selon le status
  6. if(getDocumentModel().isDetailVeille())
  7. {
  8. item.setImage(logo1);
  9. mi2.setText ("Cacher détail");
  10. } else
  11. {
  12. item.setImage(logo2);
  13. mi2.setText ("Afficher détail");
  14. }
  15. }
  16. ..

Pour la vue windowle changement de mode fait apparaître ou disparaître la fenêtre :

  1. public class WindowView extends ViewAbstract{
  2. ..
  3. public void modeChanged()
  4. {
  5. // Affiche ou cache la fenêtre selon le status
  6. if(getDocumentModel().isDetailVeille())
  7. {
  8. shell.setVisible(true);
  9. shell.forceActive();
  10. } else
  11. {
  12. shell.setVisible(false);
  13. }
  14. }
  15. ..

[modifier] Ressources

Code source du projet de l'article

[modifier] Conclusion

Nous avons vu à l'aide d'un exemple simple comment il est possible de mettre en place une interface SWT selon le modèle MVC.

[modifier] Historique de l'article

Cet article, réalisé par Olivier MALE, a été publié pour la première fois sur ToutProgrammer.com (1ème version) le 10 mai 2004.