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 :

  • Case à cocher : état coché Coché : L'option est sélectionnée,
  • Case à cocher : état décoché Non coché : L'option n'est pas sélectionnée,
  • Case à cocher : état inconnu 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 modifier

La 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  

checkbox_0.png

 

checkbox_1.png

 

checkbox_2.png

Survol de la souris  

checkbox_0_over.png

 

checkbox_1_over.png

 

checkbox_2_over.png

État du composant modifier

Le 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 modifier

Le 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 modifier

La 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 modifier

Beaucoup 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 modifier

Le 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 modifier

Le 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);
}