Télécharger la présentation
La présentation est en train de télécharger. S'il vous plaît, attendez
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
Présentations similaires
© 2024 SlidePlayer.fr Inc.
All rights reserved.