Programmation Java Swing/Créer une case à cocher à trois états
Ce chapitre complète le sujet du précédent en montrant comment créer un composant à partir d'un composant existant. Ce chapitre décrira la création d'une case à cocher à trois états :
- Coché : L'option est sélectionnée,
- Non coché : L'option n'est pas sélectionnée,
- Inconnu ou partiel : Il s'agit d'un état utilisé soit quand l'option n'a pas d'état défini par l'utilisateur, soit parce qu'une partie des éléments qu'implique la sélection de l'option est sélectionnée (sélection partielle).
Base du composant
modifierLa case à cocher sera basée sur un label (classe JLabel
) pour afficher le texte, associé à une icône pour afficher l'état.
L'état du composant dépendra de l'état de la case, mais aussi du fait que la souris soit au-dessus du composant ou non.
Le tableau ci-dessous présente les différentes images utilisées, avec le nom du fichier utilisé par le code du composant pour charger l'image.
Téléchargez les images sous le nom indiqué, dans le répertoire du package de la classe.
État | Non coché | Inconnu / partiel | Coché |
---|---|---|---|
Normal |
|
|
|
Survol de la souris |
|
|
|
État du composant
modifierLe composant a trois états possibles. Contrairement à une case à cocher normale, le type booléen ne suffit plus à représenter l'état. Il faut donc créer une énumération :
package org.wikibooks.fr.swing.component;
/**
* Trois états d'une case à cocher.
* @author fr.wikibooks.org
*/
public enum ETristate
{
/** Non coché */
UNCHECKED,
/** Inconnu ou sélection partielle. */
PARTIAL,
/** Coché */
CHECKED;
// ...
}
On ajoute deux méthodes retournant l'état suivant, selon deux modes de transition différents :
- Mode trois états : Non coché → Coché → Inconnu → Non coché.
- Mode deux états (case à cocher classique) : Inconnu → Non coché → Coché → Noncoché.
/**
* Get next state in the following tri-state cycle: {@link #UNCHECKED} -> {@link #CHECKED} -> {@link #PARTIAL} -> {@link #UNCHECKED}.
* @return The next state in the tri-state cycle.
* @see #getNextDual()
*/
public ETristate getNext()
{
switch(this)
{
case UNCHECKED: return CHECKED;
case PARTIAL: return UNCHECKED;
default: return PARTIAL;
}
}
/**
* Get next state in the following dual-state cycle: {@link #PARTIAL} -> {@link #UNCHECKED} -> {@link #CHECKED} -> {@link #UNCHECKED}.
* @return The next state in the dual-state cycle.
* @see #getNext()
*/
public ETristate getNextDual()
{
switch(this)
{
case UNCHECKED: return CHECKED;
default: return UNCHECKED;
}
}
Classe de la case à cocher
modifierLe code ci-dessous présente la première version de la classe du composant, complétée au fil des sections de ce chapitre.
La classe implémente l'interface java.awt.ItemSelectable
des objets contenant des éléments sélectionnables (la case à cocher elle-même).
package org.wikibooks.fr.swing.component;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
/**
* Case à cocher à trois états.
* @author fr.wikibooks.org
*/
public class TristateCheckbox extends JLabel
implements ItemSelectable
{
private static Icon[] icons;
static
{
// Chargement des icônes représentant l'état de la case à cocher :
icons = new Icon[6];
for(int i=0,j=0;i<3;i++)
{
icons[j++] = new ImageIcon(TristateCheckbox.class.getResource("checkbox_"+i+".png"));
icons[j++] = new ImageIcon(TristateCheckbox.class.getResource("checkbox_"+i+"_over.png"));
}
}
// L'état de la case à cocher
protected ETristate state = ETristate.UNCHECKED;
// Indicateur de souris survolant le composant, et de mode à deux ou trois états.
protected boolean mouse_over = false, dual_state = false;
private int i_state = 0; // Index de l'état courant
// Écouteur d'évènements pour la case à cocher :
ArrayList<ItemListener> ll_item = new ArrayList<ItemListener>();
protected void fireItemEvent(boolean selected)
{
ItemEvent e = new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
this, selected ? ItemEvent.SELECTED : ItemEvent.DESELECTED);
for(ItemListener l : ll_item)
{
l.itemStateChanged(e);
}
}
@Override
public void addItemListener(ItemListener l)
{ ll_item.add(l); }
@Override
public Object[] getSelectedObjects()
{ return state==ETristate.UNCHECKED?null:new Object[]{this}; }
@Override
public void removeItemListener(ItemListener l)
{ ll_item.remove(l); }
// ... à compléter
}
Gestion de l'état du composant
modifierLa méthode suivante est appelée par plusieurs autres méthodes pour mettre à jour l'icône affichée en fonction de l'état et des indicateurs de la case à cocher.
/**
* Met à jour l'icône si nécessaire.
*/
protected void updateState()
{
int n = i_state;
i_state = (mouse_over?1:0) | (state.ordinal()<<1);
if (i_state != n)
setIcon(icons[i_state]);
// Notifier les écouteurs enregistrés du changement d'état :
fireItemEvent(state!=ETristate.UNCHECKED);
}
Elle est utilisée par exemple par la méthode modifiant l'indicateur de survol de la souris :
private void setMouseOver(boolean b)
{
mouse_over = b;
updateState();
}
Et également par les méthodes modifiant l'état de la case à cocher, en mode trois états ou en mode deux états.
/**
* Get the current checkbox state.
* @return The current state.
*/
public ETristate getState()
{
return state;
}
// Définir l'état, en mode trois états
public void setState(ETristate state)
{
this.state = state;
this.dual_state = false;
updateState();
}
// Définir l'état, en mode deux états
public void setDualState(ETristate state)
{
this.state = state;
this.dual_state = true;
updateState();
}
// Définir l'état, en mode deux états, à moins que l'état soit inconnu/partiel
public void setMultipleState(ETristate state)
{
this.state = state;
this.dual_state = state != ETristate.PARTIAL;
updateState();
}
La méthode nextState()
met la case à cocher dans l'état suivant, selon le mode de cycle courant.
protected void nextState()
{
// Tester si le composant est activé, car cette méthode est appelée au clic de souris.
if (isEnabled())
{
state = dual_state ? state.getNextDual() : state.getNext();
updateState();
}
}
Déclencher une action
modifierBeaucoup de composants Swing permettent de déclencher une action sur certains évènements. Les méthodes ci-dessous fournissent une implémentation pour définir un nom d'action et enregistrer des écouteurs de déclenchement d'action.
protected String action_command = "";
public void setActionCommand(String action_command)
{
this.action_command = action_command;
}
public String getActionCommand()
{
return action_command;
}
ArrayList<ActionListener> ll_action = new ArrayList<ActionListener>();
public void addActionListener(ActionListener l)
{
ll_action.add(l);
}
public void removeActionListener(ActionListener l)
{
ll_action.remove(l);
}
protected void fireAction(String command, int modifiers)
{
ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
command, System.currentTimeMillis(), modifiers);
for(ActionListener l : ll_action)
l.actionPerformed(e);
}
Constructeurs
modifierLe constructeur de la case à cocher définit l'écouteur d'évènements de la souris qui appelle les méthodes vues précédemment.
// Constructeurs définissant des valeurs par défaut:
public TristateCheckbox()
{ this("", JLabel.LEFT); }
public TristateCheckbox(String label)
{ this(label, JLabel.LEFT); }
public TristateCheckbox(int align)
{ this("", align); }
// Contructeur principal
public TristateCheckbox(String label, int align)
{
super(label, icons[0], align); // Non coché initialement
setFocusable(true); // Peut obtenir le focus
addMouseListener(new MouseListener()
{
public void mouseReleased(MouseEvent e)
{
// Vérifier que la souris survole encore le composant
// au moment de relâcher le bouton.
if (mouse_over) nextState();
fireAction(action_command, e.getModifiersEx());
}
public void mousePressed(MouseEvent e)
{
setMouseOver(true);
}
public void mouseExited(MouseEvent e)
{
setMouseOver(false);
}
public void mouseEntered(MouseEvent e)
{
setMouseOver(true);
}
public void mouseClicked(MouseEvent e)
{ }
});
}
Test du composant
modifierLe code source pour tester la case à cocher est présenté ci-dessous :
// Méthode main utilisée pour tester le composant dans une fenêtre
public static void main(String[] args)
{
// Créer la fenêtre
JFrame f = new JFrame("Case à cocher");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Personnaliser le panneau du contenu
JPanel p_content = new JPanel();
p_content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
p_content.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 20));
f.setContentPane(p_content);
TristateCheckbox tcb = new TristateCheckbox("Test");
p_content.add(tcb);
f.pack();
f.setVisible(true);
}