Programmation Java Swing/Fenêtres

Un livre de Wikilivres.
Aller à la navigation Aller à la recherche


Les fenêtres Swing dérivent de celles disponibles avec AWT :

  • JFrame (sous-classe de Frame) représente une fenêtre d'application.
  • JDialog (sous-classe de Dialog) représente une fenêtre de dialogue.
  • JWindow (sous-classe de Window) représente une fenêtre générique.
  • JInternalFrame (pas d'équivalent AWT) représente une fenêtre interne à l'intérieur d'une zone multi-documents gérée par la classe JDesktopPane.
  • JApplet (sous-classe de Applet) représente une fenêtre intégrée dans une page HTML d'un navigateur web. Ce type de fenêtre ne se rencontre plus beaucoup car pour des raisons de sécurité, Java est désactivé par défaut sur tous les navigateurs récents. De plus les applets sont déclarées obsolètes depuis Java 9 en 2017, et supprimées de Java depuis la version 11.

Points communs[modifier | modifier le wikicode]

Les différents types de fenêtres Swing cités précédemment ont une structure commune et des méthodes communes.

L'exemple ci-dessous crée une fenêtre d'application avec un titre, contenant un label affichant du texte.

package org.wikibooks.fr.swing;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

/**
 * Créer une fenêtre. 
 * @author fr.wikibooks.org
 */
public class ExempleFenetre extends JFrame
{
	public ExempleFenetre()
	{
		// Création de la fenêtre
		setTitle("Démonstration de création de fenêtre Swing");
		Dimension d = new Dimension(500, 200);
		setMinimumSize(d);
		setSize(d);

		Container c = getContentPane();
		c.setLayout(new FlowLayout());

		JLabel l_texte = new JLabel("La fenêtre est ouverte");
		c.add(l_texte);

	}

	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
		{
			public void run()
			{
				ExempleFenetre frame = new ExempleFenetre();
				frame.setVisible(true);
			}
		});
	}
}

Titre et icône[modifier | modifier le wikicode]

Une fenêtre possède un titre (méthode setTitle(String)) et une icône (méthode setIconImage(Image)). Par défaut, l'icône est la même que celle de la fenêtre propriétaire.

Composition[modifier | modifier le wikicode]

Chaque fenêtre comporte un panel racine (classe JRootPane) qui gère la composition des éléments suivants :

  • Le panneau frontal (Glass pane en anglais) est affiché au premier plan. Il est généralement utilisé pour afficher une animation de chargement de la fenêtre, une progression d'une longue opération en cours.
  • Le panneau de contenu (Content pane) contient les composants ajoutés à la fenêtre (bouton, label, ...).
  • La barre de menu (JMenuBar) définit les listes d'actions utilisables. Elle est affichée en haut de la fenêtre.

La barre de menu et le panneau de contenu sont gérés par un panneau de classe JLayeredPane permettant de gérer la superposition de composants.

Taille et position[modifier | modifier le wikicode]

La taille et la position d'une fenêtre sont gérées avec les mêmes méthodes que celles des composants. Il n'y a pas de gestionnaire de disposition pour les fenêtres, car aucun conteneur ne les contient (hormis JInternalFrame et JApplet).

La taille et la position de la fenêtre définies par l'application peuvent ensuite être modifiées par l'utilisateur en déplaçant et en redimensionnant la fenêtre. Les méthodes setMinimumSize(Dimension) et setMaximumSize(Dimension) définissent les limites du redimensionnement. La méthode setPreferredSize(Dimension) héritée de la classe Component n'a pas de sens pour les fenêtres.

Fenêtre d'application[modifier | modifier le wikicode]

La fenêtre d'application est celle qui définie la vue principale d'une application. Une application peut en avoir plusieurs (Par exemple, une fenêtre par fichier ouvert). Les applications les plus simples n'ont qu'une seule fenêtre principale.

La classe javax.swing.JFrame définit une fenêtre d'application.

Action de fermeture[modifier | modifier le wikicode]

La méthode setDefaultCloseOperation(int operation) définit l'action effectuée quand l'utilisateur clique le bouton de fermeture de la fenêtre. L'argument peut avoir l'une des valeurs suivantes définies dans l'interface WindowConstants implémentée par la classe JFrame :

DO_NOTHING_ON_CLOSE
Ne rien faire, et appeler les gestionnaires d'évènements des fenêtres (WindowListener) enregistrés.
HIDE_ON_CLOSE
Cacher la fenêtre puis appeler les gestionnaires d'évènements des fenêtres (WindowListener) enregistrés.
DISPOSE_ON_CLOSE
Cacher et libérer la fenêtre puis appeler les gestionnaires d'évènements des fenêtres (WindowListener) enregistrés.
EXIT_ON_CLOSE
Terminer l'application en appelant System.exit(0). Les gestionnaires d'évènements des fenêtres ne sont pas appelés.

Par défaut, la valeur est HIDE_ON_CLOSE. Tout changement provoque un évènement de changement de propriété dont le nom est "defaultCloseOperation".

Quand la dernière fenêtre est libérée (dispose), l'application peut se terminer.

Quand l'application doit faire confirmer la fermeture par l'utilisateur (par exemple en cas de données non sauvegardées), il est nécessaire de définir l'action de fermeture à rien (DO_NOTHING_ON_CLOSE) et d'ajouter un écouteur d'évènements des fenêtres (interface WindowListener implémentée par la classe vide WindowAdapter).

Dans le constructeur de la classe de fenêtre principale :

addWindowListener(new WindowAdapter()
{
	@Override
	public void windowClosing(WindowEvent e)
	{
		fermerFenetre();
	}
});

Dans la classe de fenêtre principale :

// Indicateur de données modifiées depuis la dernière sauvegarde
private boolean donnees_modifiees = false;
// Assigné à false par les actions suivantes :
//    - création d'un nouveau fichier,
//    - chargement à partir d'un fichier,
//    - enregistrement des données.
// Assigné à true par toute action modifiant les données.

// Pour confirmer une action faisant perdre les données
private boolean confirmerAction(String action)
{
	if (donnees_modifiees)
	{
		int reponse = JOptionPane.showConfirmDialog(this,
			"Attention ! Les données n'ont pas été sauvegardées.\n"+
			action+" maintenant provoquera une perte de données.\n"+
			"Continuer et perdre les données ?",
			"Confirmer la perte de données",
			JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
		return reponse == JOptionPane.YES_OPTION;
	}
	else return true; // On peut effectuer l'action
}

private void fermerFenetre()
{
	if (confirmerAction("Quitter l'application"))
	{
		// Fermer la fenêtre et quitter
		dispose();
		System.exit(0);
	}
}

La méthode confirmerAction ci-dessus est réutilisable pour les actions provoquant une perte des données non sauvegardées :

  • La fermeture de la fenêtre principale,
  • L'action quitter (menu / bouton),
  • Charger un fichier (si la même fenêtre est réutilisée),
  • Créer un nouveau fichier (si la même fenêtre est réutilisée),
  • ...

Fenêtre de dialogue[modifier | modifier le wikicode]

Une fenêtre de dialogue est une fenêtre temporaire ouverte sur une action spécifique permettant de dialoguer avec l'utilisateur pour lui fournir et lui demander une ou plusieurs informations : entrer les champs d'une nouvelle entité à ajouter dans une table, sélectionner un fichier, sélectionner une couleur, afficher un message d'erreur, demander une confirmation de suppression...

Fenêtres de dialogue prédéfinies[modifier | modifier le wikicode]

Swing possède des classes ou méthodes permettant d'utiliser des fenêtres de dialogue prédéfinies.

JOptionPane
La classe JOptionPane définit une fenêtre de dialogue simple permettant d'afficher un message avec une icône prédéfinie associée (information, question, avertissement, erreur...), avec éventuellement un champ de saisie, et de 1 à 3 boutons pour divers choix possible de réponse.
Cette classe possède différentes méthodes statiques pour créer et afficher ces fenêtres.
JColorChooser
La classe JColorChooser définit une fenêtre de dialogue pour sélectionner une couleur.
JFileChooser
La classe JFileChooser définit une fenêtre de dialogue pour sélectionner un fichier ou un répertoire.

Fenêtre de dialogue de type message : JOptionPane[modifier | modifier le wikicode]

La classe JOptionPane définit une fenêtre de dialogue simple permettant d'afficher un message avec une icône prédéfinie associée, pouvant avoir un champ de saisie selon le type, et proposant plusieurs choix de bouton.

Simple notification de message[modifier | modifier le wikicode]

Ce type de fenêtre affiche simplement le message et possède un bouton OK pour fermer la fenêtre, et utilise la méthode showMessageDialog.

Exemple :

JOptionPane.showMessageDialog(frame,
    "Une erreur s'est produite pour illustrer cette section.",
    "Erreur de démonstration", JOptionPane.ERROR_MESSAGE);
  • La méthode showMessageDialog ne retourne aucune valeur.
  • frame est la fenêtre (Frame ou JFrame) par dessus laquelle le message est affiché. Si la référence est null, une fenêtre est créée.
  • Le message affichée dans la fenêtre est spécifié avant le titre.
  • Le dernier argument spécifie le type de message, définissant l'icône affichée en face du message :
JOptionPane.showMessageDialog(frame,
    "Un avertissement signale un fait important porté à l'attention de l'utilisateur.",
    "Avertissement de démonstration", JOptionPane.WARNING_MESSAGE);

JOptionPane.showMessageDialog(frame,
    "Une information pour l'utilisateur.",
    "Information de démonstration", JOptionPane.INFORMATION_MESSAGE);

JOptionPane.showMessageDialog(frame,
    "Un message pour l'utilisateur, sans icône.",
    "Message de démonstration", JOptionPane.PLAIN_MESSAGE);

// Le type question est plus pertinent pour les autres types de fenêtres où l'utilisateur peut choisir une réponse.
JOptionPane.showMessageDialog(frame,
    "Une question pour l'utilisateur, mais il ne peut répondre que OK.",
    "Des questions ?", JOptionPane.QUESTION_MESSAGE);

Il existe deux variantes de la méthode showMessageDialog :

  • Sans les deux derniers arguments de titre ("Message" par défaut) et type (JOptionPane.INFORMATION_MESSAGE par défaut).
  • Avec un cinquième argument permettant d'afficher une icône personnalisée de type xjava.swing.Icon.

Fenêtre de confirmation[modifier | modifier le wikicode]

Ce type de fenêtre affiche le message et propose plusieurs boutons de confirmation pour fermer la fenêtre, et utilise la méthode showConfirmDialog.

Exemple :

int reponse = JOptionPane.showConfirmDialog(frame,
    "Confirmez-vous vouloir fermer la fenêtre sans sauvegarder ?",
    "Modifications non sauvegardées",
    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);

Les variantes de la méthode sont :

int showConfirmDialog(Component parent, String message)
Titre "Select an option", boutons Oui, Non et Annuler ;
int showConfirmDialog(Component parent, String message, String title, int optionType)
Titre et boutons spécifiés ;
int showConfirmDialog(Component parent, String message, String title, int optionType, int messageType)
Type de message spécifié également ;
int showConfirmDialog(Component parent, String message, String title, int optionType, int messageType, Icon icon)
Avec icône personnalisée.
  • optionType définit les boutons disponibles :
    • JOptionPane.YES_NO_OPTION : Boutons Oui et Non ;
    • JOptionPane.YES_NO_CANCEL_OPTION : Boutons Oui, Non et Annuler ;
    • JOptionPane.OK_CANCEL_OPTION : : Boutons OK et Annuler.
  • La méthode retourne le choix de l'utilisateur :
    • JOptionPane.OK_OPTION : Bouton OK cliqué ;
    • JOptionPane.YES_OPTION : Bouton Oui cliqué ;
    • JOptionPane.NO_OPTION : Bouton Non cliqué ;
    • JOptionPane.CANCEL_OPTION : Bouton Annuler cliqué ;
    • JOptionPane.CLOSED_OPTION : Fenêtre fermée sans choix (à considérer comme Annuler ou Non).

Fenêtre de saisie[modifier | modifier le wikicode]

Ce type de fenêtre utilise la méthode showInputDialog et affiche le message et un champ de saisie ou une liste de choix, et les boutons OK et Cancel pour fermer la fenêtre.

Exemple :

String choix = JOptionPane.showInputDialog(frame,
    "Choisissez un nom pour le projet",
    "Choisir un nom",
    JOptionPane.QUESTION_MESSAGE,
    null,           // Pas d'icône personnalisée
    new Object[]{   // Choix possible
        "Wikilivres",
        "Wikipédia",
        "Wiktionnaire",
        null        // null pour autoriser l'utilisateur à entrer une autre valeur
    },
    // Par défaut :
    "Wikilivres");

La méthode retourne la valeur sélectionnée ou entrée par l'utilisateur, ou null si l'utilisateur a choisi d'annuler.

Panel de dialogue interne[modifier | modifier le wikicode]

Les méthodes vu précédemment ont toute une variante pour afficher un panel de dialogue interne, dont le nom est celui de la méthode où show est remplacé par showInternal :

  • showInternalConfirmDialog,
  • showInternalMessageDialog,
  • showInternalInputDialog,
  • showInternalOptionDialog.

Fenêtre de sélection de couleur :[modifier | modifier le wikicode]

La fenêtre de sélection de couleur permet de choisir une couleur parmi une palette prédéfinie ou en spécifiant les composantes RVB ou TSL.

Les arguments de la méthode showDialog de la classe JColorChooser sont :

  • la fenêtre principale sur laquelle la fenêtre de dialogue s'affiche,
  • le titre de la fenêtre de dialogue,
  • la couleur initiale.

La méthode retourne la couleur sélectionnée de classe java.awt.Color ou null si l'utilisateur a annulé.

Exemple : Sélection de la couleur de fond :

Color c_fond = Color.BLACK;

Color c = JColorChooser.showDialog(frame, "Sélectionnez la couleur de fond", c_fond);
if (c != null)
{
    c_fond = c;
    setBackground(c); // ... ou autre mise à jour de couleur
}

Fenêtre de sélection de fichier ou répertoire : JFileChooser[modifier | modifier le wikicode]

La classe JFileChooser définit une fenêtre permettant de sélectionner un ou plusieurs fichiers, ou un répertoire.

Exemple : Sélection pour ouvrir un fichier.

protected File selectionFichierOuvrir()
{
    // Création
    JFileChooser jfc = new JFileChooser();
    // Sélection pour ouvrir un fichier :
    int res = jfc.showOpenDialog(frame); // Retourne le bouton cliqué
    return (res == JFileChooser.APPROVE_OPTION) ? jfc.getSelectedFile() : null;
}

Exemple : Sélection pour enregistrer un fichier.

protected File selectionFichierEnregistrer()
{
    // Création
    JFileChooser jfc = new JFileChooser();
    // Sélection pour enregistrer un fichier :
    int res = jfc.showSaveDialog(frame); // Retourne le bouton cliqué
    return (res == JFileChooser.APPROVE_OPTION) ? jfc.getSelectedFile() : null;
}

Pour conserver le répertoire courant à chaque réouverture, il faut mieux que l'instance de la classe JFileChooser soit déclarée dans la classe et construit par le constructeur.

// Création
JFileChooser jfc = new JFileChooser();

protected File selectionFichierOuvrir()
{
    // Sélection pour ouvrir un fichier :
    int res = jfc.showOpenDialog(frame); // Retourne le bouton cliqué
    return (res == JFileChooser.APPROVE_OPTION) ? jfc.getSelectedFile() : null;
}

protected File selectionFichierEnregistrer()
{
    // Sélection pour enregistrer un fichier :
    int res = jfc.showSaveDialog(frame); // Retourne le bouton cliqué
    return (res == JFileChooser.APPROVE_OPTION) ? jfc.getSelectedFile() : null;
}

Fichier présélectionné[modifier | modifier le wikicode]

Avant l'affichage, on peut changer le répertoire courant et/ou le fichier sélectionné :

// Initialise le fichier sélectionné :
jfc.setSelectedFile(new File("/etc/config/wikilivres.fr.cfg"));
int res = jfc.showOpenDialog(frame); // Retourne le bouton cliqué
// Initialiser le répertoire courant :
jfc.setCurrentDirectory(new File("/etc/config"));
int res = jfc.showOpenDialog(frame); // Retourne le bouton cliqué

Sélectionner plusieurs fichiers[modifier | modifier le wikicode]

Il est possible d'autoriser la sélection de plusieurs fichiers :

protected File[] selectionPlusieursFichiersOuvrir()
{
    jfc.setMultiSelectionEnabled(true);
    int res = jfc.showOpenDialog(frame); // Retourne le bouton cliqué
    return (res == JFileChooser.APPROVE_OPTION) ? jfc.getSelectedFiles() : null; // getSelectedFiles au pluriel
}

Sélectionner un fichier ou un répertoire[modifier | modifier le wikicode]

Le mode de sélection permet de choisir si l'utilisateur peut sélectionner un fichier, un répertoire ou les deux :

jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES );

Filtres de fichier[modifier | modifier le wikicode]

La fenêtre de sélection possède deux types de filtre de classe javax.swing.filechooser.FileFilter :

  • Un filtre général pour ne pas afficher certains fichiers à l'utilisateur,
  • Une liste de filtre que l'utilisateur peut choisir pour afficher différents types de fichiers.

Le filtre général de la vue est configuré en appelant la méthode setFileFilter(FileFilter filter). Exemple :

jfc.setFilter(new javax.swing.filechooser.FileFilter()
    {
        @Override
        public boolean accept(File f)
        {
            return f.getName().toLowerCase().endsWith(".wiki") || f.isDirectory();
        }
        @Override
        public String getDescription()
        {
            return "Wiki source file"; // Description non utilisée pour le filtre général
        }
    });

Les filtres sélectionnables sont ajoutés un par un en appelant la méthode addChoosableFileFilter(FileFilter filter). Exemple :

jfc.addChoosableFileFilter(new javax.swing.filechooser.FileFilter()
    {
        @Override
        public boolean accept(File f)
        {
            return f.getName().toLowerCase().endsWith(".wiki");
        }
        @Override
        public String getDescription()
        {
            return "Wiki source file"; // Description utilisée dans la liste de choix.
        }
    });

Ajouter une prévisualisation[modifier | modifier le wikicode]

La fenêtre de sélection de fichiers peut afficher un composant accessoire pour, par exemple, afficher un aperçu du fichier sélectionné (image, ...). Pour ajouter un tel composant, il faut utiliser la méthode setAccessory(Component newAccessory).

Exemple : Une vue de prévisualisation d'image :

public class FilePreview extends JPanel
implements PropertyChangeListener
{
    // Largeur maximale de prévisualisation :
    private static final int PREF_IMAGE_WIDTH = 150;
    // Marge en pixels :
    private static final int MARGIN = 5;

    public FilePreview()
    {
        setPreferredSize(new Dimension(PREF_IMAGE_WIDTH+MARGIN, -1));
    }

    public void propertyChange(PropertyChangeEvent e)
    {
	    String property_name = e.getPropertyName();
        if (property_name.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
        {
            File selection = (File)e.getNewValue();
            updateView(selection == null ? null : selection.getAbsolutePath());
        }
    }

    private int width, height, xi,yi;
    private Image image, scaled_image=null;

    public static Image getImage(File f)
    {
        if (f==null) return null;
        return getImage(f, f.getPath());
    }
    public static Image getImage(String p)
    {
        if (p==null) return null;
        return getImage(new File(p), p);
    }

    public static Image getImage(File fpath, String path)
    {
        if (fpath==null) return null;
        String lname = path.toLowerCase();
        try { return ImageIO.read(fpath); }
        catch (IOException e) { } // Ignorer l'erreur, ne pas afficher d'image.
        return null;
    }

    protected void updateView(String name)
	{
        info = DEFAULT_INFO;
        image = getImage(name);
        if (image == null && name!=null) info = "Le fichier sélectionné n'est pas une image";
        scaled_image = null;
        repaint();
    }

    @Override
    public void setBounds(int x, int y, int width, int height)
    {
        scaled_image = null;
        super.setBounds(x, y, width, height);
    }

    private static final String DEFAULT_INFO = "Pas d'image sélectionnée";
    private String info = DEFAULT_INFO; // Information sur l'image

    private void scaleImage(Dimension dd)
    {
        xi = MARGIN; yi = 0; // Position du cadre virtuel de l'image
        Dimension d = new Dimension(dd.width-MARGIN, dd.height-20);
        width = image.getWidth(this);
        height = image.getHeight(this);
        info = (width<0||height<0)?DEFAULT_INFO:""+width+"x"+height+" pixels";

        // Mise à l'échelle de l'image si besoin
        // et centrage dans le cadre virtuel :
        if ((d.width==width)&&(d.height==height))
        {
            scaled_image = image;
        }
        else if (width*d.height >= height*d.width)
        {
            height = height * d.width / width;
            width = d.width;
            scaled_image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            yi+=(d.height-height)/2;
        }
        else
        {
            width = width * d.height / height;
            height = d.height;
            scaled_image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            xi+=(d.width-width)/2;
        }
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Dimension d = getSize(); if (d==null) return;
        if ((scaled_image == null)&&(image!=null)) scaleImage(d);
        g.setColor(getBackground());
        g.fillRect(0, 0, d.width, d.height);
        if (scaled_image!=null)
        {
            g.drawImage(scaled_image, xi, yi, this);
        }
        g.setColor(Color.BLACK);
        g.drawString(info, MARGIN, d.height-5);
    }
}

Une instance de cette classe s'ajoute en appelant les deux méthodes ci-dessous :

FilePreview fp = new FilePreview();
jfc.setAccessory(fp);              // Pour l'afficher
jfc.addPropertyChangeListener(fp); // Pour écouter le changement de fichier sélectionné