DESS ISIDIS - 2003/2004 2
Plan Gestion des évènements MVC Les observeurs Le réflexion Swing Le squelette d’un éditeur de texte
DESS ISIDIS - 2003/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 - 2003/2004 4
Exemple Une classe de boutons
class MonBouton extends Button {int incr;
MonBouton(String title, int incr) {super(title);this.incr = incr;
}
int getIncr() { return incr}}
Une classe d’étiquettes auditricesclass 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 - 2003/2004 5
Exemple (fin) Le cadre
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 fermerclass WindowCloser extends WindowAdapter {
public void windowClosing(WindowEvent evt) {System.exit(0);
}}
DESS ISIDIS - 2003/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 - 2003/2004 7
Exemple (1)
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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/2004 16
Observeurs : Exemple (4) Trace de l’exécution
$ java TestObserver "Voila ma phrase, ma courte phrase." J'observe l'objet : TableObservable@80ca7b7 qui m'envoie : Voila J'observe l'objet : TableObservable@80ca7b7 qui m'envoie : ma J'observe l'objet : TableObservable@80ca7b7 qui m'envoie : phrase J'observe l'objet : TableObservable@80ca7b7 qui m'envoie : ma J'observe l'objet : TableObservable@80ca7b7 qui m'envoie : courte J'observe l'objet : TableObservable@80ca7b7 qui m'envoie : phrase ==> 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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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);}
}}
DESS ISIDIS - 2003/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 - 2003/2004 24
Swing – l’arbre d’héritage
DESS ISIDIS - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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éesImageIcon tabimage; //l’icône dans les ongletsJTabbedPane tabbedPane; //le panneau à feuillesString[] boutonNames = {"TOP","BOTTOM","LEFT","RIGHT","add","remove"};JButton[] boutons = new JButton[boutonNames.length]; //les boutons de gestionJLabel statut; //le message d’étatAudioClip 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 - 2003/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 - 2003/2004 36
JTabbedPane : exemple (4) Les actions des boutons La classe SwingConstants contient les constantes de
placementpublic 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);layoutson.play();
}else if (lib.equals(boutonNames[2])) {
tabbedPane.setTabPlacement(SwingConstants.LEFT);layoutson.play();
}else if (lib.equals(boutonNames[3])) {
tabbedPane.setTabPlacement(SwingConstants.RIGHT);layoutson.play();
}else if (lib.equals(boutonNames[4]))
createTab();else if(lib.equals(boutonNames[5]))
killTab();}
DESS ISIDIS - 2003/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 - 2003/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 - 2003/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 - 2003/2004 40
Exemple
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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/2004 47
JSplitPane : exemple (7) Les boules
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;
}}
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);
DESS ISIDIS - 2003/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 - 2003/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 celluleinterface TreeModel {
...public Object getChild(Object parent, int index);public Object getRoot();public boolean isLeaf(Object node);...
}
DESS ISIDIS - 2003/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 TreeModelclass DefaultTreeModel implements TreeModel
interface TreeNodeinterface MutableTreeNode extends TreeNodeclass DefaultMutableTreeNode implements MutableTreeNode
JTree()JTree(TreeNode racine)JTree(TreeNode racine, boolean enfantsPermis)JTree(TreeModel modele)JTree(TreeModel modele, boolean enfantsPermis)
DESS ISIDIS - 2003/2004 51
Jtree : exempleclass 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");top.add(noeud);n = new DefaultMutableTreeNode("2a"); noeud.add(n);....fils = new DefaultMutableTreeNode("2d"); noeud.add(fils);n = new DefaultMutableTreeNode("3a"); fils.add( n);
}...
}
DESS ISIDIS - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/2004 58
JTree : exemple(3)public DefaultMutableTreeNode addClass(Class c) {
if (c.isInterface() || c.isPrimitive()) return null; //pas les interfaces
findUserObject(c) //cherche c dans treeDefaultMutableTreeNode 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 visibleTreePath path = new TreePath(model.getPathToRoot(newNode));tree.makeVisible(path);return newNode;
}
DESS ISIDIS - 2003/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 - 2003/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); //essayonsaddClass(c);textField.setText("");
}catch (ClassNotFoundException e) {
Toolkit.getDefaultToolkit().beep(); //si la classe n’existe pas
}}
DESS ISIDIS - 2003/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 - 2003/2004 62
JTable Les constructeurs sont :
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 - 2003/2004 63
JTable : exemple
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 - 2003/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 - 2003/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 - 2003/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 - 2003/2004 67
JTable : détailsclass ModelInvestment extends AbstractTableModel {
private int annees;private int tauxMin;private int tauxMax;private static double depot = 100000.0;
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) {
double taux = (colonne + tauxMin) / 100.0;return NumberFormat.getPercentInstance().format(taux);
}}
DESS ISIDIS - 2003/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é.
NumberFormat.getNumberInstance()NumberFormat.getCurrencyInstance()NumberFormat.getPercentInstance()
String format(int donnée)
DESS ISIDIS - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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èleclass 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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/2004 81
ToggleEdit
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 - 2003/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 - 2003/2004 83
Les listenersclass UndoIt implements ActionListener {
public void actionPerformed(ActionEvent ev) {try {
edit.undo();} catch (CannotUndoException ex) {}finally {
updateButtons();}
}}class RedoIt implements ActionListener {
public void actionPerformed(ActionEvent ev) {try {
edit.redo();} catch (CannotRedoException ex) {}finally {
updateButtons();}
}}
private void updateButtons() {undoButton.setText(edit.getUndoPresentationName());redoButton.setText(edit.getRedoPresentationName());undoButton.setEnabled(edit.canUndo());redoButton.setEnabled(edit.canRedo());
}
DESS ISIDIS - 2003/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 - 2003/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 - 2003/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 - 2003/2004 87
Modifications
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 - 2003/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 - 2003/2004 89
Les formes et le canvasclass 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 - 2003/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 - 2003/2004 91
Ajouter undo/redo (2)
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 - 2003/2004 92
AjouterForme
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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/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 - 2003/2004 102
États : fin de l’exemple 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.
class ChooseIt implements ActionListener {public void actionPerformed(ActionEvent ev) {
etat = new StateEdit(TogglePanel.this);updateButtons();
}}
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 - 2003/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 - 2003/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 - 2003/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 - 2003/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
CorrespondanceJTextArea editor;Document document = editor.getDocument();editor.setDocument(new PlainDocument());
DESS ISIDIS - 2003/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 - 2003/2004 108
Sélection 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 - 2003/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 - 2003/2004 110
Vue d’ensemble Le texte
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 - 2003/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 - 2003/2004 112
Les Actions Une action… par action !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 - 2003/2004 113
UndoAction Premier exemple : undoAction
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 - 2003/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 - 2003/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(); // texteeditMenuManager.doCut(); // boutons
}}
DESS ISIDIS - 2003/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 - 2003/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 - 2003/2004 118
EditMenuManager (suite) C’est aussi un CaretListener ,
pour écouter les sélectionspublic void caretUpdate(CaretEvent e) {
int now = e.getDot();int before = e.getMark();boolean nowSelected = now != before;if (nowSelected)
doSelected();else
doDeselected();}
DESS ISIDIS - 2003/2004 119
EditMenuManager (fin) La mise- à- jour des boutons est
paresseusepublic 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);pasteAction.setEnabled(false);break;
case PASTE: ...case FULL: ...
}}
DESS ISIDIS - 2003/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 - 2003/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 - 2003/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:
return true;case JOptionPane.CANCEL_ OPTION:
return false;}return false;
}
DESS ISIDIS - 2003/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 - 2003/2004 124
Les menus Dans le menu « Fichier », on ajoute des
raccourcisprotected 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 - 2003/2004 125
La barre d’outils On ajoute les tooltips, des espaces et de la
glueprivate 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.setText(null);b.setToolTipText("copier");...tb.add(Box.createHorizontalGlue());return tb;
}