La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Jean-Christophe Soulié

Présentations similaires


Présentation au sujet: "Jean-Christophe Soulié"— Transcription de la présentation:

1 Jean-Christophe Soulié
JAVA Avancé DESS ISIDIS Année 2003/2004 Jean-Christophe Soulié

2 Plan Gestion des évènements MVC Les observeurs Le réflexion Swing
Le squelette d’un éditeur de texte DESS ISIDIS /2004

3 Gestion des évènements
Un composant enregistre des auditeurs d’évènements (Listeners) Lorsqu’un évènement se produit dans un composant, il est envoyé aux auditeurs enregistrés Chaque auditeur définit les actions à entreprendre, dans des méthodes aux noms prédéfinis Exemple : Un Bouton enregistre des ActionListener Lors d’un clic, un ActionEvent est envoyé aux ActionListener enregistrés Ceci provoque l’exécution de la méthode actionPerformed de chaque ActionListener DESS ISIDIS /2004

4 Exemple Une classe de boutons Une classe d’étiquettes auditrices
class MonBouton extends Button { int incr; MonBouton(String title, int incr) { super(title); this.incr = incr; } int getIncr() { return incr} Une classe d’étiquettes auditrices class ListenerLabel extends Label implements ActionListener{ ListenerLabel() { super("0", Label.CENTER);} public void actionPerformed(ActionEvent evt) { MonBouton b = (MonBouton) evt.getSource(); int c = Integer.parseInt(getText()); c += b.getIncr(); setText(Integer.toString(c)); } DESS ISIDIS /2004

5 Exemple (fin) Le cadre Et pour fermer DESS ISIDIS - 2003/2004
class PlusOuMoins extends Frame { PlusOuMoins() { super("Plus ou moins"); Bouton oui = new MonBouton("Plus", 1); Bouton non = new MonBouton("Moins", -1); Label diff = new ListenerLabel(); oui.addActionListener((ActionListener) diff); non.addActionListener((ActionListener) diff); } public static void main(String args[]) { Frame r = new PlusOuMoins(); r.pack(); r.setVisible(true); r.addWindowListener(new WindowCloser()); Et pour fermer class WindowCloser extends WindowAdapter { public void windowClosing(WindowEvent evt) { System.exit(0); } DESS ISIDIS /2004

6 MVC (Model-View-Controller)
Le modèle gère les données La vue affiche les données Le contrôleur gère la communication et les mise-à-jour Origine : Smalltalk (Xerox Park, Palo Alto – Milieu des années 70) DESS ISIDIS /2004

7 Exemple (1) DESS ISIDIS - 2003/2004 class PlusouMoinsMVC {
Model model; View view; Controller control; public PlusouMoinsMVC() { model = new Model(); control = new Controller(); view = new View( control); control. setModel( model); control. setView( view); view. setVisible( true); view. addWindowListener( new WindowCloser()); } public static void main (String[] argv) { PlusouMoinsMVC r = new PlusouMoinsMVC(); DESS ISIDIS /2004

8 Exemple (2) Le modèle contient la donnée La mise-à-jour par update()
Le renseignement par getValue() class Model { int compteur; Model() { compteur = 0; } void update( int incr) { compteur += incr; int getValue() { return compteur; DESS ISIDIS /2004

9 Exemple (3) La vue affiche les composants et les données
La mise- à- jour du texte de l’étiquette est faite par update(). La notification de modifications au contrôleur. C’est le contrôleur qui écoute ! Les renseignements sont pris par getValue(). La vue se débrouille pour les obtenir. class View extends Frame { Button oui = new Button("Up !"); Button non = new Button("Down !"); Label diff = new Label("0", Label.CENTER); public View(Controller c) { super("Plus ou moins"); add(oui, BorderLayout.NORTH); add(non, BorderLayout.SOUTH); add(diff, BorderLayout.CENTER); oui.addActionListener(c); non.addActionListener(c); pack(); } void update(int compte) { diff.setText(Integer.toString(compte)); int getValue(ActionEvent e) { Button b = (Button) e.getSource(); return (b == oui) ? 1 : -1; DESS ISIDIS /2004

10 Exemple (4) Le contrôleur :
Réveillé par les actions produites dans la vue Récupère des information dans la vue Met à jour dans le modèle Récupère la nouvelle valeur dans le modèle La transmet pour affichage à la vue class Controller implements ActionListener { private Model m; private View v; public void actionPerformed(ActionEvent e) { m.update(v.getValue(e)); v.update(m.getValue()); } public void setModel (Model m) { this.m = m; public void setView (View v) { this.v = v; DESS ISIDIS /2004

11 Observeurs Une classe : Observable
Un objet observable issu de cette classe dispose, entre autres, des méthodes suivantes : addObserver(Observer o) permet de rajouter un observeur setChanged() indique un changement de l’état de l’objet notifyObservers(Object arg) si l'état de l'objet a été changé, l'appel à cette méthode permet de notifier les observateurs potentiels de ce changement, par l'appel de la méthode update avec pour arguments l'objet observé et l'argument arg DESS ISIDIS /2004

12 Observeurs (suite) Une interface : Observer
Permet de décrire un objet qui souhaite être informé du changement d’état d’objets issus de la classe Observable Introduit une seule méthode : public void update(Observable o, Object arg) qui est appelée lorsque o change d’état DESS ISIDIS /2004

13 Observeurs : Exemple (1)
L’observé class TableObservable extends Observable { Hashtable table = new Hashtable(); public synchronized Object put(Object clé, Object valeur) { setChanged(); notifyObservers(clé); return table.put(clé, valeur); } public synchronized Object get(Object clé) { return table.get(clé); public synchronized boolean containsKey(Object clé) { return table.containsKey(clé); public synchronized Enumeration keys() { return table.keys(); public int size() { return table.size(); DESS ISIDIS /2004

14 Observeurs : Exemple (2)
L’observeur class ObservateurDeTable implements Observer { public ObservateurDeTable(Observable o) { o.addObserver(this); } public void update(Observable o, Object arg) { System.out.println("J'observe l'objet : "+o+"\nqui m'envoie :"+arg); DESS ISIDIS /2004

15 Observeurs : Exemple (3)
class TestObserver{ public static void main (String[] args) { StringTokenizer lstMots = new StringTokenizer(args[0], " ,."); TableObservable table = new TableObservable(); ObservateurDeTable observateur = new ObservateurDeTable(table); while (lstMots.hasMoreTokens()) { String mot = lstMots.nextToken(); if (!table.containsKey(mot)) table.put(mot, new Integer(1)); else { Integer nbre = (Integer) table.get(mot); Integer i = new Integer(1+nbre.intValue()); table.put(mot, i); } System.out.print("==> Dans la phrase \""); System.out.print(args[0]); System.out.print("\",\n il y a "); System.out.print(table.size()); System.out.println(" mots différents qui sont:"); for (Enumeration e = table.keys(); e.hasMoreElements(); ) { String mot = (String) e.nextElement(); System.out.println("==> "+mot+" ("+table.get(mot)+" fois)"); DESS ISIDIS /2004

16 Observeurs : Exemple (4)
Trace de l’exécution $ java TestObserver "Voila ma phrase, ma courte phrase." J'observe l'objet : qui m'envoie : Voila qui m'envoie : ma qui m'envoie : phrase qui m'envoie : courte ==> Dans la phrase "Voila ma phrase, ma courte phrase.", il y a 4 mots différents qui sont: ==> phrase (2 fois) ==> ma (2 fois) ==> courte (1 fois) ==> Voila (1 fois) $ DESS ISIDIS /2004

17 Réflexion L'objet Class, qui contient toutes les informations relative à la classe (on l'appelle parfois meta-class). En fait, l'objet Class est utilisé pour créer tous les objets « habituels » d'une classe. Il y a un objet Class pour chacune des classes d'un programme. A chaque fois qu'une classe est écrite et compilée, un unique objet de type Class est aussi créé DESS ISIDIS /2004

18 Réflexion Durant l'exécution, lorsqu'un nouvel objet de cette classe doit être créé, la JVM qui exécute le programme vérifie d'abord si l'objet Class associé est déjà chargé. Si non, la JVM le charge en cherchant un fichier .class du même nom. Ainsi un programme Java n'est pas totalement chargé en mémoire lorsqu'il démarre, contrairement à beaucoup de langages classiques. Une fois que l'objet Class est en mémoire, il est utilisé pour créer tous les objets de ce type. DESS ISIDIS /2004

19 Réflexion Class.forName("NomClasse");
Cette méthode est une méthode static de Class (qui appartient à tous les objets Class). Un objet Class est comme tous les autres objets, il est donc possible d'obtenir sa référence et de la manipuler (c'est ce que fait le chargeur de classes). Un des moyens d'obtenir une référence sur un objet Class est la méthode forName(), qui prend en paramètre une chaîne de caractères contenant le nom de la classe dont vous voulez la référence. Elle retourne une référence sur un objet Class. DESS ISIDIS /2004

20 Réflexion La classe Class (décrite précédemment dans ce chapitre) supporte le concept de réflexion, et une bibliothèque additionnelle, java.lang.reflect, contenant les classes Field, Method, et Constructor (chacune implémentant l'interface Member). Les objets de ce type sont créés dynamiquement par la JVM pour représenter les membres correspondants d'une classe inconnue. DESS ISIDIS /2004

21 Réflexion On peut alors utiliser les constructeurs pour créer de nouveaux objets, les méthodes get() et set() pour lire et modifier les champs associés à des objets Field, et la méthode invoke() pour appeler une méthode associée à un objet Method. De plus, on peut utiliser les méthodes très pratiques getFields(), getMethods(), getConstructors(), etc. retournant un tableau représentant respectivement des champs, méthodes et constructeurs (pour en savoir plus, jetez un oeil à la documentation en ligne de la classe Class). Ainsi, l'information sur la classe d'objets inconnus peut être totalement déterminée dynamiquement, sans rien en savoir à la compilation. DESS ISIDIS /2004

22 Réflexion : un exemple Un extracteur de méthodes
public class ShowMethods { public static void main(String[] args) { try { Class c = Class.forName(args[0]); Method[] m = c.getMethods(); Constructor[] ctor = c.getConstructors(); if(args.length == 1) { for (int i = 0; i < m.length; i++) System.out.println(m[i]); for (int i = 0; i < ctor.length; i++) System.out.println(ctor[i]); } else { for (int i = 0; i < m.length; i++) if(m[i].toString().indexOf(args[1])!= -1) System.out.println(m[i]); for (int i = 0; i < ctor.length; i++) if(ctor[i].toString().indexOf(args[1])!= -1) System.out.println(ctor[i]); } } catch(ClassNotFoundException e) { System.err.println("Classe non trouvée : " + e); } } } Un extracteur de méthodes DESS ISIDIS /2004

23 Swing Swing est une extension des AWT
nombreux nouveaux composants nombreuses facilités séparation entre : modèle (données) aspect visuel (UI) contrôle Les composants sont légers, sauf JApplet, JWindow, JFrame, JDialog Ces derniers ont une structure spéciale DESS ISIDIS /2004

24 Swing – l’arbre d’héritage
DESS ISIDIS /2004

25 JFrame Une JFrame contient une fille unique, de la classe JRootPane.
Cette fille contient deux filles, glassPane (JPanel) et layeredPane (JLayeredPane). La layeredPane a deux filles, contentPane (un Container) et menuBar (null JMenuBar ). On travaille dans contentPane. JApplet, JWindow et JDialog sont semblables. class Tout extends JFrame { Tout() { JPanel panel; getContentPane().add(panel); ... } DESS ISIDIS /2004

26 Modèles et vues Les composants (sauf les conteneurs) ont un modèle qui contient les données associées ButtonModel pour les boutons ListModel pour les données d’un JList TableModel pour les JTable TreeModel pour les JTree Document pour tous les composants de texte La vue d’un composant sont agrégés en un délégué UI (User Interface) détermine le look-and-feel du composant (bord, couleur, ombre, forme des coches) peut- être changé est parfois spécifié par un dessinateur (renderer) à un niveau plus élevé un changement global, pour tous les composants, est le pluggable look and feel (plaf) trois implémentations (quatre ave le Mac) existent : Windows, CDE/ Motif, Metal (défaut). Le contrôle est assuré par le modèle. DESS ISIDIS /2004

27 Look and Feel Trois “look and feel” existent, de noms
"com. sun. java. swing. plaf. windows.WindowsLookAndFeel" "com. sun. java. swing. plaf. motif.MotifLookAndFeel" "javax. swing. plaf. metal. MetalLookAndFeel” On essaye de l’utiliser par UIManager.setLookAndFeel(lf); et de l’appliquer à la racine de l’arbre par SwingUtilities.updateComponentTreeUI(SwingUtilities.getRoot(this)); DESS ISIDIS /2004

28 Actions Moyen commode pour définir une entrée (dans un menu) et simultanément y attacher un auditeur Entrée simultanée dans Toolbar possible Tout changement dans l’un se reflète sur l’autre (grisé etc.) DESS ISIDIS /2004

29 AbstractAction AbstractAction est une classe abstraite
elle implémente l’interface Action Action étend ActionListener la seule méthode à écrire est actionPerformed() Les conteneurs JMenu , JPopupMenu et JToolBar honorent les actions : un même objet d’une classe implémentant AbstractAction peut être « ajouté » à plusieurs de ces conteneurs. les diverses instances opèrent de concert. par exemple, un objet ajouté à un menu et à une barre d’outils est activé ou désactivé simultanément dans les deux. Les classes dérivées de AbstractAction sont utiles quand une même action peut être déclenchée de plusieurs manières. DESS ISIDIS /2004

30 Emploi d’AbstractAction
Création d’une classe qui étend AbstractAction Utilisation comme ActionListener Utilisation dans un menu et dans une barre d’outils class MonAction extends AbstractAction { public void actionPerformed(ActionEvent e) { ... } Action monAction = new MonAction(); JButton b = new JButton("Hello"); b. addActionListener(monAction); Action copyAction = new MonAction("Copy"); JMenu menu = new JMenu("Edit"); JToolBar tools = new JToolBar(); JMenuItem copyItem = menu.add(copyAction); JButton copyBtn = tools.add(copyAction); DESS ISIDIS /2004

31 JTabbedPane Groupe une liste de conteneurs repérés par des onglets.
Création: Ajout de conteneurs à un tabbedPane : JTabbedPane() JTabbedPane( int cotéOnglets) addTab(String texteOnglet, Component composant) addTab(String texteOnglet, Icon icone, Component composant) addTab(String texteOnglet, Icon icone, Component composant, String toolTipText) DESS ISIDIS /2004

32 JTabbedPane Feuille initiale Récupérer le choix
Et la feuille elle-même Nombre total de feuilles tabbedPane.setSelectedIndex(int numero) int tabbedPane. getSelectedIndex() Component tabbedPane.getComponentAt(int numero); int tabbedPane.getTabCount(); DESS ISIDIS /2004

33 JTabbedPane : exemple (1)
Pour naviguer ajouter, enlever les feuilles choisir la position des onglets De plus un message affiche le numéro de l’onglet, à chaque changement DESS ISIDIS /2004

34 JTabbedPane : exemple (2)
Acteurs principaux class Panneau extends JPanel implements ActionListener { String [] imageNames = {"arques","berstel","crochemore","desarmenien", ...}; ImageIcon[] images = new ImageIcon[imageNames.length]; //les images montrées ImageIcon tabimage; //l’icône dans les onglets JTabbedPane tabbedPane; //le panneau à feuilles String[] boutonNames = {"TOP","BOTTOM","LEFT","RIGHT","add","remove"}; JButton[] boutons = new JButton[boutonNames.length]; //les boutons de gestion JLabel statut; //le message d’état AudioClip layoutson, tabson; //les sons des actions Panneau() {} //création de la scène void createTab() {} //ajoute une feuille et son onglet void killTab() {} //supprime une feuille void setStatus(int index) {...} //gestion du message public void actionPerformed(ActionEvent e) {...} //les actions des boutons } DESS ISIDIS /2004

35 JTabbedPane : exemple (3)
Création/Suppression de feuilles public void createTab() { JLabel feuille = null; int ong = tabbedPane.getTabCount(); feuille = new JLabel(imageNames[ong % images.length], images[ong % images.length], SwingConstants.CENTER); feuille.setOpaque(true); feuille.setBackground(Color. green); tabbedPane.addTab("Feuille No " + ong, tabimage, feuille); tabbedPane.setSelectedIndex(ong); setStatus(ong); } public void killTab() { // dernière if (tabbedPane.getTabCount()> 0) { tabbedPane.removeTabAt(tabbedPane.getTabCount() - 1); setStatus(tabbedPane.getSelectedIndex()); public void setStatus( int index) { if (index > -1) statut.setText("Feuille choisie: " + index); else statut.setText("Pas de feuille choisie"); DESS ISIDIS /2004

36 JTabbedPane : exemple (4)
Les actions des boutons La classe SwingConstants contient les constantes de placement public void actionPerformed(ActionEvent e) { String lib = ((JButton) e.getSource()).getActionCommand(); if (lib.equals(boutonNames[0])) { tabbedPane.setTabPlacement(SwingConstants.TOP); layoutson.play(); } else if (lib.equals(boutonNames[1])) { tabbedPane.setTabPlacement(SwingConstants.BOTTOM); else if (lib.equals(boutonNames[2])) { tabbedPane.setTabPlacement(SwingConstants.LEFT); else if (lib.equals(boutonNames[3])) { tabbedPane.setTabPlacement(SwingConstants.RIGHT); else if (lib.equals(boutonNames[4])) createTab(); else if(lib.equals(boutonNames[5])) killTab(); DESS ISIDIS /2004

37 JTabbedPane : exemple (5)
Panneau() { tabimage = new ImageIcon("gifs/tabimage.gif"); for (int i = 0 ; i < images.length; i++) images[i] = new ImageIcon("gifs/" + imageNames[i] +".jpg"); for (int i = 0; i < boutons.length; i++) boutons[i] = new JButton(boutonNames[i]); statut = new JLabel(); JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new GridLayout(0,1)); for (int i = 0; i < boutons.length ; i++){ boutons[i].addActionListener(this); buttonPanel.add(boutons[i]); } JPanel leftPanel = new JPanel(); leftPanel.add(buttonPanel); tabbedPane = new JTabbedPane(SwingConstants.TOP); createTab(); createTab(); createTab(); createTab(); tabbedPane.addChangeListener(new ChangeListener(){ public void stateChanged(ChangeEvent e) { setStatus(((JTabbedPane) e.getSource()).getSelectedIndex()); tabson.play(); }); setLayout(new BorderLayout()); add(leftPanel, BorderLayout.WEST); add(statut, BorderLayout.SOUTH); add(tabbedPane, BorderLayout.CENTER); DESS ISIDIS /2004

38 JTabbedPane : exemple (6)
On modifie le contenu après le chargement La méthode revalidate sert à « forcer » le réaffichage. public void init() { JLabel loading = new JLabel("Initialisation en cours...", JLabel.CENTER); setContentPane(loading); setVisible(true); getRootPane().revalidate(); try { Thread. sleep( 1000); } catch (InterruptedException e) {}; layoutson = getAudioClip(getCodeBase(), "switch.wav"); tabson = getAudioClip(getCodeBase(), "tab.wav"); Panneau panneau = new Panneau(); panneau.addAudioClips(layoutson, tabson); setContentPane(panneau); } DESS ISIDIS /2004

39 JScrollPane Gère automatiquement des ascenseurs autour de son composant central qui est un JViewPort . Constructeurs principaux Une « vue » s’ajoute au JViewPort , si elle ne l’est dans le constructeur, par JScrollPane() JScrollPane(Component view) scrollPane.getViewPort().add(view) DESS ISIDIS /2004

40 Exemple DESS ISIDIS - 2003/2004 class ScrollPanel extends JPanel {
public ScrollPanel() { setLayout(new BorderLayout()); Icon iconeTigre = new ImageIcon("BigTiger.gif"); JLabel etiquetteTigre = new JLabel(iconeTigre); JScrollPane scrollPane = new JScrollPane(etiquetteTigre); add(scrollPane, BorderLayout.CENTER); } DESS ISIDIS /2004

41 JSplitPane Panneau à compartiments, chaque compartiment est ajustable
Seule une classe de look-and-feel est nécessaire. Panneau à séparation verticale ou horizontale Constructeurs : JSplitPane(int orientation, boolean dessinContinu, Component gauche, Component droit) JSplitPane(int orientation, Component gauche, Component droit) JSplitPane(int orientation, boolean dessinContinu) JSplitPane(int orientation) JSplitPane() // horizontal par défaut DESS ISIDIS /2004

42 JSplitPane : exemple (1)
La taille de la barre de séparation peut être réglée par setDividerSize(int taille) L’affichage continue spécifié explicitement par setContinuousLayout(boolean dessinContinu) Poignée d’ouverture/ fermeture spécifiées par setOneTouchExpandable(boolean ouvrable) ImageIcon bleue = new ImageIcon("bleue.gif"); aireGauche = new PanneauBoules(150, bleue.getImage()); ImageIcon rouge = new ImageIcon("rouge.gif"); aireDroite = new PanneauBoules(150, rouge.getImage()); JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_ SPLIT, aireGauche, aireDroite); sp.setDividerSize(5); sp.setContinuousLayout(true); getContentPane().add(sp, BorderLayout.CENTER); DESS ISIDIS /2004

43 JSplitPane : exemple (2)
public class Split extends JFrame { protected PanneauBoules aireGauche, aireDroite; public Split() { ... ImageIcon bleue = new ImageIcon("bleue. gif"); aireGauche = new PanneauBoules(150, bleue.getImage()); ImageIcon rouge = new ImageIcon("rouge.gif"); aireDroite = new PanneauBoules(150, rouge.getImage()); JSplitPane sp = new JSplitPane(JSplitPane. HORIZONTAL_ SPLIT, aireGauche, aireDroite); sp.setDividerSize(5); sp.setContinuousLayout(true); getContentPane().add(sp, BorderLayout.CENTER); setVisible(true); ... new Thread(aireGauche).start(); new Thread(aireDroite).start(); } DESS ISIDIS /2004

44 JSplitPane : exemple (3)
class PanneauBoules extends JPanel implements Runnable, ComponentListener { Boule[] boules; Image img; Dimension dim; int sommeil; public PanneauBoules(int nBoules, Image img) { sommeil = 10; this.img = img; setBackground(Color.yellow); setPreferredSize(new Dimension(200, 300)); addComponentListener(this); boules = new Boule[nBoules]; dim = getPreferredSize(); for (int k= 0; k < nBoules; k++) boules[k] = new Boule(dim); } public void run() {...} DESS ISIDIS /2004

45 JSplitPane : exemple (5)
Runnable public void run() { for(;;) { for (int k = 0; k < boules.length; k++) boules[k].move(dim); repaint(); if (sommeil != 0) { try { Thread.sleep(sommeil); } catch(InterruptedException e) {} public void paintComponent(Graphics g) { g.setColor(getBackground()); g.fillRect(0, 0, dim.width, dim.height); for (int k = 0; k < boules.length; k++) g. drawImage(img,(int) boules[k].x,(int) boules[k].y, this); } DESS ISIDIS /2004

46 JSplitPane : exemple (6)
ComponentListener public void componentHidden(ComponentEvent e){} public void componentShown(ComponentEvent e){} public void componentMoved(ComponentEvent e){} public void componentResized(ComponentEvent e){ dim = getSize(); for (int k = 0; k < boules.length; k++) boules[k].moveIntoRect(dim); } DESS ISIDIS /2004

47 JSplitPane : exemple (7)
Les boules public void moveIntoRect(Dimension dim) { x = Math.max(x, 0); x = Math.min(x, dim.width); y = Math.max(y, 0); y = Math.min(y, dim.height); class Boule { protected double x, y, vx, vy; public Boule(Dimension dim) { x = dim.width * Math.random(); y = dim.height * Math.random(); double angle = 2 * Math.PI * Math.random(); vx = 2* Math.cos(angle); vy = 2* Math.sin(angle); } public void move(Dimension dim) { double nx =x +vx; double ny =y +vy; if ((nx < 0)|| (nx > dim.width)) { vx = - vx; nx = x +vx; if ((ny < 0)||( ny > dim.height)) { vy = - vy; ny = y +vy; x = nx; y = ny; DESS ISIDIS /2004

48 JTree Description hiérarchique de données Sept autre classes utilisées
TreeModel : contient les données figurant dans l’arbre TreeNode : implémentation des nœuds et de la structure d’arbre TreeSelectionModel : contient le ou les nœuds sélectionnés TreePath : un tel objet contient un chemin (de la racine vers le sommet sélectionné par exemple) TreeCellRenderer : est appelé pour dessiner un nœud TreeCellEditor : l’éditeur pour un nœud est éditable TreeUI : look-and-feel DESS ISIDIS /2004

49 JTree Un arbre est créé à partir d’un TreeModel
Il existe plusieurs modèles de sélection sélection d’un seul élément sélection de plusieurs éléments contigus sélection de plusieurs éléments disparates On peut indiquer un CellRenderer pour afficher une cellule de façon particulière. On peut indiquer un CellEditor pour changer la valeur d’une cellule interface TreeModel { ... public Object getChild(Object parent, int index); public Object getRoot(); public boolean isLeaf(Object node); } DESS ISIDIS /2004

50 JTree JTree fournit une vue du modèle
Le modèle d’arbre est en deux étapes: Le modèle des nœuds est en trois étages: Constructeurs une feuille peut recevoir des fils ? reste sans fils ? interface TreeModel class DefaultTreeModel implements TreeModel interface TreeNode interface MutableTreeNode extends TreeNode class DefaultMutableTreeNode implements MutableTreeNode JTree() JTree(TreeNode racine) JTree(TreeNode racine, boolean enfantsPermis) JTree(TreeModel modele) JTree(TreeModel modele, boolean enfantsPermis) DESS ISIDIS /2004

51 Jtree : exemple DESS ISIDIS - 2003/2004 class Arbre extends JPanel {
JTree tree; public Arbre() { DefaultMutableTreeNode top, noeud, fils, n; top = new DefaultMutableTreeNode("Top"); tree = new JTree(top); noeud = new DefaultMutableTreeNode("Repertoire 1"); top.add(noeud); n = new DefaultMutableTreeNode("1a"); noeud.add(n); n = new DefaultMutableTreeNode("1b"); noeud.add(n); ... noeud = new DefaultMutableTreeNode("Repertoire 2"); n = new DefaultMutableTreeNode("2a"); noeud.add(n); .... fils = new DefaultMutableTreeNode("2d"); noeud.add(fils); n = new DefaultMutableTreeNode("3a"); fils.add( n); } DESS ISIDIS /2004

52 JTree Le contenu d’un nœud est appelé user object C’est un objet
A l’affichage, la méthode toString() d’un nœud délègue à la méthode toString() du contenu. DESS ISIDIS /2004

53 JTree Un DefaultTreeCellRenderer s’occupe du rendu. Il peut être modifié par des fonctions utilitaires par une redéfinition DefaultTreeCellRenderer rendu ; rendu = (DefaultTreeCellRenderer) tree.getCellRenderer(); rendu.setOpenIcon(new ImageIcon("Opened.gif")); rendu.setLeafIcon(new ImageIcon("Leaf.gif")); DESS ISIDIS /2004

54 JTree Un TreeSelectionListener rapporte tous les changements dans les sélections De nombreuses fonctions utilitaires tree.addTreeSelectionListener(new Selecteur()); class Selecteur implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e ) { message.setText("Nouveau : " + e.getNewLeadSelectionPath()); } DESS ISIDIS /2004

55 JTree On parcourt un arbre par une énumération Il en existe quatre :
breadthFirstEnumeration depthFirstEnumeration postorderEnumeration preorderEnumeration public void actionPerformed(ActionEvent ev) { DefaultMutableTreeNode n, top; Enumeration e; top = (DefaultMutableTreeNode) tree.getModel().getRoot(); System.out.println("\ n En largeur"); e =top.breadthFirstEnumeration(); while (e.hasMoreElements()) { n = (DefaultMutableTreeNode) e.nextElement(); System.out.println(n.getUserObject() + " "); } DESS ISIDIS /2004

56 JTree : exemple(1) Dans cette application, on entre un nom de classe dans la zone de texte, et la classe s’insère dans la hiérarchie des classes. La classe Class permet de connaître la classe mère. On n’insère une classe que si elle n’est pas déjà dans l’arbre. DESS ISIDIS /2004

57 JTree : exemple(2) C’est addClass(Class c) qui fait l’insertion
class ClassTreeFrame extends JFrame implements ActionListener { private DefaultMutableTreeNode root; private DefaultTreeModel model; private JTree tree; private JTextField textField; public ClassTreeFrame() { setTitle("ClassTree"); root = new DefaultMutableTreeNode(Object.class); model = new DefaultTreeModel(root); tree = new JTree(model); addClass( getClass()); getContentPane().add(new JScrollPane(tree), "Center"); textField = new JTextField(); textField.addActionListener(this); getContentPane().add(textField, "South"); } ... DESS ISIDIS /2004

58 JTree : exemple(3) DESS ISIDIS - 2003/2004
public DefaultMutableTreeNode addClass(Class c) { if (c.isInterface() || c.isPrimitive()) return null; //pas les interfaces findUserObject(c) //cherche c dans tree DefaultMutableTreeNode node = findUserObject(c); if (node != null) return node; Class s = c.getSuperclass(); //classe mère DefaultMutableTreeNode parent = addClass(s); //appel récursif DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(c); model.insertNodeInto(newNode, parent, parent.getChildCount()); //à la fin //développe l’arbre pour que le nœud soit visible TreePath path = new TreePath(model.getPathToRoot(newNode)); tree.makeVisible(path); return newNode; } DESS ISIDIS /2004

59 JTree : exemple(4) Trouver un nœud dans un arbre
Un simple parcours, en largeur par exemple public DefaultMutableTreeNode findUserObject(Object obj){ Enumeration e = root.breadthFirstEnumeration(); while (e.hasMoreElements()) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); if (node.getUserObject().equals(obj)) return node; } return null; DESS ISIDIS /2004

60 JTree : exemple(5) Lire le nom de la classe On fait confiance à Java…
public void actionPerformed(ActionEvent event) { String text = textField.getText(); try { Class c = Class.forName(text); //essayons addClass(c); textField.setText(""); } catch (ClassNotFoundException e) { Toolkit.getDefaultToolkit().beep(); //si la classe n’existe pas DESS ISIDIS /2004

61 JTable JTable affiche des données dans un tableau
TableModel régit la gestion des données On peut fournir les données dans un tableau bidimensionnel d’objets : Object[][] et utiliser le DefaultTableModel, mais il vaut mieux étendre AbstractTableModel . La sélection est régi par une modèle de sélection De plus, il y a un modèle de colonnes. Un tableau est entouré d’ascenseurs, en général. DESS ISIDIS /2004

62 JTable Les constructeurs sont : DESS ISIDIS - 2003/2004
JTable() modèles par défaut pour les trois modèles JTable(int numRows, int numColumns) avec autant de cellules vides JTable(Object[][] rowData, Object[] columnNames) avec les valeurs des cellules de rowData et noms de colonnes columnNames . JTable(TableModel dm) avec le modèle de données dm , les autres par défaut. JTable(TableModel dm, TableColumnModel cm) avec modèle de données et modèle de colonnes fournis. JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) Les trois modèles sont fournis. JTable(Vector rowData, Vector columnNames) ici, les données sont fournies par colonne. DESS ISIDIS /2004

63 JTable : exemple DESS ISIDIS - 2003/2004
class TablePlanetes extends JPanel { TablePlanetes() { setLayout(new BorderLayout()); JTable table = new JTable(cellules, columnNames); add(new JScrollPane(table), BorderLayout.CENTER); } private Object[][] cellules = { { "Mercure", new Double(2440), new Integer(0), "non"}, { "Vénus", new Double(6052), new Integer(0), "non"}, { "Terre", new Double(6378), new Integer(1), "non"}, { "Mars", new Double(3397), new Integer(2), "non"}, { "Jupiter", new Double(71492), new Integer(16), "oui"}, { "Saturne", new Double(60268), new Integer(18), "oui"}, { "Uranus", new Double(25559), new Integer(17), "oui"}, { "Neptune", new Double(24766), new Integer(8), "oui"}, { "Pluton", new Double(1137), new Integer(1), "non"} }; private String[] columnNames = { "Planète", "Rayon", "Lunes", "Gazeuse"}; DESS ISIDIS /2004

64 JTable Les données sont accessible par un modèle. Ils peuvent être stockés ou calculés, de façon transparente. La classe AbstractTableModel implémente les méthodes d’un modèle de table, sauf qui retournent respectivement le nombre de lignes le nombre de colonnes l’objet à afficher dans les ligne et colonne indiquées (sa méthode toString est utilisée). public int getRowCount() public int getColumnCount() public Object getValueAt(int ligne, int colonne) DESS ISIDIS /2004

65 JTable : un premier exemple
En plus des trois méthodes obligées, la fonction getColumClass a été redéfinie, ce qui produit la justification à droite class SimpleTable extends JPanel { SimpleTable() { setLayout(new BorderLayout()); TableModel dataModel = new AbstractTableModel() { public int getColumnCount() { return 10; } public int getRowCount() { return 10;} public Object getValueAt(int row, int col) { return new Integer((1 + row)*(1 + col)); } public Class getColumnClass(int column) { return Number.class; }; JTable table = new JTable(dataModel); add(new JScrollPane(table)); DESS ISIDIS /2004

66 JTable : un deuxième exemple
Construction par avec bien entendu méthodes à écrire (plus getColumnName qui, par défaut, numérote A, B, etc.): TableModel model = new ModelInvestment(30, 5, 10); JTable table = new JTable(model); class ModelInvestment extends AbstractTableModel {...} public int getRowCount() public int getColumnCount() public Object getValueAt(int ligne, int colonne) public String getColumnName(int colonne) DESS ISIDIS /2004

67 JTable : détails DESS ISIDIS - 2003/2004
class ModelInvestment extends AbstractTableModel { private int annees; private int tauxMin; private int tauxMax; private static double depot = ; ModelInvestment(int annees, int tauxMin, int tauxMax) { this.annees = annees; this.tauxMin = tauxMin; this.tauxMax = tauxMax; } public int getRowCount() { return annees;} public int getColumnCount() { return tauxMax - tauxMin + 1;} public Object getValueAt(int ligne, int colonne) { double taux = (colonne + tauxMin) / 100.0; double depotFinal = depot * Math.pow(1 + taux, ligne); return NumberFormat.getCurrencyInstance().format(depotFinal); public String getColumnName(int colonne) { return NumberFormat.getPercentInstance().format(taux); DESS ISIDIS /2004

68 JTable La classe abstraite java.text.NumberFormat est la classe de base pour le formatage de nombres. Nombreuses méthodes statiques retournant des formats appropriés. Le formatage effectif se fait par la méthode du format Exemples de méthodes: Le format dépend de la Locale , c’est-à-dire du pays concerné. String format(int donnée) NumberFormat.getNumberInstance() NumberFormat.getCurrencyInstance() NumberFormat.getPercentInstance() DESS ISIDIS /2004

69 JTable La méthode boolean TableModel.isCellEditable(int l, int c) renvoie true si la cellule peut être modifiée (par défaut non) La méthode int JTable.columnAtPoint(Point p) renvoie l’indice de la colonne du tableau où est le point La méthode int JTable.convertColumnIndexToModel(int colonne) renvoie l’indice, dans le modèle, de l’indice colonne DESS ISIDIS /2004

70 JTable : tri par ligne (1)
On veut trier les lignes, en fonction d’une colonne désignée par un double clic. On veut que le tri se fasse sur la vue, pas sur le modèle, pour que le modèle soit inchangé. Pour cela, on introduit un filtre de modèle similaire aux filtres de streams. Ce filtre enregistre une référence au modèle réel, et intercepte les communications entre la table et son modèle pour les réinterpréter. Le filtre maintient une permutation des lignes déterminée par le tri virtuel des lignes en fonction de la colonne choisie. DESS ISIDIS /2004

71 JTable : tri par ligne (2)
Le modèle est associé à la table class TablePlanetes extends JPanel { TablePlanetes() { setLayout(new BorderLayout()); DefaultTableModel model = new DefaultTableModel(cellules, columnNames); FiltreTriModel sorter = new FiltreTriModel(model); JTable table = new JTable(sorter); sorter.addEcouteur(table); add(new JScrollPane(table), BorderLayout.CENTER); } private Object[][] cellules ={... }; private String[] columnNames = { ... }; DESS ISIDIS /2004

72 JTable : tri par ligne (3)
La classe FiltreTriModel est un modèle de table avec une référence au modèle réel un tableau de lignes une méthode de tri de ce tableau Le tri est fait par la méthode statique de la classe Arrays , en fonction de la colonne choisie. Pour cela, la classe Ligne doit implémenter l’interface Comparable . class FiltreTriModel extends AbstractTableModel { public FiltreTriModel(TableModel m) { model = m; lignes = new Ligne[model.getRowCount()]; for (int i = 0; i < lignes.length; i++) { lignes[i] = new Ligne(); lignes[i].index = i; } public void sort(int c) { colonneTri = c; Arrays.sort(lignes); fireTableDataChanged(); ... private TableModel model; private int colonneTri; private Ligne[] lignes; DESS ISIDIS /2004

73 JTable : tri par ligne (4)
La classe Ligne est interne à FiltreTriModel pour accéder facilement aux données. Une ligne est plus petite qu’une autre si son élément dans la colonne de tri est plus petit que l’élément de cette même colonne dans l’autre ligne. class FiltreTriModel extends AbstractTableModel { ... private class Ligne implements Comparable { public int index; public int compareTo(Object autre) { Ligne autreLigne = (Ligne) autre; Object cellule = model.getValueAt(index, colonneTri); Object autreCellule = model.getValueAt(autreLigne.index, colonneTri); return ((Comparable) cellule).compareTo(autreCellule); } DESS ISIDIS /2004

74 JTable : tri par ligne (5)
Les trois fonctions obligées tiennent compte de l’ordre virtuel class FiltreTriModel extends AbstractTableModel { ... public Object getValueAt(int r, int c) { return model.getValueAt(lignes[r].index, c); } public int getRowCount(){ return model.getRowCount();} public int getColumnCount(){ return model.getColumnCount();} public String getColumnName(int c){ return model.getColumnName(c);} public Class getColumnClass(int c){ return (c == 1 || c == 2) ? Number.class : Object.class; DESS ISIDIS /2004

75 JTable : tri par ligne (6)
Et le double clic C’est l’en-tête de colonne qui réagit. Au double clic sur une colonne, on récupère le numéro de la colonne dans le modèle class FiltreTriModel extends AbstractTableModel { ... public void addEcouteur(final JTable table) { table.getTableHeader().addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent event) { if (event.getClickCount() < 2) return; int tc = table.columnAtPoint(event.getPoint()); int mc = table.convertColumnIndexToModel(tc); sort(mc); } }); DESS ISIDIS /2004

76 JTable : tri par ligne (7)
Événements La classe JTable écoute les événements reçus du modèle Le modèle a plusieurs méthodes pour signaler des modifications de données Les colonnes sont régies par un TableColumnModel qui a ses propres notificateurs d’événements fireTableDataChanged() fireTableStructureChanged() fireTableRowsInverted(int first, int last) fireTableRowsUpdated(int first, int last) fireTableRowsDeleted(int first, int last) fireTableCellUpdated(int row, int col) fireTableChangedEvent(TableModelEvent e) DESS ISIDIS /2004

77 Undo/Redo Le « undo » (annuler) et « redo » (refaire) sont parmi les opérations les plus appréciées dans les interfaces ergonomiques. Ce sont des opérations difficiles à implémenter. Questions: quelles sont les transactions annulables ? quelle partie de l’environnement doit être sauvegardée pour pouvoir le reconstituer ? vaut- il mieux conserver l’opération, ou son inverse ? Java fournit un cadre surtout adapté aux opérations sur les textes. Plusieurs variantes existent, mais il reste du travail au programmeur. DESS ISIDIS /2004

78 UndoableEdit L’interface de base est UndoableEdit. Une implémentation par défaut est AbstractUndoableEdit « Edit » est synonyme de transaction ou opération, terme emprunté aux éditeurs de textes. Les méthodes sont boolean canUndo() indique que la transaction peut être annulée boolean canRedo() indique que la transaction peut être refaite void die() la transaction ne peut plus être annulée ni répétée void redo() throws CannotRedoException refait la transaction void undo() throws CannotUndoException annule la transaction DESS ISIDIS /2004

79 AbstractUndoableEdit
C’est l’implémentation par défaut de UndoableEdit Elle maintient deux booléens internes alive et done qui gèrent correctement le canUndo() et canRedo(). On sous-classe cette classe en redéfinissant undo() et redo(). On utilise la super-classe en appelant super. undo(), super.redo(). DESS ISIDIS /2004

80 Exemple des boutons à cocher
L’opération de coche ou décoche peut être annulée ou refaite, à partir d’un autre composant (paire de boutons, plus souvent entrée de menu ou boutons d’une barre d’outils) Démarche: Chaque action sur le bouton génère un objet d’une classe ToggleEdit dérivant de AbstractUndoableEdit. L’objet contient le bouton concerné l’état du bouton La classe ToggleEdit redéfinit les méthodes undo() et redo(). L’opération d’annulation ou répétition est lancée en appelant la méthode undo() ou redo() sur l’objet créé. DESS ISIDIS /2004

81 ToggleEdit DESS ISIDIS - 2003/2004 import javax.swing.undo.*;
public class ToggleEdit extends AbstractUndoableEdit { private final JToggleButton bouton; private final boolean selectionne; public ToggleEdit(JToggleButton bouton) { this.bouton = bouton; selectionne = bouton.isSelected(); } public void redo() throws CannotRedoException { super.redo(); bouton.setSelected(selectionne); public void undo() throws CannotUndoException { super.undo(); bouton.setSelected(!selectionne); DESS ISIDIS /2004

82 Le panneau Le panneau est composé de trois boutons à cocher
et de deux boutons d’annulation et répétition: chaque bouton à cocher (JCheckBox) a un écouteur dont la méthode actionPerformed est : JCheckBox gras = new JCheckBox("gras"); JCheckBox ital = new JCheckBox("italique"); JCheckBox soul = new JCheckBox("souligné"); JButton undoButton = new JButton("Undo"); JButton redoButton = new JButton("Redo"); undoButton.addActionListener(new UndoIt()); redoButton.addActionListener(new RedoIt()); public void actionPerformed(ActionEvent ev) { JToggleButton b = (JToggleButton) ev.getSource(); edit = new ToggleEdit(b); updateButtons(); } DESS ISIDIS /2004

83 Les listeners DESS ISIDIS - 2003/2004
class UndoIt implements ActionListener { public void actionPerformed(ActionEvent ev) { try { edit.undo(); } catch (CannotUndoException ex) {} finally { updateButtons(); } class RedoIt implements ActionListener { edit.redo(); } catch (CannotRedoException ex) {} private void updateButtons() { undoButton.setText(edit.getUndoPresentationName()); redoButton.setText(edit.getRedoPresentationName()); undoButton.setEnabled(edit.canUndo()); redoButton.setEnabled(edit.canRedo()); } DESS ISIDIS /2004

84 Complément L’interface UndoableEdit a une méthode getPresentationName qui retourne une chaîne de caractère modifiable en fonction de la transaction Les méthodes getUndoPresentationName et getRedoPresentationName concatènent le préfix Undo et Redo avec la chaîne fournie par getPresentationName class ToggleEdit { private final JToggleButton bouton; private final boolean selectionne; ... public String getPresentationName() { return "\"" + bouton.getText() + (selectionne ? " on" : " off") + "\""; } DESS ISIDIS /2004

85 Séquences de transactions
Pour se « souvenir » d’une séquence de transactions, et pouvoir revenir en arrière arbitrairement loin, on utilise un gestionnaire de transactions (UndoManager). Un UndoManager gère les transactions (Edit). Il permet de reculer (undo) et d’avancer (redo) tant que possible. Une transaction à inscrire dans un gestionnaire doit lui être notifiée, soit directement, par addEdit(UndoableEdit edit) soit en utilisant le fait qu’un UndoManager implémente un UndoableEditListener. On enregistre le gestionnaire dans la liste des auditeurs. DESS ISIDIS /2004

86 Implémentation simple
Un UndoManager étend CompoundEdit qui lui étend AbstractUndoableEdit On remplace simplement la variable UndoEdit edit par UndoManager manager et on modifie actionPerformed() en conséquence DESS ISIDIS /2004

87 Modifications DESS ISIDIS - 2003/2004
class TogglePanel extends Jpanel implements ActionListener { private UndoManager manager = new UndoManager(); private JButton undoButton, ...; public void actionPerformed(ActionEvent e) { JToggleButton b = (JToggleButton) e.getSource(); manager.addEdit(new ToggleEdit(b)); updateButtons(); } class UndoIt implements ActionListener { public void actionPerformed(ActionEvent e){ try { manager.undo(); }... private void updateButtons() { undoButton.setText(manager.getUndoPresentationName()); ... DESS ISIDIS /2004

88 Un deuxième exemple Le programme de départ affiche, au clic de souris, un carré ou un cercle, selon que la touche majuscule n’est pas ou est enfoncé. La séquence des formes engendrées est enregistrée dans un vecteur en vue d’un affichage facile. Comment l’adapter au undo/redo ? class SimplePaint extends JPanel { protected Vector formes = new Vector(); protected PaintCanvas canvas =new PaintCanvas(formes); protected int width = 50; protected int height = 50; public SimplePaint() { setLayout(new BorderLayout()); add(new Label("Do it", Label.CENTER), BorderLayout.NORTH); add(canvas, BorderLayout.CENTER); canvas.addMouseListener(new AjouterForme()); } ... DESS ISIDIS /2004

89 Les formes et le canvas DESS ISIDIS - 2003/2004
class AjouterForme extends MouseAdapter { public void mousePressed(MouseEvent e) { Shape s; if (e.isShiftDown()) s = new Ellipse2D.Double(e.getX(), e.getY(), width, height); else s = new Rectangle2D.Double(e.getX(), e.getY(), width, height); formes.addElement(s); canvas.repaint(); } class PaintCanvas extends JPanel { Vector formes; ... public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; super.paintComponent(g2); g2.setColor(Color.black); Enumeration enum = formes.elements(); while (enum.hasMoreElements()) { Shape shape = (Shape) enum.nextElement(); g2.draw(shape); } DESS ISIDIS /2004

90 Ajouter undo/redo (1) Comme pour l’exemple précédent
deux boutons « undo » et « redo » deux listeners d’actions, un sur chaque bouton Création d’une classe FormeEdit pour les transactions, et de deux classes dérivées. DESS ISIDIS /2004

91 Ajouter undo/redo (2) DESS ISIDIS - 2003/2004
class FormeEdit extends AbstractUndoableEdit { protected Vector formes; protected Shape shape; public FormeEdit(Shape shape, Vector formes) { this.formes = formes; this.shape = shape; } public void undo() { super.undo(); formes.remove(shape); public void redo() { super.redo(); formes.add(shape); class CarreEdit extends FormeEdit { public CarreEdit(Shape s, Vector v) { super(s, v); } public String getPresentationName() { return "carré"; DESS ISIDIS /2004

92 AjouterForme DESS ISIDIS - 2003/2004
public void mousePressed(MouseEvent e) { Shape shape; UndoableEdit edit; if (e.isShiftDown()) { shape = new Ellipse2D.Double(e.getX(), e.getY(), width, height); edit = new CercleEdit(shape, formes); } else { shape = new Rectangle2D.Double(e.getX(), e.getY(), width, height); edit = new CarreEdit(shape, formes); formes.addElement(shape); manager.addEdit(edit); canvas.repaint(); updateButtons(); DESS ISIDIS /2004

93 Événements Il existe une classe spécifique UndoableEditEvent
Un tel événement comporte une source, et un UndoableEdit Les auditeurs sont de l’interface UndoableEditListener, avec la méthode undoableEditHappened(UndoableEditEvent e). UndoManager implémente UndoableEditListener, avec la méthode public void undoableEditHappened( UndoableEditEvent e){ addEdit( e.getEdit()) } Usage : ... UndoableEdit edit; edit = new CercleEdit(shape, formes); UndoableEditEvent ue; ue = new UndoableEditEvent(this, edit); manager. undoableEditHappened(ue); DESS ISIDIS /2004

94 Mais… Il manque une objet qui lance des UndoableEditEvent ’s. L’implémentation précédente fait comme si, la présente le fait. Seuls les documents de textes sont capables, pour l’instant, d’en lancer. Lançons cela pour JPanel : class PaintCanvas extends JPanel { ... Class uel = UndoableEditListener.class; public void addUndoableEditListener(UndoableEditListener listener) { listenerList.add(uel , listener); } public void removeUndoableEditListener(UndoableEditListener listener) { listenerList.remove(uel , listener); public void fireUndoableEditUpdate(UndoableEditEvent e) { Object[] l = listenerList.getListenerList(); for (int i = l.length - 2; i >= 0; i -= 2) { if (l[i] == uel ) ((UndoableEditListener) l[i + 1]).undoableEditHappened(e); DESS ISIDIS /2004

95 …et donc Un UndoManager est un listener parfait
Il ne reste plus qu’à lancer les événements public FireUndo() { ... canvas.addMouseListener(new AjouterForme()); canvas.addUndoableEditListener(manager); } public void mousePressed(MouseEvent e) { Shape shape; UndoableEdit edit; shape = ... edit = ... formes.addElement(shape); UndoableEditEvent ue = new UndoableEditEvent(this, edit); canvas.fireUndoableEditUpdate(ue); canvas.repaint(); updateButtons(); } DESS ISIDIS /2004

96 UndoableEditSupport Pour faciliter la vie aux programmeurs (en attendant que les choses se simplifient), Java propose une classe utilitaire de gestion de listeners et d’envoi d’événements, les UndoableEditSupport . Ils réalisent pour l’essentiel ce qui a été programmé en dur. class PaintCanvas extends JPanel { UndoableEditSupport support = new UndoableEditSupport(); ... public void addUndoableEditListener(UndoableEditListener listener) { support.addUndoableEditListener(listener); } public void removeUndoableEditListener(UndoableEditListener listener) { support.removeUndoableEditListener(listener); public void postEdit(UndoableEdit e) {// le fireUndoableEditUpdate.... support.postEdit(e); DESS ISIDIS /2004

97 UndoableEditSupport (fin)
Au lieu de lancer les événements, on poste les Edit : public void mousePressed(MouseEvent e) { UndoableEdit edit; edit = ... canvas.postEdit(edit); ... } DESS ISIDIS /2004

98 Dans les textes Dans les textes, ça va tout seul.
JTextArea editor = new JTextArea(); public UndoRedoText() { editor.getDocument().addUndoableEditListener(new ManageIt()); undoButton.addActionListener(new UndoIt()); redoButton.addActionListener(new RedoIt()); } class ManageIt implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { manager.undoableEditHappened( e); updateButtons(); DESS ISIDIS /2004

99 États Dans les exemples précédents, on conservait explicitement l’état après modification. Un tel procédé n’est pas suffisant dans de nombreuses situations, comme dans un groupe de boutons radio. Java propose une forme générale d’état appelé StateEdit. Tout objet dont la classe implémente l’interface StateEditable peut sauvegarder son état avant et après modification dans l’état, et ainsi le récupérer. Mieux, la prise en compte de l’état de départ et de l’état d’arrivée peut être programmée, permettant ainsi de cumuler des modifications. DESS ISIDIS /2004

100 États : description Un StateEdit est créé par
La classe de unObjet implémente l’interface StateEditable. Un StateEdit contient en interne une table de hachage (en fait deux). Les méthodes de StateEditable permettent de sauvegarder et de récupérer les données à conserver. La sauvegarde débute à la création, et s’arrête par la méthode end() de StateEdit. StateEdit etat = new StateEdit(unObjet); public void storeState( Hashtable h); public void restoreState( Hashtable h); DESS ISIDIS /2004

101 États : structure de l’exemple
class TogglePanel extends JPanel implements ActionListener, StateEditable { StateEdit etat; JButton undoButton, redoButton, chooseButton, endButton; JRadioButton gras, ital, soul; ButtonGroup polices; public TogglePanel() { // installer les composants; chooseButton.addActionListener(new ChooseIt()); endButton.addActionListener(new EndIt()); undoButton.addActionListener(new UndoIt()); redoButton.addActionListener(new RedoIt()); } public void storeState(Hashtable h) {...} public void restoreState(Hashtable h) {...} class ChooseIt implements ActionListener {...} class EndIt implements ActionListener {...} class UndoIt implements ActionListener {...} class RedoIt implements ActionListener {...} DESS ISIDIS /2004

102 États : fin de l’exemple
class ChooseIt implements ActionListener { public void actionPerformed(ActionEvent ev) { etat = new StateEdit(TogglePanel.this); updateButtons(); } Trois boutons radio sont donnés. A partir de Choose , on démarre l’enregistrement des modifications, jusqu’à l’activation du bouton Ok . Un Undo restitue l’état initial, et un Redo revient à l’état final. public void storeState(Hashtable h) { h. put(polices, polices.getSelection()); } public void restoreState(Hashtable h) { ButtonModel b = (ButtonModel) h.get(polices); b.setSelected(true); class UndoIt implements ActionListener { public void actionPerformed(ActionEvent ev) { try { etat.undo(); } catch (CannotUndoException ex) {} updateB(); } DESS ISIDIS /2004

103 Le squelette d’un éditeur de texte
Objectifs Présenter des interactions d’un composant de texte avec son environnement Ouverture et sauvegarde des fichiers Couper-coller Undo-Redo L’important est la cohérence de l’environnement Entrées des menus activables seulement si cela a un sens « Aide implicite » que cela apporte En revanche, on ignore le style du texte lui- même Style des paragraphes Polices de caractères DESS ISIDIS /2004

104 Cahier des charges Éditeur SDI (single document interface)
un seul document présent une seule fenêtre de manipulation du texte Autres modèles une seule fenêtre, plusieurs documents plusieurs fenêtres, une par document Commandes de manipulation de documents nouveau, ouvrir, sauver, sauver sous Commandes de manipulation du texte copier-couper, coller, tout sélectionner annuler-rétablir Présentation de ces commandes sous forme menu - toolbar - raccourcis clavier DESS ISIDIS /2004

105 Textes et documents Classes de textes Classes de documents
| +-- javax.swing JComponent +-- javax.swing.text.JTextComponent +-- javax.swing.JTextArea +-- javax.swing.JTextField +-- javax.swing.JEditorPane +-- javax.swing.JTextPane java. lang. Object | +-- javax.swing.text.AbstractDocument implements Document | | +-- javax.swing.text.PlainDocument | extends +-- javax.swing.text.DefaultStyledDocument implements StyledDocument +-- javax.swing.text.html.HTMLDocument DESS ISIDIS /2004

106 Document / Vue Un composant de texte présente une vue d’un document.
TextArea et TextField associés au PlainDocument TextPane associé à StyledDocument Correspondance JTextArea editor; Document document = editor.getDocument(); editor.setDocument(new PlainDocument()); DESS ISIDIS /2004

107 Écouter le texte Via le document, insertion, suppression, remplacement
Un DocumentListener implémente trois méthodes appelées après modification d’un attribut, insertion, suppression. Document document = editor.getDocument(); document.addDocumentListener( un listener ); public void changedUpdate (DocumentEvent e); public void insertUpdate (DocumentEvent e); public void removeUpdate (DocumentEvent e); DESS ISIDIS /2004

108 Sélection DESS ISIDIS - 2003/2004
On peut pister les déplacements du point d’insertion (caret) par un CaretListener Un CaretListener possède une méthode caretUpdate appelée chaque fois que le point d’insertion bouge Un CaretEvent fournit deux méthodes getDot() qui donne le point actuel getMark() qui donne le point précédent Un mouvement de souris, avec bouton enfoncé, ne provoque pas d’évènement, mais provoque un évènement quand on relâche le bouton Caret caret = editor.getCaret(); caret.addCaretListener( un CaretListener ); public void caretUpdate( CaretEvent e) { int now = e. getDot(); int before = e. getMark(); boolean nowSelected = now != before; ... } DESS ISIDIS /2004

109 Manipulations de texte
Manipulations de texte prédéfinies (sont en fait des méthodes de JTextComponent) : les dernières transfèrent dans le presse- papier système. Le DefaultEditorKit prédéfinit une trentaine d’actions sur les composants de textes. void editor.cut(); void editor.copy(); void editor.paste(); void editor.selectAll(); Clipboard clip = Toolkit. getDefaultToolkit(). getSystemClipboard(); DESS ISIDIS /2004

110 Vue d’ensemble Le texte Les actions Les menus et la barre d’outils
une seule zone de texte (JTextArea) le document associé ne change pas, sauf pour la commande « nouveau ». Les actions chaque action (Action) (nouveau,…, tout sélectionner) est implémentée dans une classe séparée Les menus et la barre d’outils construits à partir des actions Les gestionnaires de cohérence de la cohérence des menu : une EditMenuManager de la cohérence des undo-redo : un UndoHandler de sauvegarde de documents modifiés : une StatusBar. DESS ISIDIS /2004

111 Composants Composants de la vue Composants de la gestion
JTextComponent editor; JMenuBar menubar; JToolBar toolbar; StatusBar status; File currentFile = null; JFileChooser selecteurFichier; UndoHandler undoHandler; EditMenuManager editMenuManager; DESS ISIDIS /2004

112 Les Actions Une action… par action ! DESS ISIDIS - 2003/2004
Action undoAction = new UndoAction(); Action redoAction = new RedoAction(); Action newAction = new NewAction(); Action openAction = new OpenAction(); Action saveAction = new SaveAction(); Action saveAsAction = new SaveAsAction(); Action exitAction = new ExitAction(); Action cutAction = new CutAction(); Action copyAction = new CopyAction(); Action pasteAction = new PasteAction(); Action selectAllAction = new SelectAllAction(); DESS ISIDIS /2004

113 UndoAction Premier exemple : undoAction DESS ISIDIS - 2003/2004
class UndoAction extends AbstractAction { public UndoAction() { super("Undo", new ImageIcon("gifs/undo.gif")); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undoHandler.undo(); } catch (CannotUndoException ex) {} undoHandler.update(); DESS ISIDIS /2004

114 UndoHandler Il gère les undo, mais aussi l’état des boutons !
class UndoHandler extends UndoManager { public void undoableEditHappened(UndoableEditEvent e) { super.addEdit(e.getEdit()); update(); } public void update() { undoAction.setEnabled(canUndo()); redoAction.setEnabled(canRedo()); DESS ISIDIS /2004

115 CutAction Couper implique mettre dans la corbeille
mettre à jour les boutons class CutAction extends AbstractAction { CutAction() { super("Cut", new ImageIcon("gifs/cut.gif")); } public void actionPerformed(ActionEvent e) { getEditor().cut(); // texte editMenuManager.doCut(); // boutons DESS ISIDIS /2004

116 EditMenuManager Il gère les transitions entre les 4 états du menu
la mise-à-jour de la vue (menu et toolbar) par la fonction update() class EditMenuManager implements CaretListener { int state; static final int EMPTY = 0, CUTCOPY = 1, PASTE = 2, FULL = 3; void doInitial() {...} void doCopy() {...} void doCut() {...} void doPaste() {...} void doSelected() {...} void doDeselected() {...} } DESS ISIDIS /2004

117 EditMenuManager (suite)
Après une sélection : Après un copy : void doSelected() { if (state == EMPTY) state = CUTCOPY; else if (state == PASTE) state = FULL; updateEnables(state); } void doCopy() { if (state == CUTCOPY) { state = FULL; updateEnables(state); } DESS ISIDIS /2004

118 EditMenuManager (suite)
C’est aussi un CaretListener , pour écouter les sélections public void caretUpdate(CaretEvent e) { int now = e.getDot(); int before = e.getMark(); boolean nowSelected = now != before; if (nowSelected) doSelected(); else doDeselected(); } DESS ISIDIS /2004

119 EditMenuManager (fin)
La mise- à- jour des boutons est paresseuse public void updateEnables(int state) { switch (state) { case EMPTY : cutAction.setEnabled(false); copyAction.setEnabled(false); pasteAction.setEnabled(false); break; case CUTCOPY: cutAction.setEnabled(true); copyAction.setEnabled(true); case PASTE: ... case FULL: ... } DESS ISIDIS /2004

120 Ouvrir un fichier Il faut
s’assurer que le fichier courant n’est pas modifié s’il est modifié, demander une éventuelle sauvegarde ouvrir un dialogue de choix de fichier lire ce fichier Ces opérations sont assumées par la méthode actionPerformed() class OpenAction extends AbstractAction { OpenAction() { super("Ouvrir...", new ImageIcon("gifs/open. gif")); } public void actionPerformed(ActionEvent e) {...} DESS ISIDIS /2004

121 Ouvrir un fichier (suite)
public void actionPerformed(ActionEvent e) { if (!isConfirmed( "Voulez vous sauver le texte courant\ n"+ " avant d'ouvrir un autre fichier ?", "Sauver avant d'ouvrir ?")) return; int answer = selecteurFichier.showOpenDialog(frame); if (answer != JFileChooser.APPROVE_ OPTION) return; currentFile = selecteurFichier.getSelectedFile(); try { FileReader in = new FileReader(currentFile); getEditor().read(in, null); in.close(); } catch (IOException ex) { ex.printStackTrace(); } status.setSaved(); frame.setTitle(currentFile.getName()); DESS ISIDIS /2004

122 Ouvrir un fichier (fin)
boolean isConfirmed(String question, String titre) { if (! status.isModified()) return true; int reponse = JOptionPane.showConfirmDialog(null, question, titre, JOptionPane.YES_ NO_ CANCEL_ OPTION); switch(reponse) { case JOptionPane.YES_ OPTION:{ saveAction.actionPerformed(null); return !status.isModified(); } case JOptionPane.NO_ OPTION: case JOptionPane.CANCEL_ OPTION: return false; DESS ISIDIS /2004

123 État du document Il n’existe pas de fonction qui indique une modification du document StatusBar assume ce rôle… class StatusBar extends JPanel implements DocumentListener { boolean modStatus = false; // true = modified; public boolean isModified() { return modStatus; } public void changedUpdate(DocumentEvent ev) { setModified();} public void insertUpdate(DocumentEvent ev) { setModified();} public void removeUpdate(DocumentEvent ev) { setModified();} public void setSaved() { modStatus = false; getEditor().getDocument().addDocumentListener(this); saveAction.setEnabled(false); } public void setModified() { modStatus = true; getEditor().getDocument().removeDocumentListener(this); saveAction.setEnabled(true); DESS ISIDIS /2004

124 Les menus Dans le menu « Fichier », on ajoute des raccourcis
protected JMenuBar createMenubar() { JMenuBar mb = new JMenuBar(); JMenu menu; JMenuItem item; menu = new JMenu("Fichier"); item = menu. add(newAction); item.setIcon(null); item.setMnemonic('N'); item = menu. add(openAction); item.setIcon(null); item.setMnemonic('O'); ... menu.addSeparator(); item = menu.add(exitAction); mb. add(menu); return mb; } DESS ISIDIS /2004

125 La barre d’outils On ajoute les tooltips, des espaces et de la glue
private JToolBar createToolbar() { JButton b; JToolBar tb = new JToolBar(); b = tb.add(newAction); b.setText(null); b.setToolTipText("nouveau"); ... tb.add(Box.createHorizontalStrut(10)); b = tb.add(copyAction); b.setToolTipText("copier"); tb.add(Box.createHorizontalGlue()); return tb; } DESS ISIDIS /2004


Télécharger ppt "Jean-Christophe Soulié"

Présentations similaires


Annonces Google