Vous êtes sur la page 1sur 21

Welcome to OpenClassrooms! By continuing on the site, you are agreeing to our use of cookies.

Read more OK

Sign up Sign in

Home Course Apprenez programmer en Java Interagir avec des boutons

Apprenez programmer en Java


40 hours Hard

Interagir avec des boutons

Log in or subscribe to enjoy all this course has to offer!

Nous avons vu dans le chapitre prcdent les diffrentes faons de positionner des boutons et, par extension, des composants (car oui, ce que nous venons d'apprendre
pourra tre rutilis avec tous les autres composants que nous verrons par la suite).

Maintenant que vous savez positionner des composants, il est grand temps de leur indiquer ce qu'ils doivent faire. C'est ce que je vous propose d'aborder dans ce chapitre.
Mais avant cela, nous allons voir comment personnaliser un bouton. Toujours prts?

Une classe Bouton personnalise


Crons une classe hritant de javax.swing.JButton que nous appellerons Bouton et redfinissons sa mthode paintComponent() . Vous devriez y arriver tout seuls. Cet
exemple est reprsent la figure suivante:

Bouton personnalis

Voici la classe Bouton de cette application:


java
importjava.awt.Color;
importjava.awt.Font;
importjava.awt.FontMetrics;
importjava.awt.GradientPaint;
importjava.awt.Graphics;
importjava.awt.Graphics2D;

importjavax.swing.JButton;

publicclassBoutonextendsJButton{
privateStringname;
publicBouton(Stringstr){
super(str);
this.name=str;
}

publicvoidpaintComponent(Graphicsg){
Graphics2Dg2d=(Graphics2D)g;
GradientPaintgp=newGradientPaint(0,0,Color.blue,0,20,Color.cyan,true);
g2d.setPaint(gp);
g2d.fillRect(0,0,this.getWidth(),this.getHeight());
g2d.setColor(Color.white);
g2d.drawString(this.name,this.getWidth()/2(this.getWidth()/2/4),(this.getHeight()/2)+5);
}
}

J'ai aussi cr un bouton personnalis avec une image de fond, comme le montre la figure suivante.
Image de fond du bouton

Voyez le rsultat en figure suivante.

Bouton avec une image de fond

J'ai appliqu l'image (bien sr, ladite image se trouve la racine de mon projet!) sur l'intgralit du fond, comme je l'ai montr lorsque nous nous amusions avec
notre Panneau . Voici le code de cette classe Bouton :
java
importjava.awt.Color;
importjava.awt.GradientPaint;
importjava.awt.Graphics;
importjava.awt.Graphics2D;
importjava.awt.Image;
importjava.awt.event.MouseEvent;
importjava.awt.event.MouseListener;
importjava.io.File;
importjava.io.IOException;
importjavax.imageio.ImageIO;
importjavax.swing.JButton;

publicclassBoutonextendsJButton{
privateStringname;
privateImageimg;

publicBouton(Stringstr){
super(str);
this.name=str;
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidpaintComponent(Graphicsg){
Graphics2Dg2d=(Graphics2D)g;
GradientPaintgp=newGradientPaint(0,0,Color.blue,0,20,Color.cyan,true);
g2d.setPaint(gp);
g2d.drawImage(img,0,0,this.getWidth(),this.getHeight(),this);
g2d.setColor(Color.black);
g2d.drawString(this.name,this.getWidth()/2(this.getWidth()/2/4),(this.getHeight()/2)+5);
}
}

Rien de compliqu jusque-l C'est partir de maintenant que les choses deviennent intressantes!

Et si je vous proposais de changer l'aspect de votre objet lorsque vous cliquez dessus avec votre souris et lorsque vous relchez le clic? Il existe des interfaces
implmenter qui permettent de grer toutes sortes d'vnements dans votre IHM. Le principe est un peu droutant au premier abord, mais il est assez simple lorsqu'on a un
peu pratiqu. N'attendons plus et voyons cela de plus prs!

Interactions avec la souris: l'interfaceMouseListener

Avant de nous lancer dans l'implmentation, vous pouvez voir le rsultat que nous allons obtenir sur les deux figures suivantes.
Apparence du bouton au survol de la souris

Apparence du bouton lors d'un clic de souris

Il va tout de mme falloir passer par un peu de thorie avant d'arriver ce rsultat. Pour dtecter les vnements qui surviennent sur votre composant, Java utilise ce qu'on
appelle le design pattern observer. Je ne vous l'expliquerai pas dans le dtail tout de suite, nous le verrons la fin de ce chapitre.

Vous vous en doutez, nous devrons implmenter l'interface MouseListener dans notre classe Bouton . Nous devrons aussi prciser notre classe qu'elle devra tenir
quelqu'un au courant de ses changements d'tat par rapport la souris. Ce quelqu'un n'est autre qu'elle-mme! Eh oui : notre classe va s'couter, ce qui signifie que ds
que notre objet observable (notre bouton) obtiendra des informations concernant les actions effectues par la souris, il indiquera l'objet qui l'observe (c'est--dire lui-
mme) ce qu'il doit effectuer.

Cela est ralisable grce la mthode addMouseListener(MouseListenerobj) qui prend un objet MouseListener en paramtre (ici, elle prendra this ). Rappelez-vous que
vous pouvez utiliser le type d'une interface comme supertype: ici, notre classe implmente l'interface MouseListener , nous pouvons donc utiliser cet objet comme
rfrence de cette interface.

Voici prsent notre classe Bouton :


java
importjava.awt.Color;
importjava.awt.GradientPaint;
importjava.awt.Graphics;
importjava.awt.Graphics2D;
importjava.awt.Image;
importjava.awt.event.MouseEvent;
importjava.awt.event.MouseListener;
importjava.io.File;
importjava.io.IOException;
importjavax.imageio.ImageIO;
importjavax.swing.JButton;

publicclassBoutonextendsJButtonimplementsMouseListener{
privateStringname;
privateImageimg;
publicBouton(Stringstr){
super(str);
this.name=str;
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
//Grcecetteinstruction,notreobjetvas'couter
//Dsqu'unvnementdelasourisseraintercept,ilenseraaverti
this.addMouseListener(this);
}

publicvoidpaintComponent(Graphicsg){
Graphics2Dg2d=(Graphics2D)g;
GradientPaintgp=newGradientPaint(0,0,Color.blue,0,20,Color.cyan,true);
g2d.setPaint(gp);
g2d.drawImage(img,0,0,this.getWidth(),this.getHeight(),this);
g2d.setColor(Color.black);
g2d.drawString(this.name,this.getWidth()/2(this.getWidth()/2/4),(this.getHeight()/2)+5);
}

//Mthodeappelelorsduclicdesouris
publicvoidmouseClicked(MouseEventevent){}

//Mthodeappelelorsdusurvoldelasouris
publicvoidmouseEntered(MouseEventevent){}

//Mthodeappelelorsquelasourissortdelazonedubouton
publicvoidmouseExited(MouseEventevent){}

//Mthodeappelelorsquel'onpresseleboutongauchedelasouris
publicvoidmousePressed(MouseEventevent){}

//Mthodeappelelorsquel'onrelcheleclicdesouris
publicvoidmouseReleased(MouseEventevent){}
}

C'est en redfinissant ces diffrentes mthodes prsentes dans l'interface MouseListener que nous allons grer les diffrentes images dessiner dans notre objet.
Rappelez-vous en outre que mme si vous n'utilisez pas toutes les mthodes d'une interface, vous devez malgr tout insrer le squelette des mthodes non utilises (avec
les accolades), cela tant galement valable pour les classes abstraites.

Dans notre cas, la mthode repaint() est appele de faon implicite : lorsqu'un vnement est dclench, notre objet se redessine automatiquement! Comme
lorsque vous redimensionniez votre fentre dans les premiers chapitres.

Nous n'avons alors plus qu' modifier notre image en fonction de la mthode invoque. Notre objet comportera les caractristiques suivantes:

il aura une teinte jaune au survol de la souris;


il aura une teinte orange lorsque l'on pressera le bouton gauche;
il reviendra la normale si on relche le clic.

Pour ce faire, je vous propose de tlcharger les fichiers PNG dont je me suis servi (rien ne vous empche de les crer vous-mmes).

Tlcharger les images

Je vous rappelle que dans le code qui suit, les images sont places la racine du projet.
Voici maintenant le code de notre classe Bouton personnalise:
java
importjava.awt.Color;
importjava.awt.GradientPaint;
importjava.awt.Graphics;
importjava.awt.Graphics2D;
importjava.awt.Image;
importjava.awt.event.MouseEvent;
importjava.awt.event.MouseListener;
importjava.io.File;
importjava.io.IOException;
importjavax.imageio.ImageIO;
importjavax.swing.JButton;

publicclassBoutonextendsJButtonimplementsMouseListener{
privateStringname;
privateImageimg;

publicBouton(Stringstr){
super(str);
this.name=str;
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
this.addMouseListener(this);
}

publicvoidpaintComponent(Graphicsg){
Graphics2Dg2d=(Graphics2D)g;
GradientPaintgp=newGradientPaint(0,0,Color.blue,0,20,Color.cyan,true);
g2d.setPaint(gp);
g2d.drawImage(img,0,0,this.getWidth(),this.getHeight(),this);
g2d.setColor(Color.black);
g2d.drawString(this.name,this.getWidth()/2(this.getWidth()/2/4),(this.getHeight()/2)+5);
}

publicvoidmouseClicked(MouseEventevent){
//Inutiled'utilisercettemthodeici
}

publicvoidmouseEntered(MouseEventevent){
//Nouschangeonslefonddenotreimagepourlejaunelorsdusurvol,aveclefichierfondBoutonHover.png
try{
img=ImageIO.read(newFile("fondBoutonHover.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidmouseExited(MouseEventevent){
//Nouschangeonslefonddenotreimagepourlevertlorsquenousquittonslebouton,aveclefichierfondBouton.png
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidmousePressed(MouseEventevent){
//Nouschangeonslefonddenotreimagepourlejaunelorsduclicgauche,aveclefichierfondBoutonClic.png
try{
img=ImageIO.read(newFile("fondBoutonClic.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidmouseReleased(MouseEventevent){
//Nouschangeonslefonddenotreimagepourleorangelorsquenousrelchonsleclic,aveclefichierfondBoutonHover.png
try{
img=ImageIO.read(newFile("fondBoutonHover.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}
}

Et voil le travail! Si vous avez enregistr mes images, elles ne possdent probablement pas le mme nom que dans mon code : vous devez alors modifier le code en
fonction de celui que vous leur avez attribu! D'accord, a va de soi mais on ne sait jamais.

Vous possdez dornavant un bouton personnalis qui ragit au passage de votre souris. Je sais qu'il y aura des p'tits malins qui cliqueront sur le bouton et relcheront
le clic en dehors du bouton : dans ce cas, le fond du bouton deviendra orange, puisque c'est ce qui doit tre effectu vu la mthode mouseReleased() . Afin de pallier ce
problme, nous allons vrifier que lorsque le clic est relch, la souris se trouve toujours sur le bouton.
Nous avons implment l'interface MouseListener ; il reste cependant un objet que nous n'avons pas encore utilis. Vous ne le voyez pas? C'est le paramtre prsent dans
toutes les mthodes de cette interface : oui, c'est MouseEvent !

Cet objet nous permet d'obtenir beaucoup d'informations sur les vnements. Nous ne dtaillerons pas tout ici, mais nous verrons certains cts pratiques de ce type
d'objet tout au long de cette partie. Dans notre cas, nous pouvons rcuprer les coordonnes x et y du curseur de la souris par rapport au Bouton grce aux
mthodes getX() et getY() . Cela signifie que si nous relchons le clic en dehors de la zone o se trouve notre objet, la valeur retourne par la mthode getY() sera
ngative.

Voici le correctif d
e la mthode mouseReleased() de notre classe Bouton :
java
publicvoidmouseReleased(MouseEventevent){
//NouschangeonslefonddenotreimagepourleorangelorsquenousrelchonsleclicaveclefichierfondBoutonHover.pngsilasourisesttoujourssurlebouton
if((event.getY()>0&&event.getY()<this.getHeight())&&(event.getX()>0&&event.getX()<this.getWidth())){
try{
img=ImageIO.read(newFile("fondBoutonHover.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}
//Sionsetrouvel'extrieur,ondessinelefondpardfaut
else{
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}
}

Vous verrez dans les chapitres qui suivent qu'il existe plusieurs interfaces pour les diffrentes actions possibles sur une IHM. Sachez qu'il existe aussi une convention
pour ces interfaces : leur nom commence par le type de l'action, suivi du mot Listener . Nous avons tudi ici les actions de la souris, voyez le nom de
l'interface: MouseListener .

Nous possdons prsent un bouton ractif, mais qui n'effectue rien pour le moment. Je vous propose de combler cette lacune.

Interagir avec son bouton


Dclencher une action: l'interfaceActionListener

Afin de grer les diffrentes actions effectuer selon le bouton sur lequel on clique, nous allons utiliser l'interface ActionListener .

Nous n'allons pas implmenter cette interface dans notre classe Bouton mais dans notre classe Fenetre , le but tant de faire en sorte que lorsque l'on clique sur le
bouton, il se passe quelque chose dans notre application : changer un tat, une variable, effectuer une incrmentation Enfin, n'importe quelle action!

Comme je vous l'ai expliqu, lorsque nous appliquons un addMouseListener() , nous informons l'objet observ qu'un autre objet doit tre tenu au courant de l'vnement.
Ici, nous voulons que ce soit notre application (notre Fenetre ) qui coute notre Bouton , le but tant de pouvoir lancer ou arrter l'animation dans le Panneau .

Avant d'en arriver l, nous allons faire plus simple : nous nous pencherons dans un premier temps sur l'implmentation de l'interface ActionListener . Afin de vous
montrer toute la puissance de cette interface, nous utiliserons un nouvel objet issu du package javax.swing : le JLabel . Cet objet se comporte comme un libell : il est
spcialis dans l'affichage de texte ou d'image. Il est donc idal pour notre premier exemple!

On l'instancie et l'initialise plus ou moins de la mme manire que le JButton :


java
JLabellabel1=newJLabel();
label1.setText("MonpremierJLabel");
//Ouencore
JLabellabel2=newJLabel("MondeuximeJLabel");

Crez une variable d'instance de type JLabel (appelez-la label ) et initialisez-la avec le texte qui vous plat; ajoutez-la ensuite votre content pane en
position BorderLayout.NORTH .

Le rsultat se trouve en figure suivante.

Utilisation d'un JLabel

Voici le code correspondant:


java
publicclassFenetreextendsJFrame{
privatePanneaupan=newPanneau();
privateBoutonbouton=newBouton("monbouton");
privateJPanelcontainer=newJPanel();
privateJLabellabel=newJLabel("LeJLabel");

publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);
container.add(bouton,BorderLayout.SOUTH);
container.add(label,BorderLayout.NORTH);

this.setContentPane(container);
this.setVisible(true);
go();
}
//Lerestenechangepas
}

Vous pouvez voir que le texte de cet objet est align par dfaut en haut gauche. Il est possible de modifier quelques paramtres tels que:

l'alignement du texte;
la police utiliser;
la couleur du texte;
d'autres paramtres.

Voici un code mettant tout cela en pratique:


java
publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);
container.add(bouton,BorderLayout.SOUTH);

//Dfinitiond'unepoliced'criture
Fontpolice=newFont("Tahoma",Font.BOLD,16);
//Onl'appliqueauJLabel
label.setFont(police);
//Changementdelacouleurdutexte
label.setForeground(Color.blue);
//Onmodifiel'alignementdutextegrceauxattributsstatiques
//delaclasseJLabel
label.setHorizontalAlignment(JLabel.CENTER);

container.add(label,BorderLayout.NORTH);
this.setContentPane(container);
this.setVisible(true);
go();
}

La figure suivante donne un aperu de ce code.

Utilisation plus fine d'un JLabel

Maintenant que notre libell se prsente exactement sous la forme que nous voulons, nous pouvons implmenter l'interface ActionListener . Vous remarquerez que cette
interface ne contient qu'une seule mthode!
java
//CTRL+SHIFT+Opourgnrerlesimports
publicclassFenetreextendsJFrameimplementsActionListener{
privatePanneaupan=newPanneau();
privateBoutonbouton=newBouton("monbouton");
privateJPanelcontainer=newJPanel();
privateJLabellabel=newJLabel("LeJLabel");

publicFenetre(){
//Cemorceaudecodenechangepas
}

//Mthodequiseraappelelorsd'unclicsurlebouton
publicvoidactionPerformed(ActionEventarg0){
}
}

Nous allons maintenant informer notre objet Bouton que notre objet Fenetre l'coute. Vous l'avez devin: ajoutons notre Fenetre la liste des objets qui coutent
notre Bouton grce la mthode addActionListener(ActionListenerobj) prsente dans la classe JButton , donc utilisable avec la variable bouton . Ajoutons cette
instruction dans le constructeur en passant this en paramtre (puisque c'est notre Fenetre qui coute le Bouton ).

Une fois l'opration effectue, nous pouvons modifier le texte du JLabel avec la mthode actionPerformed() . Nous allons compter le nombre de fois que l'on a cliqu sur
le bouton : ajoutons une variable d'instance de type int dans notre class et appelons-la compteur , puis dans la mthode actionPerformed() , incrmentons ce compteur
et affichons son contenu dans notre libell.

Voici le code de notre objet mis jour:


java
importjava.awt.BorderLayout;
importjava.awt.Color;
importjava.awt.Font;
importjava.awt.event.ActionEvent;
importjava.awt.event.ActionListener;
importjavax.swing.JFrame;
importjavax.swing.JLabel;
importjavax.swing.JPanel;

publicclassFenetreextendsJFrameimplementsActionListener{
privatePanneaupan=newPanneau();
privateBoutonbouton=newBouton("monbouton");
privateJPanelcontainer=newJPanel();
privateJLabellabel=newJLabel("LeJLabel");
//Compteurdeclics
privateintcompteur=0;

publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);

//Nousajoutonsnotrefentrelalistedesauditeursdenotrebouton
bouton.addActionListener(this);

container.add(bouton,BorderLayout.SOUTH);

Fontpolice=newFont("Tahoma",Font.BOLD,16);
label.setFont(police);
label.setForeground(Color.blue);
label.setHorizontalAlignment(JLabel.CENTER);
container.add(label,BorderLayout.NORTH);
this.setContentPane(container);
this.setVisible(true);
go();
}

privatevoidgo(){
//Cettemthodenechangepas
}

publicvoidactionPerformed(ActionEventarg0){
//Lorsquel'oncliquesurlebouton,onmetjourleJLabel
this.compteur++;
label.setText("Vousavezcliqu"+this.compteur+"fois");
}
}

Voyez le rsultat la figure suivante.

Interaction avec le bouton

Et nous ne faisons que commencer Eh oui, nous allons maintenant ajouter un deuxime bouton notre Fenetre , ct du premier (vous tes libres d'utiliser la classe
personnalise ou un simple JButton ). Pour ma part, j'utiliserai des boutons normaux; en effet, dans notre classe personnalise, la faon dont le libell est crit dans notre
bouton n'est pas assez souple et l'affichage peut donc tre dcevant (dans certains cas, le libell peut ne pas tre centr)

Bref, nous possdons prsent deux boutons couts par notre objet Fenetre .

Vous devez crer un deuxime JPanel qui contiendra nos deux boutons, puis l'insrer dans le content pane en position BorderLayout.SOUTH . Si vous tentez de
positionner deux composants au mme endroit grce un BorderLayout ,seul le dernier composant ajout apparatra: en effet, le composant occupe toute la place
disponible dans un BorderLayout !

Voici notre nouveau code:


java
importjava.awt.BorderLayout;
importjava.awt.Color;
importjava.awt.Font;
importjava.awt.event.ActionEvent;
importjava.awt.event.ActionListener;
importjavax.swing.JButton;
importjavax.swing.JFrame;
importjavax.swing.JLabel;
importjavax.swing.JPanel;

publicclassFenetreextendsJFrameimplementsActionListener{
privatePanneaupan=newPanneau();
privateJButtonbouton=newJButton("bouton1");
privateJButtonbouton2=newJButton("bouton2");
privateJPanelcontainer=newJPanel();
privateJLabellabel=newJLabel("LeJLabel");
privateintcompteur=0;

publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);

bouton.addActionListener(this);
bouton2.addActionListener(this);

JPanelsouth=newJPanel();
south.add(bouton);
south.add(bouton2);
container.add(south,BorderLayout.SOUTH);

Fontpolice=newFont("Tahoma",Font.BOLD,16);
label.setFont(police);
label.setForeground(Color.blue);
label.setHorizontalAlignment(JLabel.CENTER);
container.add(label,BorderLayout.NORTH);
this.setContentPane(container);
this.setVisible(true);
go();
}

//
}

La figure suivante illustre le rsultat que nous obtenons.

Un deuxime bouton dans la fentre

prsent, le problme est le suivant: comment effectuer deux actions diffrentes dans la mthode actionPerformed() ?

En effet, si nous laissons la mthode actionPerformed() telle quelle, les deux boutons excutent la mme action lorsqu'on les clique. Essayez, vous verrez le rsultat.

Il existe un moyen de connatre l'lment ayant dclench l'vnement : il faut se servir de l'objet pass en paramtre dans la mthode actionPerformed() . Nous pouvons
exploiter la mthode getSource() de cet objet pour connatre le nom de l'instance qui a gnr l'vnement. Testez la mthode actionPerformed() suivante et voyez si le
rsultat correspond la figure suivante.
java
publicvoidactionPerformed(ActionEventarg0){
if(arg0.getSource()==bouton)
label.setText("Vousavezcliqusurlebouton1");

if(arg0.getSource()==bouton2)
label.setText("Vousavezcliqusurlebouton2");
}
Dtection de la source de l'vnement

Notre code fonctionne merveille! Cependant, cette approche n'est pas trs oriente objet : si notre IHM contient une multitude de boutons, la
mthode actionPerformed() sera trs charge. Nous pourrions crer deux objets part, chacun coutant un bouton, dont le rle serait de ragir de faon approprie pour
chaque bouton ; mais si nous avions besoin de modifier des donnes spcifiques la classe contenant nos boutons, il faudrait ruser afin de parvenir faire communiquer
nos objets Pas terrible non plus.

Parler avec sa classe intrieure

En Java, on peut crer ce que l'on appelle des classes internes. Cela consiste dclarer une classe l'intrieur d'une autre classe. Je sais, a peut paratre tordu, mais vous
allez bientt constater que c'est trs pratique.

En effet, les classes internes possdent tous les avantages des classes normales, de l'hritage d'une superclasse l'implmentation d'une interface. Elles bnficient donc
du polymorphisme et de la covariance des variables. En outre, elles ont l'avantage d'avoir accs aux attributs de la classe dans laquelle elles sont dclares!

Dans le cas qui nous intresse, cela permet de crer une implmentation de l'interface ActionListener dtache de notre classe Fenetre , mais pouvant utiliser ses
attributs. La dclaration d'une telle classe se fait exactement de la mme manire que pour une classe normale, si ce n'est qu'elle se trouve dj dans une autre classe.
Nous procdons donc comme ceci:
java
publicclassMaClasseExterne{

publicMaClasseExterne(){
//
}

classMaClassInterne{
publicMaClassInterne(){
//
}
}
}

Grce cela, nous pourrons concevoir une classe spcialise dans l'coute des composants et qui effectuera un travail bien dtermin. Dans notre exemple, nous crerons
deux classes internes implmentant chacune l'interface ActionListener et redfinissant la mthode actionPerformed() :

BoutonListener coutera le premier bouton;


Bouton2Listener coutera le second.

Une fois ces oprations effectues, il ne nous reste plus qu' indiquer chaque bouton qui l'coute grce la mthode addActionListener() .

Voyez ci-dessous la classe Fenetre mise jour.


java
importjava.awt.BorderLayout;
importjava.awt.Color;
importjava.awt.Font;
importjava.awt.event.ActionEvent;
importjava.awt.event.ActionListener;
importjavax.swing.JButton;
importjavax.swing.JFrame;
importjavax.swing.JLabel;
importjavax.swing.JPanel;

publicclassFenetreextendsJFrame{

privatePanneaupan=newPanneau();
privateJButtonbouton=newJButton("bouton1");
privateJButtonbouton2=newJButton("bouton2");
privateJPanelcontainer=newJPanel();
privateJLabellabel=newJLabel("LeJLabel");
privateintcompteur=0;

publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);

//Cesontmaintenantnosclassesinternesquicoutentnosboutons
bouton.addActionListener(newBoutonListener());
bouton2.addActionListener(newBouton2Listener());

JPanelsouth=newJPanel();
south.add(bouton);
south.add(bouton2);
container.add(south,BorderLayout.SOUTH);
Fontpolice=newFont("Tahoma",Font.BOLD,16);
label.setFont(police);
label.setForeground(Color.blue);
label.setHorizontalAlignment(JLabel.CENTER);
container.add(label,BorderLayout.NORTH);
this.setContentPane(container);
this.setVisible(true);
go();
}

privatevoidgo(){
//Cettemthodenechangepas
}

//Classecoutantnotrepremierbouton
classBoutonListenerimplementsActionListener{
//RedfinitiondelamthodeactionPerformed()
publicvoidactionPerformed(ActionEventarg0){
label.setText("Vousavezcliqusurlebouton1");
}
}

//Classecoutantnotresecondbouton
classBouton2ListenerimplementsActionListener{
//RedfinitiondelamthodeactionPerformed()
publicvoidactionPerformed(ActionEvente){
label.setText("Vousavezcliqusurlebouton2");
}
}
}

Le rsultat, visible la figure suivante, est parfait.

Utilisation de deux actions sur deux boutons

Vous pouvez constater que nos classes internes ont mme accs aux attributs dclars private dans notre classe Fenetre .

Dornavant, nous n'avons plus nous soucier du bouton qui a dclench l'vnement, car nous disposons de deux classes coutant chacune un bouton. Nous pouvons
souffler un peu: une grosse pine vient de nous tre retire du pied.

Vous pouvez aussi faire couter votre bouton par plusieurs classes. Il vous suffit d'ajouter ces classes supplmentaires l'aide d' addActionListener() .

Eh oui, faites le test: crez une troisime classe interne et attribuez-lui le nom que vous voulez (personnellement, je l'ai appele Bouton3Listener ). Implmentez-y
l'interface ActionListener et contentez-vous d'effectuer un simple System.out.println() dans la mthode actionPerformed() . N'oubliez pas de l'ajouter la liste des
classes qui coutent votre bouton (n'importe lequel des deux; j'ai pour ma part choisi le premier).

Je vous cris uniquement le code ajout:


java
//Lesimports

publicclassFenetreextendsJFrame{
//Lesvariablesd'instance

publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);

//Premireclassecoutantmonpremierbouton
bouton.addActionListener(newBoutonListener());
//Deuximeclassecoutantmonpremierbouton
bouton.addActionListener(newBouton3Listener());

bouton2.addActionListener(newBouton2Listener());

JPanelsouth=newJPanel();
south.add(bouton);
south.add(bouton2);
container.add(south,BorderLayout.SOUTH);

Fontpolice=newFont("Tahoma",Font.BOLD,16);
label.setFont(police);
label.setForeground(Color.blue);
label.setHorizontalAlignment(JLabel.CENTER);
container.add(label,BorderLayout.NORTH);
this.setContentPane(container);
this.setVisible(true);
go();
}

//

classBouton3ListenerimplementsActionListener{
//RedfinitiondelamthodeactionPerformed()
publicvoidactionPerformed(ActionEvente){
System.out.println("Maclasseinternenumro3coutebien!");
}
}
}

Le rsultat se trouve sur la figure suivante.

Deux couteurs sur un bouton

Les classes internes sont vraiment des classes part entire. Elles peuvent galement hriter d'une superclasse. De ce fait, c'est presque comme si nous nous trouvions
dans le cas d'un hritage multiple (ce n'en est pas un, mme si cela y ressemble). Ce code est donc valide:
java
publicclassMaClasseExterneextendsJFrame{

publicMaClasseExterne(){
//...
}

classMaClassInterneextendsJPanel{
publicMaClassInterne(){
//
}
}

classMaClassInterne2extendsJButton{
publicMaClassInterne(){
//
}
}
}

Vous voyez bien que ce genre de classes peut s'avrer trs utile.

Bon, nous avons rgl le problme d'implmentation: nous possdons deux boutons qui sont couts. Il ne nous reste plus qu' lancer et arrter notre animation l'aide
de ces boutons. Mais auparavant, nous allons tudier une autre manire d'implmenter des couteurs et, par extension, des classes devant redfinir les mthodes d'une
classe abstraite ou d'une interface.

Les classes anonymes

Il n'y a rien de compliqu dans cette faon de procder, mais je me rappelle avoir t dconcert lorsque je l'ai rencontre pour la premire fois.

Les classes anonymes sont le plus souvent utilises pour la gestion d'vnements ponctuels, lorsque crer une classe pour un seul traitement est trop lourd. Rappelez-vous
ce que j'ai utilis pour dfinir le comportement de mes boutons lorsque je vous ai prsent l'objet CardLayout : c'taient des classes anonymes. Pour rappel, voici ce que je
vous avais amens coder:
java
JButtonbouton=newJButton("Contenusuivant");
//Dfinitiondel'actionsurlebouton
bouton.addActionListener(newActionListener(){
publicvoidactionPerformed(ActionEventevent){
//Action!
}
});

L'une des particularits de cette mthode, c'est que l'couteur n'coutera que ce composant. Vous pouvez vrifier qu'il n'y se trouve aucune dclaration de classe et que
nous instancions une interface par l'instruction newActionListener() . Nous devons seulement redfinir la mthode, que vous connaissez bien maintenant, dans un bloc
d'instructions ; d'o les accolades aprs l'instanciation, comme le montre la figure suivante.
Dcoupage d'une classe anonyme

Pourquoi appelle-t-on cela une classe anonyme ?


C'est simple: procder de cette manire revient crer une classe fille sans tre oblig de crer cette classe de faon explicite. L'hritage se produit automatiquement. En
fait, le code ci-dessus revient effectuer ceci:
java
classFenetreextendsJFrame{
//
bouton.addActionListener(newActionListenerBis());
//

publicclassActionListenerBisimplementsActionListener{
publicvoidactionPerformed(ActionEventevent){
//Action!
}
}
}

Seulement, la classe cre n'a pas de nom, l'hritage s'effectue de faon implicite! Nous bnficions donc de tous les avantages de la classe mre en ne redfinissant que la
mthode qui nous intresse.

Sachez aussi que les classes anonymes peuvent tre utilises pour implmenter des classes abstraites. Je vous conseille d'effectuer de nouveaux tests en utilisant notre
exemple du pattern strategy; mais cette fois, plutt que de crer des classes, crez des classes anonymes.

Les classes anonymes sont soumises aux mmes rgles que les classes normales :

utilisation des mthodes non redfinies de la classe mre;


obligation de redfinir toutes les mthodes d'une interface;
obligation de redfinir les mthodes abstraites d'une classe abstraite.

Cependant, ces classes possdent des restrictions cause de leur rle et de leur raison d'tre:

elles ne peuvent pas tre dclares abstract ;


elles ne peuvent pas non plus tre dclares static ;
elles ne peuvent pas dfinir de constructeur;
elles sont automatiquement dclares final : on ne peut driver de cette classe, l'hritage est donc impossible!

Contrler son animation: lancement et arrt

Pour parvenir grer le lancement et l'arrt de notre animation, nous allons devoir modifier un peu le code de notre classe Fenetre . Il va falloir changer le libell des
boutons de notre IHM: le premier affichera Go et le deuxime Stop . Pour viter d'interrompre l'animation alors qu'elle n'est pas lance et de l'animer quand elle l'est dj,
nous allons tantt activer et dsactiver les boutons. Je m'explique:

au lancement, le bouton Go ne sera pas cliquable alors que le bouton Stop oui;
si l'animation est interrompue, le bouton Stop ne sera plus cliquable, mais le bouton Go le sera.

Ne vous inquitez pas, c'est trs simple raliser. Il existe une mthode grant ces changements d'tat:
java
JButtonbouton=newJButton("bouton");
bouton.setEnabled(false);//Leboutonn'estpluscliquable
bouton.setEnabled(true);//Leboutonestdenouveaucliquable

Ces objets permettent de raliser pas mal de choses; soyez curieux et testez-en les mthodes. Allez donc faire un tour sur le site d'Oracle: fouillez, fouinez

L'une de ces mthodes, qui s'avre souvent utile et est utilisable avec tous ces objets (ainsi qu'avec les objets que nous verrons par la suite), est la mthode de gestion de
dimension. Il ne s'agit pas de la mthode setSize() , mais de la mthode setPreferredSize() . Elle prend en paramtre un objet Dimension , qui, lui, prend deux entiers
comme arguments.

Voici un exemple:
java
bouton.setPreferredSize(newDimension(150,120));

En l'utilisant dans notre application, nous obtenons la figure suivante.


Gestion de la taille de nos boutons

Afin de bien grer notre animation, nous devons amliorer notre mthode go() . Sortons donc de cette mthode les deux entiers dont nous nous servions afin de recalculer
les coordonnes de notre rond. La boucle infinie doit dornavant pouvoir tre interrompue! Pour russir cela, nous allons dclarer un boolen qui changera d'tat selon le
bouton sur lequel on cliquera; nous l'utiliserons comme paramtre de notre boucle.

Voyez ci-dessous le code de notre classe Fenetre .


java
importjava.awt.BorderLayout;
importjava.awt.Color;
importjava.awt.Font;
importjava.awt.event.ActionEvent;
importjava.awt.event.ActionListener;
importjavax.swing.JButton;
importjavax.swing.JFrame;
importjavax.swing.JLabel;
importjavax.swing.JPanel;

publicclassFenetreextendsJFrame{

privatePanneaupan=newPanneau();
privateJButtonbouton=newJButton("Go");
privateJButtonbouton2=newJButton("Stop");
privateJPanelcontainer=newJPanel();
privateJLabellabel=newJLabel("LeJLabel");
privateintcompteur=0;
privatebooleananimated=true;
privatebooleanbackX,backY;
privateintx,y;

publicFenetre(){
this.setTitle("Animation");
this.setSize(300,300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);

container.setBackground(Color.white);
container.setLayout(newBorderLayout());
container.add(pan,BorderLayout.CENTER);
bouton.addActionListener(newBoutonListener());
bouton.setEnabled(false);
bouton2.addActionListener(newBouton2Listener());

JPanelsouth=newJPanel();
south.add(bouton);
south.add(bouton2);
container.add(south,BorderLayout.SOUTH);
Fontpolice=newFont("Tahoma",Font.BOLD,16);
label.setFont(police);
label.setForeground(Color.blue);
label.setHorizontalAlignment(JLabel.CENTER);
container.add(label,BorderLayout.NORTH);
this.setContentPane(container);
this.setVisible(true);
go();
}

privatevoidgo(){
//Lescoordonnesdedpartdenotrerond
x=pan.getPosX();
y=pan.getPosY();
//Danscetexemple,j'utiliseunebouclewhile
//Vousverrezqu'ellefonctionnetrsbien
while(this.animated){
if(x<1)backX=false;
if(x>pan.getWidth()50)backX=true;
if(y<1)backY=false;
if(y>pan.getHeight()50)backY=true;
if(!backX)pan.setPosX(++x);
elsepan.setPosX(x);
if(!backY)pan.setPosY(++y);
elsepan.setPosY(y);
pan.repaint();

try{
Thread.sleep(3);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}

classBoutonListenerimplementsActionListener{
publicvoidactionPerformed(ActionEventarg0){
animated=true;
bouton.setEnabled(false);
bouton2.setEnabled(true);
go();
}
}

classBouton2ListenerimplementsActionListener{
publicvoidactionPerformed(ActionEvente){
animated=false;
bouton.setEnabled(true);
bouton2.setEnabled(false);
}
}
}

l'excution, vous remarquez que:

le bouton Go n'est pas cliquable et l'autre l'est;


l'animation se lance;
l'animation s'arrte lorsque l'on clique sur le bouton Stop ;
le bouton Go devient alors cliquable;
lorsque vous cliquez dessus, l'animation ne se relance pas!

Comment est-ce possible?


Comme je l'ai expliqu dans le chapitre traitant des conteneurs, un thread est lanc au dmarrage de notre application: c'est le processus principal du programme. Au
dmarrage, l'animation est donc lance dans le mme thread que notre objet Fenetre . Lorsque nous lui demandons de s'arrter, aucun problme: les ressources mmoire
sont libres, on sort de la boucle infinie et l'application continue fonctionner.

Mais lorsque nous redemandons l'animation de se lancer, l'instruction se trouvant dans la mthode actionPerformed() appelle la mthode go() et, tant donn que
nous nous trouvons l'intrieur d'une boucle infinie, nous restons dans la mthode go() et ne sortons plus de la mthode actionPerformed() .

Explication de ce phnomne

Java gre les appels aux mthodes grce ce que l'on appelle vulgairement la pile.

Pour expliquer cela, prenons un exemple tout bte; regardez cet objet:
java
publicclassTestPile{
publicTestPile(){
System.out.println("Dbutconstructeur");
methode1();
System.out.println("Finconstructeur");
}

publicvoidmethode1(){
System.out.println("Dbutmthode1");
methode2();
System.out.println("Finmthode1");
}

publicvoidmethode2(){
System.out.println("Dbutmthode2");
methode3();
System.out.println("Finmthode2");
}

publicvoidmethode3(){
System.out.println("Dbutmthode3");
System.out.println("Finmthode3");
}
}

Si vous instanciez cet objet, vous obtenez dans la console la figure suivante.

Exemple de pile d'invocations

Je suppose que vous avez remarqu avec stupfaction que l'ordre des instructions est un peu bizarre. Voici ce qu'il se passe:

l'instanciation, notre objet appelle la mthode 1;


cette dernire invoque la mthode 2;
celle-ci utilise la mthode 3: une fois qu'elle a termin, la JVM retourne dans la mthode 2;
lorsqu'elle a fini de s'excuter, on remonte la fin de la mthode 1, jusqu' la dernire instruction appelante: le constructeur.

Lors de tous les appels, on dit que la JVM empile les invocations sur la pile. Une fois que la dernire mthode empile a termin de s'excuter, la JVM la dpile.
La figure suivante prsente un schma rsumant la situation.

Empilage et dpilage de mthodes

Dans notre programme, imaginez que la mthode actionPerformed() soit reprsente par la mthode 2, et que notre mthode go() soit reprsente par la mthode 3.
Lorsque nous entrons dans la mthode 3, nous entrons dans une boucle infinie Consquence directe: nous ne ressortons jamais de cette mthode et la JVM ne dpile
plus!

Afin de pallier ce problme, nous allons utiliser un nouveau thread. Grce cela, la mthode go() se trouvera dans une pile part.

Attends: on arrive pourtant arrter l'animation alors qu'elle se trouve dans une boucle infinie. Pourquoi?
Tout simplement parce que nous ne demandons d'effectuer qu'une simple initialisation de variable dans la gestion de notre vnement! Si vous crez une deuxime
mthode comprenant une boucle infinie et que vous l'invoquez lors du clic sur le bouton Stop , vous aurez exactement le mme problme.

Je ne vais pas m'terniser l-dessus, nous verrons cela dans un prochain chapitre. prsent, je pense qu'il est de bon ton de vous parler du mcanisme d'coute
d'vnements, le fameux pattern observer.

tre l'coute de ses objets : le design pattern Observer


Le design pattern Observer est utilis pour grer les vnements de vos IHM. C'est une technique de programmation. La connatre n'est pas absolument indispensable,
mais cela vous aide mieux comprendre le fonctionnement de Swing et AWT . C'est par ce biais que vos composants effectueront quelque chose lorsque vous les cliquerez
ou les survolerez.

Je vous propose de dcouvrir son fonctionnement l'aide d'une situation problmatique.

Posons le problme

Sachant que vous tes des dveloppeurs Java chevronns, un de vos amis proches vous demande si vous tes en mesure de l'aider raliser une horloge digitale en Java. Il
a en outre la gentillesse de vous fournir les classes utiliser pour la cration de son horloge. Votre ami a l'air de s'y connatre, car ce qu'il vous a fourni est bien structur.

Packagecom.sdz.vue, classeFenetre.java
java
packagecom.sdz.vue;

importjava.awt.BorderLayout;
importjava.awt.Font;
importjavax.swing.JFrame;
importjavax.swing.JLabel;

importcom.sdz.model.Horloge;

publicclassFenetreextendsJFrame{
privateJLabellabel=newJLabel();
privateHorlogehorloge;

publicFenetre(){
//OninitialiselaJFrame
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setSize(200,80);
//Oninitialisel'horloge
this.horloge=newHorloge();
//OninitialiseleJLabel
Fontpolice=newFont("DSdigital",Font.TYPE1_FONT,30);
this.label.setFont(police);
this.label.setHorizontalAlignment(JLabel.CENTER);
//OnajouteleJLabellaJFrame
this.getContentPane().add(this.label,BorderLayout.CENTER);
}

//Mthodemain()lanantleprogramme
publicstaticvoidmain(String[]args){
Fenetrefen=newFenetre();
fen.setVisible(true);
}
}

Packagecom.sdz.model, classeHorloge.java
java
packagecom.sdz.model;

importjava.util.Calendar;
publicclassHorloge{
//Objetcalendrierpourrcuprerl'heurecourante
privateCalendarcal;
privateStringhour="";

publicvoidrun(){
while(true){
//Onrcuprel'instanced'uncalendrierchaquetour
//Ellevanouspermettredercuprerl'heureactuelle
this.cal=Calendar.getInstance();
this.hour=//Lesheures
this.cal.get(Calendar.HOUR_OF_DAY)+":"
+
(//Lesminutes
this.cal.get(Calendar.MINUTE)<10
?"0"+this.cal.get(Calendar.MINUTE)
:this.cal.get(Calendar.MINUTE)
)
+":"
+
(//Lessecondes
(this.cal.get(Calendar.SECOND)<10)
?"0"+this.cal.get(Calendar.SECOND)
:this.cal.get(Calendar.SECOND)
);
try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}
}

Si vous ne disposez pas de la police d'criture que j'ai utilise, utilisez-en une autre: Arial ou Courrier , par exemple.

Le problme auquel votre ami est confront est simple: il est impossible de faire communiquer l'horloge avec la fentre.

Je ne vois pas o est le problme: il n'a qu' passer son instance de JLabel dans son objet Horloge , et le tour est jou!

En ralit, votre ami, dans son infinie sagesse, souhaite -je le cite- que l'horloge ne dpende pas de son interface graphique, juste au cas o il devrait passer d'une
IHM swing une IHM awt .

Il est vrai que si l'on passe l'objet d'affichage dans l'horloge, dans le cas o l'on change le type de l'IHM, toutes les classes doivent tre modifies; ce n'est pas gnial. En fait,
lorsque vous procdez de la sorte, on dit que vous couplez des objets: vous rendez un ou plusieurs objets dpendants d'un ou de plusieurs autres objets (entendez par l
que vous ne pourrez plus utiliser les objets coupls indpendamment des objets auxquels ils sont attachs).

Le couplage entre objets est l'un des problmes principaux relatifs la rutilisation des objets. Dans notre cas, si vous utilisez l'objet Horloge dans une autre application,
vous serez confronts plusieurs problmes tant donn que cet objet ne s'affiche que dans un JLabel .

C'est l que le pattern observer entre en jeu: il fait communiquer des objets entre eux sans qu'ils se connaissent rellement! Vous devez tre curieux de voir comment il
fonctionne, je vous propose donc de l'tudier sans plus tarder.

Des objets qui parlent et qui coutent: le pattern observer

Faisons le point sur ce que vous savez de ce pattern pour le moment:

il fait communiquer des objets entre eux;


c'est un bon moyen d'viter le couplage d'objets.

Ce sont deux points cruciaux, mais un autre lment, que vous ne connaissez pas encore, va vous plaire: tout se fait automatiquement!

Comment les choses vont-elles alors se passer? Rflchissons ce que nous voulons que notre horloge digitale effectue: elle doit pouvoir avertir l'objet servant afficher
l'heure lorsqu'il doit rafrachir son affichage. Puisque les horloges du monde entier se mettent jour toutes les secondes, il n'y a aucune raison pour que la ntre ne fasse
pas de mme.

Ce qui est merveilleux avec ce pattern, c'est que notre horloge ne se contentera pas d'avertir un seul objet que sa valeur a chang: elle pourra en effet mettre plusieurs
observateurs au courant!

En fait, pour faire une analogie, interprtez la relation entre les objets implmentant le pattern observer comme un diteur de journal et ses clients (voir figure suivante).

Livreur de journaux

Grce ce schma, vous pouvez sentir que notre objet dfini comme observable pourra tre surveill par plusieurs objets: il s'agit d'une relation dite de un plusieurs vers
l'objet Observateur . Avant de vous expliquer plus en dtail le fonctionnement de ce pattern, jetez un il au diagramme de classes de notre application en figure suivante.
Diagramme de classes du pattern observer

Ce diagramme indique que ce ne sont pas les instances d' Horloge ou de JLabel que nous allons utiliser, mais des implmentations d'interfaces.

En effet, vous savez que les classes implmentant une interface peuvent tre dfinies par le type de l'interface. Dans notre cas, la classe Fenetre implmentera
l'interface Observateur : nous pourrons la voir comme une classe du type Observateur . Vous avez sans doute remarqu que la deuxime interface -celle ddie
l'objet Horloge - possde trois mthodes:

une permettant d'ajouter des observateurs (nous allons donc grer une collection d'observateurs) ;
une permettant de retirer les observateurs;
enfin, une mettant jour les observateurs.

Grce cela, nos objets ne sont plus lis par leurs types, mais par leurs interfaces! L'interface qui apportera les mthodes de mise jour, d'ajout d'observateurs, etc.
travaillera donc avec des objets de type Observateur .

Ainsi, le couplage ne s'effectue plus directement, il s'opre par le biais de ces interfaces. Ici, il faut que nos deux interfaces soient couples pour que le systme fonctionne.
De mme que, lorsque je vous ai prsent le pattern decorator, nos classes taient trs fortement couples puisqu'elles devaient travailler ensemble: nous devions alors
faire en sorte de ne pas les sparer.

Voici comment fonctionnera l'application:

nous instancierons la classe Horloge dans notre classe Fenetre ;


cette dernire implmentera l'interface Observateur ;
notre objet Horloge , implmentant l'interface Observable , prviendra les objets spcifis de ses changements;
nous informerons l'horloge que notre fentre l'observe;
partir de l, notre objet Horloge fera le reste: chaque changement, nous appellerons la mthode mettant tous les observateurs jour.

Le code source de ces interfaces se trouve ci-dessous (notez que j'ai cr un package com.sdz.observer ).

Observateur.java
java
packagecom.sdz.observer;

publicinterfaceObservateur{
publicvoidupdate(Stringhour);
}

Observer.java
java
packagecom.sdz.observer;

publicinterfaceObservable{
publicvoidaddObservateur(Observateurobs);
publicvoidupdateObservateur();
publicvoiddelObservateur();
}

Voici maintenant le code de nos deux classes, travaillant ensemble mais n'tant que faiblement couples.

Horloge.java
java
packagecom.sdz.model;

importjava.util.ArrayList;
importjava.util.Calendar;

importcom.sdz.observer.Observable;
importcom.sdz.observer.Observateur;

publicclassHorlogeimplementsObservable{
//Onrcuprel'instanced'uncalendrier
//Ellevanouspermettredercuprerl'heureactuelle
privateCalendarcal;
privateStringhour="";
//Notrecollectiond'observateurs
privateArrayList<Observateur>listObservateur=newArrayList<Observateur>();

publicvoidrun(){
while(true){
this.cal=Calendar.getInstance();
this.hour=//Lesheures
this.cal.get(Calendar.HOUR_OF_DAY)+":"
+
(//Lesminutes
this.cal.get(Calendar.MINUTE)<10
?"0"+this.cal.get(Calendar.MINUTE)
:this.cal.get(Calendar.MINUTE)
)
+":"
+
(//Lessecondes
(this.cal.get(Calendar.SECOND)<10)
?"0"+this.cal.get(Calendar.SECOND)
:this.cal.get(Calendar.SECOND)
);
//Onavertitlesobservateursquel'heureatmisejour
this.updateObservateur();

try{
Thread.sleep(1000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}

//Ajouteunobservateurlaliste
publicvoidaddObservateur(Observateurobs){
this.listObservateur.add(obs);
}
//Retiretouslesobservateursdelaliste
publicvoiddelObservateur(){
this.listObservateur=newArrayList<Observateur>();
}
//Avertitlesobservateursquel'objetobservableachang
//etinvoquelamthodeupdate()dechaqueobservateur
publicvoidupdateObservateur(){
for(Observateurobs:this.listObservateur)
obs.update(this.hour);
}
}

Fenetre.java
java
packagecom.sdz.vue;

importjava.awt.BorderLayout;
importjava.awt.Font;
importjavax.swing.JFrame;
importjavax.swing.JLabel;

importcom.sdz.model.Horloge;
importcom.sdz.observer.Observateur;

publicclassFenetreextendsJFrame{
privateJLabellabel=newJLabel();
privateHorlogehorloge;

publicFenetre(){
//OninitialiselaJFrame
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setResizable(false);
this.setSize(200,80);

//Oninitialisel'horloge
this.horloge=newHorloge();
//Onplaceuncouteursurl'horloge
this.horloge.addObservateur(newObservateur(){
publicvoidupdate(Stringhour){
label.setText(hour);
}
});

//OninitialiseleJLabel
Fontpolice=newFont("DSdigital",Font.TYPE1_FONT,30);
this.label.setFont(police);
this.label.setHorizontalAlignment(JLabel.CENTER);
//OnajouteleJLabellaJFrame
this.getContentPane().add(this.label,BorderLayout.CENTER);
this.setVisible(true);
this.horloge.run();
}

//Mthodemain()lanantleprogramme
publicstaticvoidmain(String[]args){
Fenetrefen=newFenetre();
}
}

Excutez ce code, vous verrez que tout fonctionne merveille. Vous venez de permettre deux objets de communiquer en n'utilisant presque aucun couplage:
flicitations!

Vous pouvez voir que lorsque l'heure change, la mthode updateObservateur() est invoque. Celle-ci parcourt la collection d'objets Observateur et appelle sa
mthode update(Stringhour) . La mthode tant redfinie dans notre classe Fenetre afin de mettre JLabel jour, l'heure s'affiche!

J'ai mentionn que ce pattern est utilis dans la gestion vnementielle d'interfaces graphiques. Vous avez en outre remarqu que leurs syntaxes sont identiques. En
revanche, je vous ai cach quelque chose: il existe des classes Java permettant d'utiliser le pattern observer sans avoir coder les interfaces.

Le pattern observer: le retour

En ralit, il existe une classe abstraite Observable et une interface Observer fournies dans les classes Java.

Celles-ci fonctionnent de manire quasiment identique notre faon de procder, seuls quelques dtails diffrent. Personnellement, je prfre de loin utiliser un pattern
observer fait maison .

Pourquoi cela? Tout simplement parce que l'objet que l'on souhaite observer doit hriter de la classe Observable . Par consquent, il ne pourra plus hriter d'une autre
classe tant donn que Java ne gre pas l'hritage multiple. La figure suivante prsente la hirarchie de classes du pattern observer prsent dans Java.
Diagramme de classes du pattern observer de Java

Vous remarquez qu'il fonctionne presque de la mme manire que celui que nous avons dvelopp. Il y a toutefois une diffrence dans la mthode update(Observableobs,
Objectobj) : sa signature a chang.
Cette mthode prend ainsi deux paramtres:

un objet Observable ;
un Object reprsentant une donne supplmentaire que vous souhaitez lui fournir.

Vous connaissez le fonctionnement de ce pattern, il vous est donc facile de comprendre le schma. Je vous invite cependant effectuer vos propres recherches sur son
implmentation dans Java: vous verrez qu'il existe des subtilits (rien de mchant, cela dit).

Cadeau: un bouton personnalis optimis


Terminons par une version amliore de notre bouton qui reprend ce que nous avons appris :
java
importjava.awt.Color;
importjava.awt.FontMetrics;
importjava.awt.GradientPaint;
importjava.awt.Graphics;
importjava.awt.Graphics2D;
importjava.awt.Image;
importjava.awt.event.MouseEvent;
importjava.awt.event.MouseListener;
importjava.io.File;
importjava.io.IOException;
importjavax.imageio.ImageIO;
importjavax.swing.JButton;

publicclassBoutonextendsJButtonimplementsMouseListener{
privateStringname;
privateImageimg;

publicBouton(Stringstr){
super(str);
this.name=str;
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
this.addMouseListener(this);
}

publicvoidpaintComponent(Graphicsg){
Graphics2Dg2d=(Graphics2D)g;
GradientPaintgp=newGradientPaint(0,0,Color.blue,0,20,Color.cyan,true);
g2d.setPaint(gp);
g2d.drawImage(img,0,0,this.getWidth(),this.getHeight(),this);
g2d.setColor(Color.black);

//Objetpermettantdeconnatrelespropritsd'unepolice,dontlataille
FontMetricsfm=g2d.getFontMetrics();
//Hauteurdelapoliced'criture
intheight=fm.getHeight();
//Largeurtotaledelachanepasseenparamtre
intwidth=fm.stringWidth(this.name);

//Oncalculealorslapositiondutexte,etletourestjou
g2d.drawString(this.name,this.getWidth()/2(width/2),(this.getHeight()/2)+(height/4));
}

publicvoidmouseClicked(MouseEventevent){
//Inutiled'utilisercettemthodeici
}

publicvoidmouseEntered(MouseEventevent){
//Nouschangeonslefonddenotreimagepourlejaunelorsdusurvol,aveclefichierfondBoutonHover.png
try{
img=ImageIO.read(newFile("fondBoutonHover.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidmouseExited(MouseEventevent){
//Nouschangeonslefonddenotreimagepourlevertlorsquenousquittonslebouton,aveclefichierfondBouton.png
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidmousePressed(MouseEventevent){
//Nouschangeonslefonddenotreimagepourlejaunelorsduclicgauche,aveclefichierfondBoutonClic.png
try{
img=ImageIO.read(newFile("fondBoutonClic.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}

publicvoidmouseReleased(MouseEventevent){
//Nouschangeonslefonddenotreimagepourl'orangelorsquenousrelchonsleclicaveclefichierfondBoutonHover.pngsilasourisesttoujourssurlebouton
if((event.getY()>0&&event.getY()<this.getHeight())&&(event.getX()>0&&event.getX()<this.getWidth())){
try{
img=ImageIO.read(newFile("fondBoutonHover.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}
//Sionsetrouvel'extrieur,ondessinelefondpardfaut
else{
try{
img=ImageIO.read(newFile("fondBouton.png"));
}catch(IOExceptione){
e.printStackTrace();
}
}
}
}

Essayez, vous verrez que cette application fonctionne correctement.

Vous pouvez interagir avec un composant grce votre souris en implmentant l'interface MouseListener .
Lorsque vous implmentez une interface <>Listener , vous indiquez votre classe qu'elle doit se prparer observer des vnements du type de l'interface. Vous
devez donc spcifier qui doit observer et ce que la classe doit observer grce aux mthodes de type add<>Listener(<>Listener) .
L'interface utilise dans ce chapitre est ActionListener issue du package java.awt .
La mthode implmenter de cette interface est actionPerformed() .
Une classe interne est une classe se trouvant l'intrieur d'une classe.
Une telle classe a accs toutes les donnes et mthodes de sa classe externe.
La JVM traite les mthodes appeles en utilisant une pile qui dfinit leur ordre d'excution.
Une mthode est empile son invocation, mais n'est dpile que lorsque toutes ses instructions ont fini de s'excuter.
Le pattern observer permet d'utiliser des objets faiblement coupls. Grce ce pattern, les objets restent indpendants.

Positionner des boutons TP: une calculatrice

The author
Cyrille Herby
Spcialiste en dveloppement Java et
curieux insatiable dinformatique et de
programmation web. Actuellement
auditeur en scurit.

Check out this course via


Premium

Premium

eBook Livre papier PDF

OpenClassrooms

Partnerships

Learn more

Follow us

Espaol Franais