Nous allons analyser un exemple fourni avec le JDK qui montre comme créer des boutons au look particulier (OpenLook) en partant de la classe Component. Vous pouvez vous faire une idée de l'apparence de ces boutons en ouvrant la page OpenlookTest.html.
Lors de la création d'un composant à partir de la classe Component nous avons à gérer plusieurs problèmes :
La classe OpenLookButon que nous créons sera dérivée de la classe
Component. Elle utilisera :
- un champ label qui contiendra le texte
affiché sur le bouton,
- un champ privé pressed qui indiquera si le
bouton est pressé ou non,
- un champ actionListener qui contiendra l'écouteur
associé au bouton.
D'autre part une constante capWidth sera utilisée comme rayon
des arcs utilisés pour le dessin du bouton.
import java.util.*; import java.awt.*; import java.awt.event.*; public class OpenlookButton extends Component { static int capWidth = 20; // The width of the Button's endcap String label; // The Button's text protected boolean pressed = false; // true if the button is detented. ActionListener actionListener; // Post action events to listeners /** * Constructs an OpenlookButton with no label. */ public OpenlookButton() { this(""); } /** * Constructs an OpenlookButton with the specified label. * @param label the label of the button */ public OpenlookButton(String label) { this.label = label; enableEvents(AWTEvent.MOUSE_EVENT_MASK); } }
Deux constructeurs sont définis. Il faudra utiliser le second qui permet d'initialiser le champ label. Ce constructeur définit le type d'évènements à gérer en utilisant la méthode enableEvents.
L'accès au champ label se fera par l'intermédiaire des méthodes getLabel et setLabel.
/** * gets the label * @see setLabel */ public String getLabel() { return label; } /** * sets the label * @see getLabel */ public void setLabel(String label) { this.label = label; invalidate(); repaint(); }
Notons que la méthode setLabel fait appel aux méthodes invalidate et repaint. En effet un changement du libellé du bouton doit provoquer un redimensionnement (d'où invalidate) et un réaffichage du bouton (d'où repaint).
La méthode paint va utiliser 3 couleurs nommées interior, highlight1 et highlight2 qui serviront à dessiner le fond et la bordure. La couleur interior est la couleur de fond du composant qui pourra être définie par setBackground. Les deux autres couleurs sont dérivées de interior en utilisant les méthodes darker et brighter de la classe Color. Le choix de ces couleurs va dépendre de l'état du bouton (pressé ou non) pour donner l'impression d'ombrage correspondante.
/** * paints the button */ public void paint(Graphics g) { int width = getSize().width - 1; int height = getSize().height - 1; Color interior; Color highlight1; Color highlight2; interior = getBackground(); // ***** determine what colors to use if(pressed) { highlight1 = interior.darker(); highlight2 = interior.brighter(); } else { highlight1 = interior.brighter(); highlight2 = interior.darker(); } // ***** paint the interior of the button g.setColor(interior); // left cap g.fillArc(0, 0, // start capWidth, height, // size 90, 180); // angle // right cap g.fillArc(width - capWidth, 0, // start capWidth, height, // size 270, 180); // angle // inner rectangle g.fillRect(capWidth/2, 0, width - capWidth, height); // ***** highlight the perimeter of the button // draw upper and lower highlight lines g.setColor(highlight1); g.drawLine(capWidth/2, 0, width - capWidth/2, 0); g.setColor(highlight2); g.drawLine(capWidth/2, height, width - capWidth/2, height); // upper arc left cap g.setColor(highlight1); g.drawArc(0, 0, // start capWidth, height, // size 90, 180-40 // angle ); // lower arc left cap g.setColor(highlight2); g.drawArc(0, 0, // start capWidth, height, // size 270-40, 40 // angle ); // upper arc right cap g.setColor(highlight1); g.drawArc(width - capWidth, 0, // start capWidth, height, // size 90-40, 40 // angle ); // lower arc right cap g.setColor(highlight2); g.drawArc(width - capWidth, 0, // start capWidth, height, // size 270, 180-40 // angle ); // ***** draw the label centered in the button Font f = getFont(); if(f != null) { FontMetrics fm = getFontMetrics(getFont()); g.setColor(getForeground()); g.drawString(label, width/2 - fm.stringWidth(label)/2, height/2 + fm.getHeight()/2 - fm.getMaxDescent() ); } }
Nous pouvons remarquer l'utilisation de la classe FontMetrics pour calculer les dimensions du texte et obtenir un affichage centré.
Deux méthodes donnent les dimensions minimales et optimales.
/** * The preferred size of the button. */ public Dimension getPreferredSize() { Font f = getFont(); if(f != null) { FontMetrics fm = getFontMetrics(getFont()); return new Dimension(fm.stringWidth(label) + capWidth*2, fm.getHeight() + 10); } else { return new Dimension(100, 50); } } /** * The minimum size of the button. */ public Dimension getMinimumSize() { return new Dimension(100, 50); }
Les dimensions minimales sont fixées à 100 sur 50. Les dimensions optimales sont calculées en fonction de la dimension du texte en tenant compte des demi-cercles et d'une marge.
Nous devons écrire deux méthodes qui permettent de déclarer ou de supprimer l'écouteur vers lequel les évènements ActionEvent générés par le bouton devront être envoyés. Cela se fait en surchargeant les méthodes addActionListener et removeActionListener.
/** * Adds the specified action listener to receive action events * from this button. * @param listener the action listener */ public void addActionListener(ActionListener listener) { actionListener = AWTEventMulticaster.add(actionListener, listener); enableEvents(AWTEvent.MOUSE_EVENT_MASK); } /** * Removes the specified action listener so it no longer receives * action events from this button. * @param listener the action listener */ public void removeActionListener(ActionListener listener) { actionListener = AWTEventMulticaster.remove(actionListener, listener); }
Ces méthodes mettent à jour le champ actionListener de notre bouton et font état des choix au système de transmission des évènements en utilisant les méthodes AWTEventMulticaster.add et AWTEventMulticaster.remove.
La dernière méthode à écrire concerne la gestion des évènements
souris en surchargeant la méthode processMouseEvent. Il faudra
effectuer les opérations suivantes :
- au moment où on appuie sur le bouton (évènement MOUSE_PRESSED)
on met le champ pressed à true et on redessine le bouton
- au moment où on relache le bouton (évènement MOUSE_RELEASED)
on appelle la méthode actionPerformed de l'écouteur s'il est défini,
puis on met le champ pressed à false et on redessine.
/** * Paints the button and sends an action event to all listeners. */ public void processMouseEvent(MouseEvent e) { Graphics g; switch(e.getID()) { case MouseEvent.MOUSE_PRESSED: // render myself inverted.... pressed = true; // Repaint might flicker a bit. To avoid this, you can use // double buffering (see the Gauge example). repaint(); break; case MouseEvent.MOUSE_RELEASED: if(actionListener != null) { actionListener.actionPerformed(new ActionEvent( this, ActionEvent.ACTION_PERFORMED, label)); } // render myself normal again if(pressed == true) { pressed = false; // Repaint might flicker a bit. To avoid this, you can use // double buffering (see the Gauge example). repaint(); } break; case MouseEvent.MOUSE_ENTERED: break; case MouseEvent.MOUSE_EXITED: if(pressed == true) { // Cancel! Don't send action event. pressed = false; // Repaint might flicker a bit. To avoid this, you can use // double buffering (see the Gauge example). repaint(); // Note: for a more complete button implementation, // you wouldn't want to cancel at this point, but // rather detect when the mouse re-entered, and // re-highlight the button. There are a few state // issues that that you need to handle, which we leave // this an an excercise for the reader (I always // wanted to say that!) } break; } super.processMouseEvent(e); }
On termine cette méthode par un appel à la méthode définie dans la classe parente Component en utilisant le mot-clé super.
Ecrivons une applet simple qui utilise des boutons OpenLook.
//test des boutons OpenLook import java.awt.*; import java.awt.event.*; public class OpenlookTest extends java.applet.Applet implements ActionListener { TextArea zoneTexte; OpenlookButton bouton; public void init() { setLayout(new BorderLayout()); zoneTexte=new TextArea(); add(zoneTexte,"Center"); Panel p=new Panel(); p.setBackground(Color.lightGray); add(p,"South"); bouton=new OpenlookButton("VERT"); bouton.setBackground(Color.green); bouton.setForeground(Color.black); bouton.addActionListener(this); p.add(bouton); bouton=new OpenlookButton("BLEU"); bouton.setBackground(Color.cyan); bouton.setForeground(Color.black); bouton.addActionListener(this); p.add(bouton); } public void actionPerformed(ActionEvent e) { zoneTexte.append("Bouton pressé: " + e.getActionCommand()+'\n'); } }
La couleur des boutons est définie avec la méthode héritée setBackground. On pourrait de même utiliser la méthode setFont pour choisir la fonte dans laquelle le libellé du bouton doit s'afficher.
Observons une nouvelle fois le résultat obtenu en ouvrant la page OpenlookTest.html.