• 6 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 30/11/2016

TP : Des intercepteurs d'événements

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Voilà un TP qui devrait vous tenir en haleine quelques temps…
Celui-ci sera sûrement l'un des plus difficiles que vous aurez à faire donc, petit conseil : réfléchissez bien à ce que je vous demande, prenez votre temps et ne paniquez pas. :)

Cahier des charges

Alors, voilà ce que vous allez devoir faire dans ce TP : créer des proxies permettant d'ajouter des traitements lors d'événements levés sur deux boutons.

Voilà à quoi ressemble l'interface que vous allez développer :

IHM du TP

Étant d'un naturel magnanime, voici les codes sources que vous allez utiliser pour faire ce dernier :

Fenetre.java
package com.sdz.test;

import java.awt.BorderLayout;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class Fenetre extends JFrame{

   private JButton valider = new JButton("Valider");
   private JButton annuler = new JButton("Annuler");
   
   private JLabel label1 = new JLabel("Texte 1");
   private JLabel label2 = new JLabel("Texte 2");
   
   private JTextField texte1 = new JTextField();
   private JTextField texte2 = new JTextField();
   
   private JPanel panneau1 = new JPanel();
   private JPanel panneau2 = new JPanel();
   private JPanel panneau3 = new JPanel();
   
   public Fenetre(){
      getContentPane().setLayout(new BorderLayout());
      texte1.setPreferredSize(new Dimension(100, 20));
      texte2.setPreferredSize(new Dimension(100, 20));
      
      panneau1.add(label1);
      panneau1.add(texte1);      
      
      panneau2.add(label2);
      panneau2.add(texte2);      
      
      panneau3.add(valider);
      //Cette ligne sera à modifiée
      valider.addActionListener(new ListenerValider(this));
      panneau3.add(annuler);
      //Cette ligne sera à modifiée
      annuler.addActionListener(new ListenerAnnuler(this));
      
      getContentPane().add(panneau1, BorderLayout.NORTH);
      getContentPane().add(panneau2, BorderLayout.CENTER);
      getContentPane().add(panneau3, BorderLayout.SOUTH);
      pack();
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLocationRelativeTo(null);
      setVisible(true);
   }

   public JTextField getTexte1() {
      return texte1;
   }

   public JTextField getTexte2() {
      return texte2;
   }
}
ListenerValider.java
package com.sdz.test;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;

public class ListenerValider implements ActionListener{
   private Fenetre fen;
   public ListenerValider(Fenetre pFen){
      fen = pFen;
   }
   
   public void actionPerformed(ActionEvent e) {
      String message = "VALIDATION : " + "\n"; 
      message += "Texte 1 : " + fen.getTexte1().getText() + "\n ";
      message += "Texte 2 : " + fen.getTexte2().getText() + "\n ";
      JOptionPane.showMessageDialog(null, message, "actionPerformed", JOptionPane.INFORMATION_MESSAGE);
   }
}
ListenerAnnuler.java
package com.sdz.test;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;

public class ListenerAnnuler implements ActionListener{
   private Fenetre fen;
   public ListenerAnnuler(Fenetre pFen){
      fen = pFen;
   }
   
   public void actionPerformed(ActionEvent e) {
      String message = "Annulation : " + "\n"; 
      message += "Texte 1 : " + fen.getTexte1().getText() + "\n ";
      message += "Texte 2 : " + fen.getTexte2().getText() + "\n ";
      JOptionPane.showMessageDialog(null, message, "actionPerformed", JOptionPane.WARNING_MESSAGE);
   }
}
Main.java
package com.sdz.test;

public class Main {
   public static void main(String[] args) {
     new Fenetre();
   }
}

Ce code ne fait de particulier, il se contente d'afficher les données présentes dans les champs textes… Maintenant, ce qu'il vous reste à faire consiste à créer des proxies, des annotations et utiliser le tout pour ajouter des fonctionnalités aux écouteurs d'événements. L'idée est simple, utiliser deux annotations, Before et After à utiliser sur nos écouteurs afin de leurs spécifier quoi faire et quand.

Vous aurez donc ces deux annotations à créer mais aussi deux interfaces DoBefore et DoAfter et leurs implémentations.
Les deux annotations devront prendre un paramètre qui sera une classe correspondant à l'implémentation de l'une des deux interfaces mentionnées ci-dessus. Bien entendu, l'annotation Before n'acceptera que des objets implémentant DoBefore et l'annotation After n'acceptera que des objets implémentant l'interface DoAfter : pour ce point, pensez à la généricité... :-°

Je ne vous demande que deux classe concrètes pour ajouter des fonctionnalités :

  • une classe qui met des valeurs par défaut aux champs s'ils sont vides ;

  • une classe qui vide les champs.

Il vous faudra, bien entendu, un gestionnaire d'invocation et une classe qui se charge de créer les proxies souhaités pour, au final, modifier la création des listeners dans la classe Fenetre. Il y aura aussi une annotation à mettre sur chaque méthode actionPerformed dans nos écouteurs d'événements :

  • une annotation de type

    Before

    dans l'objet ListenerValider;

  • une annotation de type

    After

    dans l'objet ListenerAnnuler;

Vous avez maintenant toutes les instructions pour travailler mais, pour vous aider, je vais maintenant vous montrer ce que j'obtiens avec ma solution.

Après avoir saisi un seul champ et cliqué sur le bouton Valider :

Après un clic sur Valider

Après avoir cliqué sur Annuler :

Image utilisateur
Après un clic sur Annuler

Une dernière indication, voici à quoi ressemble mon projet :

Image utilisateur

Allez, à vos claviers !

Correction

Voici une correction possible pour ce TP.

Les interfaces et les classes concrètes

DoAfter.java
package com.sdz.actions;

public interface DoAfter {
   public void doAfter();
}
DoBefore.java
package com.sdz.actions;

public interface DoBefore {
   public void doBefore();
}
CheckForm.java
package com.sdz.actions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.swing.JFrame;
import javax.swing.JTextField;

public class CheckForm<T extends JFrame> implements DoBefore {

   private T fen;
   public CheckForm(T pFen){
      fen = pFen;
   }

   @Override
   public void doBefore() {
      System.out.println("DO BEFORE ! ! ! !");
      Method[] methods = fen.getClass().getDeclaredMethods();
      
      for(Method m : methods){
         if(m.getReturnType().getName().equals(JTextField.class.getName())){
            try {
               JTextField jtf = (JTextField)m.invoke(fen, null);
               if(jtf.getText().trim().equals(""))
                  jtf.setText("Default value inside !");
               
            } catch (IllegalAccessException | IllegalArgumentException
                  | InvocationTargetException e) {
               e.printStackTrace();
            }
         }
      }
   }
}
ResetForm.java
package com.sdz.actions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.swing.JFrame;
import javax.swing.JTextField;

public class ResetForm<T extends JFrame> implements DoAfter {

   private T fen;
   public ResetForm(T pFen){
      fen = pFen;
   }

   @Override
   public void doAfter() {
      System.out.println("DO AFTER ! ! ! !");
      Method[] methods = fen.getClass().getDeclaredMethods();
      for(Method m : methods){

         if(m.getReturnType().getName().equals(JTextField.class.getName())){
            try {
               JTextField jtf = (JTextField)m.invoke(fen, null);
               jtf.setText("");
            } catch (IllegalAccessException | IllegalArgumentException
                  | InvocationTargetException e) {
               e.printStackTrace();
            }
         }
      }
   }
}

Les annotations

After.java
package com.sdz.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import com.sdz.actions.DoAfter;

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface After {

   Class<? extends DoAfter> value();
}
Before.java
package com.sdz.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import com.sdz.actions.DoBefore;

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {

   Class<? extends DoBefore> value();
   
}

Les classes gérant les proxys

ListenerHandler.java
package com.sdz.handlers;
import java.awt.event.ActionListener;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import javax.swing.JFrame;

import com.sdz.actions.DoAfter;
import com.sdz.actions.DoBefore;
import com.sdz.annotations.After;
import com.sdz.annotations.Before;
import com.sdz.test.Fenetre;

class ListenerHandler implements InvocationHandler {

      //On y stocke l'objet Fenetre pour y accéder 
      private Fenetre fen;
      //Idem pour l'objet listener
      private ActionListener listener;
       
      public ListenerHandler(ActionListener pListener, Fenetre pFen){
         fen = pFen;
         listener = pListener;
      }
       
      //Méthode à redéfinir pour y coder le fonctionnement souhaité
      public Object invoke(Object proxy, Method method, Object[] args) 
                     throws Throwable {
           
           //Nous récupérons la méthode actionPeformed
           //de notre listener, nous partons donc de cet objet
           Method customActionPerformed = listener .getClass()
                                                   .getMethod(
                                                           method.getName(), 
                                                           args[0].getClass()
                                                    );
           
           //Pour les actions à faire avant, via notre annotation
           Before before = customActionPerformed.getAnnotation(Before.class);
           if(before != null){
              //On instancie un objet dynamiquement
              Class<? extends DoBefore> obj = before.value();
              //via son constructeur
              Constructor<? extends DoBefore> construct = obj.getConstructor(new Class[]{JFrame.class});
              //Nous avons maintenant notre objet implémentant DoBefore
              Object db = construct.newInstance(fen);
              //On récupère la méthode souhaitée
              Method m = obj.getMethod("doBefore", null);
              //et on l'invoque
              m.invoke(db, null);
           }
           
           System.out.println("Invocation de la méthode actionPerformed de notre proxy");
           //Ici se trouvait une petite subtilité...
           //Il fallait bien invoquer la méthode actionPerformed mais sur notre
           //implémentation perso, donc l'objet écouteur que nous avons fait
           Object o =  customActionPerformed.invoke(listener, args);
           
           // et les actions à faire après : idem que précédemment
           After after = customActionPerformed.getAnnotation(After.class);
           if(after != null){
              Class<? extends DoAfter> obj = after.value();
              Constructor<? extends DoAfter> construct = obj.getConstructor(new Class[]{JFrame.class});
              Object db = construct.newInstance(fen);
              Method m = obj.getMethod("doAfter", null);
              m.invoke(db, null);
           }
           
           return o;
      }
   }
ListenerProxyFactory.java
package com.sdz.handlers;

import java.awt.event.ActionListener;
import java.lang.reflect.Proxy;

import com.sdz.test.Fenetre;

public class ListenerProxyFactory {

   public static ActionListener newInstance(ActionListener listener, Fenetre fen){
      return (ActionListener) Proxy.newProxyInstance(
                 listener.getClass().getClassLoader(),//Nous utilisons le loader par défaut
                 new Class[]{ActionListener.class},   //Nous créons un proxy d'événement 
                 new ListenerHandler(listener, fen)   //Nous utilisons notre handler perso
             );
   }   
}

Le reste du projet

package com.sdz.test;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;

import com.sdz.actions.ResetForm;
import com.sdz.annotations.After;

public class ListenerAnnuler implements ActionListener{
   private Fenetre fen;
   public ListenerAnnuler(Fenetre pFen){
      fen = pFen;
   }
   
   @After(ResetForm.class)
   public void actionPerformed(ActionEvent e) {
      String message = "Annulation : " + "\n"; 
      message += "Texte 1 : " + fen.getTexte1().getText() + "\n ";
      message += "Texte 2 : " + fen.getTexte2().getText() + "\n ";
      JOptionPane.showMessageDialog(null, message, "actionPerformed", JOptionPane.WARNING_MESSAGE);
   }   

   public Fenetre getFenetre(){
      return fen;
   }
}
package com.sdz.test;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;

import com.sdz.actions.CheckForm;
import com.sdz.annotations.Before;

public class ListenerValider implements ActionListener{
   private Fenetre fen;
   public ListenerValider(Fenetre pFen){
      fen = pFen;
   }
   
   @Before(CheckForm.class)
   public void actionPerformed(ActionEvent e) {
      String message = "VALIDATION : " + "\n"; 
      message += "Texte 1 : " + fen.getTexte1().getText() + "\n ";
      message += "Texte 2 : " + fen.getTexte2().getText() + "\n ";
      JOptionPane.showMessageDialog(null, message, "actionPerformed", JOptionPane.INFORMATION_MESSAGE);
   }
   
   public Fenetre getFenetre(){
      return fen;
   }
}
package com.sdz.test;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import com.sdz.handlers.ListenerProxyFactory;

public class Fenetre extends JFrame{

   private JButton valider = new JButton("Valider");
   private JButton annuler = new JButton("Annuler");
   
   private JLabel label1 = new JLabel("Texte 1");
   private JLabel label2 = new JLabel("Texte 2");
   
   private JTextField texte1 = new JTextField();
   private JTextField texte2 = new JTextField();
   
   private JPanel panneau1 = new JPanel();
   private JPanel panneau2 = new JPanel();
   private JPanel panneau3 = new JPanel();
   
   public Fenetre(){
      getContentPane().setLayout(new BorderLayout());
      texte1.setPreferredSize(new Dimension(100, 20));
      texte2.setPreferredSize(new Dimension(100, 20));
      
      panneau1.add(label1);
      panneau1.add(texte1);      
      
      panneau2.add(label2);
      panneau2.add(texte2);
      
      //C'est à partir de ce moment que les choses changent
      panneau3.add(valider);
      //Nous créons un proxy de notre listener pour que ce dernier
      //utilise un gestionnaire d'invocation pour intercepter les événements
      ActionListener listener1 = ListenerProxyFactory.newInstance(
            new ListenerValider(this),
            this
      );
      valider.addActionListener(listener1);
      
      panneau3.add(annuler);
      //Nous créons un proxy de notre listener pour que ce dernier
      //utilise un gestionnaire d'invocation pour intercepter les événements
      ActionListener listener2 = ListenerProxyFactory.newInstance(
            new ListenerAnnuler(this),
            this
      );
      annuler.addActionListener(listener2);
            
      getContentPane().add(panneau1, BorderLayout.NORTH);
      getContentPane().add(panneau2, BorderLayout.CENTER);
      getContentPane().add(panneau3, BorderLayout.SOUTH);
      
      pack();
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLocationRelativeTo(null);
      setVisible(true);
   }

   public JTextField getTexte1() {
      return texte1;
   }

   public JTextField getTexte2() {
      return texte2;
   }
}
package com.sdz.test;

public class Main {

   public static void main(String[] args) {
     new Fenetre();
   }
}

Et voilà !
Je vous laisse analyser ce code. Je vous rassure, vous avez toutes les informations pour comprendre ce code. Relisez le chapitre précédent si besoin. :)

Voilà, cette partie est maintenant terminée. Vous savez maintenant vous servir des annotations et les utiliser dans vos codes sources pour ajouter un peu de dynamisme à vos programmes.

Cette partie a été riche en nouveautés et n'a pas été très facile, je le conçois, mais je suis sûr qu'elle vous rendra grandement service. :)

Ce cours vous a plu, et vous souhaitez aller encore plus loin avec Java ? Il y a plein de pistes à explorer avec ce langage ! En voici quelques unes :

  • utiliser des fichiers XML avec Java : n'hésitez pas à consulter mon cours Java et le XML qui traite de cette question ; 

  • gérer plusieurs threads en Java : on dit qu'on fait de la programmation concurrente, communément appelée le multithreading. Un cours est en préparation sur le sujet et sortira très prochainement, restez aux aguets !

Exemple de certificat de réussite
Exemple de certificat de réussite