Affichage des articles dont le libellé est ListModel. Afficher tous les articles
Affichage des articles dont le libellé est ListModel. Afficher tous les articles

mercredi 6 mars 2019

L'utilisation des modèles associés aux composants Swing : JList comme exemple

Contrairement aux composants AWT, les composants Swing sépare la représentation (Vue) des données (Modèle) dans une approche très similaire à l'approche (MVC). Cette série vise à mettre l'accent sur séparation et démontrer quelques possibilités offertes par l'hiérarchie des classes Java pour les composants Swing.

La série se compose de quatre (04) articles :

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 :












lundi 4 mars 2019

JList (1) : un premier exemple simple

Ce code présente un exemple très simple pour créer et ajouter des éléments à une JList.

Ce code a été créé comme un premier code dans une (petite) série pour mettre l'accent sur l'utilisation des modèles tels que définis par  l'approche MVC, cette dernière qui est respectée par les composants Swing en comparaison avec les composants AWT.

Si la manipulation du modèle (donnée) associé à un composant simple comme JTextField, ce n'est pas de même pour les composants qui affichent plusieurs données tels que JComboBox, JList et JTable.

Dans cette série, JList sera choisie pour démontrer quelques fonctionnalités.

Dans cet premier code, une JList est créé en se basant sur DefaultListModel. En effet, les différents modèles sont disponibles comme des interfaces.


Pour simplifier leur utilisation (garantir les fonctionnalités de base communes), une classe abstraite qui implémente le minimum des fonctionnalités est livrée. Cela est valide pour tous les modèles des données pour les autres composants. Pour le cas de la JList, la classe abstraite est AbstractListModel :


Néanmoins, l'interface et la classe abstraite ne donnent aucune implémentation réelle qui supportent la sauvegarde des données et qui est prête pour être utilisée. C'est pourquoi, une implémentation par défaut est livrée. Cela aussi est valide pour les autres modèles des données des autres composants Swing. Pour la JList, la classe par défaut est DefaultListModel (c'est très explicit comme noms de classes) :


Cette dernière utilise un Vector pour stocker les données :

private Vector delegate = new Vector();


Le code suivant, montre comment créer une JList avec un DefaultListModel. Le code de création des autres composants graphiques est séparé. Le bouton permet d'ajouter la valeur saisie dans le champs à la liste (à gauche) :

Code source :


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

public class ExempleSimpleJList extends JFrame implements ActionListener {
 
 JList liste;
 DefaultListModel modele;
 
 JLabel lblNouveau;
 JTextField txtNouveau;
 JButton btnAjouter;
 JPanel pnlAjouter;
 
 public ExempleSimpleJList(){
  
  setTitle("Exemple Simple JList");
  setSize(800, 400);
  setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  modele = new DefaultListModel();
  liste = new JList(modele);
  
  creerFormulaire();
  
  getContentPane().add(pnlAjouter, BorderLayout.CENTER);
  getContentPane().add(new JScrollPane(liste), BorderLayout.WEST);
  
 }
 
 public void actionPerformed(ActionEvent e){
  modele.addElement(txtNouveau.getText());
 }
 
 public void creerFormulaire(){
  
  lblNouveau = new JLabel("Nouveau Element :");
  lblNouveau.setBounds(20, 20, 150, 30);
  txtNouveau = new JTextField();
  txtNouveau.setBounds(190, 20, 150, 30);
  btnAjouter = new JButton("Ajouter");
  btnAjouter.setBounds(20, 70, 150, 30);
  btnAjouter.addActionListener(this);
  
  pnlAjouter = new JPanel();
  pnlAjouter.setLayout(null);
  pnlAjouter.add(lblNouveau);
  pnlAjouter.add(txtNouveau);
  pnlAjouter.add(btnAjouter);
  
 }
 
 public static void main (String args[]) {
  
  (new ExempleSimpleJList()).setVisible(true);
 }
}

Résultat :