mardi 5 mars 2019

JList (3) : utilisation d'un écouteur (Listener) sur le modèle

Après la présentation d'un premier exemple simple (ici) qui montre la séparation entre la Vue et le Modèle dans les composants Swing et d'un deuxième exemple (ici) qui montre l'utilisation d'un Listener "logique" qui récupère les actions de l'utilisateur, cet article vise à montrer l'utilisation d'un Listener sur le modèle, c'est-à-dire, détecter qu'un changement a eu lieu dans le modèle de donnée.

Malgré qu'un tel écouteur peut être utilisé pour détecter des changements effectués par l'utilisateur, le plus intéressant est de l'utiliser pour détecter des changements appliqués par d'autres programmes ou bien d'autres threads.

Ainsi, pour cet article, deux classes seront nécessaires :
  • La fenêtre qui contiendra la liste et son modèle. Pour simplifier l'exemple, le modèle sera exposé via un simple getter.
  • Un "autre programme" qui simule une source externe du changement effectué sur le modèle de la liste.
L'écouteur sur un modèle de données ne cherche pas à détecter des actions de l'utilisateur. Mais, il se concentre sur les changements possibles des données. Ainsi, l'écouteur (Listener) ListDataListener propose trois méthodes :
  • intervalAdded
  • intervalRemoved
  • contentsChanged
Les deux premières permet de récupérer les événements simples, le troisième sert à récupérer des changements du contenu du modèle.


Les trois méthodes reçoivent le même objet événement : ListDataEvent


Si ce dernier ne cache pas les données changées, il garde les positions (début/fin) des intervalles affectées par le changement. Par la suite, il est possible de récupérer les données à partir du modèle lui-même.

Pour pouvoir utiliser directement ces fonctionnalités, il faut faire appel au moins à la classe AbstractListModel, d'ailleurs, c'est la valeur ajoutée principale de cette classe par rapport à l'interface de base ListModel. Autrement, tous les mécanismes internes doivent être implémentées (chose qui n'est pas vraiment difficile après tout).


Revenons à notre exemple. Il se compose de trois classes :
  • Une classe qui simule un programme externe qui change les données. Elle ajoute une donnée à partir d'une position aléatoire d'un tableau qui contient "quelques données".
  • Une fenêtre qui continent la liste et qui expose le modèle de la liste. Cette classe constitue aussi l'écouteur ListDtaListener. en recevant une nouvelle valeur, le texte d'une JLabel est mis à jour.
  • La classe qui contient le main crée une instance de chaque classe et les passe à deux thread séparés. Pour une meilleure visibilité, l'ajout des valeurs aléatoires s'effectue à des intervalles de 2 seconds. 
Ainsi, le code essentiel de la première classe :

class MonDeuxiemeProgramme {
 
 MaFrame frameAttachee;
 String[] quelquesDonnees = ...;

 public MonDeuxiemeProgramme(MaFrame frame){
  frameAttachee = frame;
 }
 
 public void ajouterElementAleatoire(){
  Random r = new Random();
  int valeur = r.nextInt(26);
  
  frameAttachee.getModele().addElement(quelquesDonnees[valeur]);
 }
 
}

Le code essentiel de la deuxième classe :


class MaFrame extends JFrame implements ListDataListener {

 JList liste;
 DefaultListModel modele;
 
 ...
 
 public MaFrame(){
  
  setTitle("Exemple JList");
  setSize(800, 400);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  modele = new DefaultListModel();
  modele.addListDataListener(this);
  liste = new JList(modele);
  
  creerInterface();
  
 }
 
 public void creerInterface(){
  ...
 }
 
 public void intervalAdded(ListDataEvent e){
  lblMessage.setText("Ajout à la position " + e.getIndex0() + " (" + modele.get(e.getIndex0()) + ")");
 }
 
 public DefaultListModel getModele(){
  return modele;
 }
 
 public void intervalRemoved(ListDataEvent e){}
 
 public void contentsChanged(ListDataEvent e){}

} 

Le code complet :


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.util.*;

public class ExempleEvenementModelJList {
 
 public static void main (String args[]) {
  
  MaFrame frame = new MaFrame();
  MonDeuxiemeProgramme autre = new MonDeuxiemeProgramme(frame);
  
  Thread t1 = new Thread(new Runnable(){
   
   public void run(){
    frame.setVisible(true);
   }
   
  });
  
  Thread t2 = new Thread(new Runnable(){
   
   public void run(){
    while(true){
     try{
      Thread.sleep(2000);
      autre.ajouterElementAleatoire();
     }catch(Exception e){}
    }
   }
   
  });
  
  t1.start();
  t2.start();
  
 }
}

class MaFrame extends JFrame implements ListDataListener {

 JList liste;
 JScrollPane scroll;
 DefaultListModel modele;
 
 JLabel lblMessage;
 
 JPanel pnlDroite;
 JPanel pnlGauche;
 
 public MaFrame(){
  
  setTitle("Exemple JList");
  setSize(800, 400);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  modele = new DefaultListModel();
  modele.addListDataListener(this);
  liste = new JList(modele);
  
  creerInterface();
  
 }
 
 public void creerInterface(){
  
  lblMessage = new JLabel("Pas d'élément pour le moment");
  lblMessage.setBounds(20, 20, 450, 30);
  
  pnlDroite = new JPanel();
  pnlDroite.setLayout(null);
  pnlDroite.add(lblMessage);
  pnlDroite.setPreferredSize(new Dimension(480, 360));
  
  scroll = new JScrollPane(liste);
  scroll.setBounds(0, 0, 250, 360);
  
  pnlGauche = new JPanel();
  pnlGauche.setLayout(null);
  pnlGauche.add(scroll);
  pnlGauche.setPreferredSize(new Dimension(280, 360));
  
  getContentPane().setLayout(new FlowLayout());
  getContentPane().add(pnlGauche);
  getContentPane().add(pnlDroite);
  
 }
 
 public void intervalAdded(ListDataEvent e){
  lblMessage.setText("Ajout à la position " + e.getIndex0() + " (" + modele.get(e.getIndex0()) + ")");
 }
 
 public DefaultListModel getModele(){
  return modele;
 }
 
 public void intervalRemoved(ListDataEvent e){}
 
 public void contentsChanged(ListDataEvent e){}

} 

class MonDeuxiemeProgramme {
 
 MaFrame frameAttachee;
 String[] quelquesDonnees = new String[]{"Alfa",
  "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf",
  "Hotel", "India", "Juliett", "Kilo", "Lima", "Mike",
  "November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra",
  "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee",
  "Zulu"};
 
 public MonDeuxiemeProgramme(MaFrame frame){
  frameAttachee = frame;
 }
 
 public void ajouterElementAleatoire(){
  Random r = new Random();
  int valeur = r.nextInt(26);
  
  frameAttachee.getModele().addElement(quelquesDonnees[valeur]);
 }
 
}

Résultat après quelques seconds :












Aucun commentaire: