Programmation Java Swing/Fenêtres

Un livre de Wikilivres.

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.

Menu[modifier | modifier le wikicode]

Les fenêtres de type javax.swing.JFrame et javax.swing.JDialog peuvent avoir une barre de menus.

Voir le chapitre suivant sur les menus pour plus de détails.

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),
  • ...

États de la fenêtre[modifier | modifier le wikicode]

La méthode setExtendedState(int state) permet de modifier l'état de la fenêtre, et la méthode int getExtendedState() permet de récupérer l'état actuel :

NORMAL
Affichage normal,
ICONIFIED
Fenêtre icônifiée sur le bureau ou réduite dans la barre des tâches,
MAXIMIZED_HORIZ
Fenêtre affichée sur toute la largeur de l'écran,
MAXIMIZED_VERT
Fenêtre affichée sur toute la hauteur de l'écran,
MAXIMIZED_BOTH
Fenêtre affichée sur tout l'écran (combine les deux états précédents).

Icône de fenêtre[modifier | modifier le wikicode]

L'icône de la fenêtre d'application est visible, selon le système d'exploitation, dans le coin du bord de la fenêtre (sous Windows, en haut à gauche en 16x16 pixels), dans la barre des tâches (taille variable : 16x16, 48x48, 64x64, ...), dans le sélecteur de fenêtres (sous Windows avec AltTab ↹ ou Tab ↹). L'icône de la fenêtre d'application peut aussi être utilisée par les boîtes de dialogue ayant la fenêtre comme propriétaire (owner) passé au constructeur.

Deux méthodes existent :

  • La méthode setIconImage(java.awt.Image image) définit une image comme icône. Cette image est redimensionnée selon la taille nécessaire. Il est donc recommandé d'utiliser une image de grande résolution (64x64 pixels minimum) afin d'éviter un agrandissement produisant des blocs de pixels. Celle-ci doit donc aussi pouvoir être réduite sans perte de visibilité.
  • La méthode setIconImages(java.util.List<java.awt.Image> images) permet de définir une liste d'images. Selon la taille d'icône du contexte (exemple : 16x16 pour l'icône du bord de fenêtre, 64x64 pour la barre des tâches, ...), une image suffisamment grande est sélectionnée et redimensionnée si nécessaire. Cette méthode est recommandée si l'icône change en fonction de la taille, pour par exemple y mettre moins d'éléments pour les plus petites tailles d'icône.

La méthode setIconImage(java.awt.Image image) est en fait un appel à la méthode setIconImages(java.util.List<java.awt.Image> images) en lui passant une liste contenant une seule image.

En général, l'icône est une image stockée dans le même répertoire que les classes de l'application, dans un format supporté par ImageIO (PNG, GIF). Exemple pour une image stockée dans le même répertoire que la classe de la fenêtre :

/**
 * Créer la fenêtre principale.
 * @throws IOException Erreur de lecture de ressource interne.
 */
public MainWindow() throws IOException
{
	Image image_icon = ImageIO.read(MainWindow.class.getResource("icone_application.png"));
	setIconImage(image_icon);
}

Pour une liste d'icônes :

public MainWindow() throws IOException
{
	List<Image> liste_icones = new ArrayList<>();
	liste_icones.add(ImageIO.read(MainWindow.class.getResource("icone_application_16.png"))); // 16x16
	liste_icones.add(ImageIO.read(MainWindow.class.getResource("icone_application_48.png"))); // 48x48
	liste_icones.add(ImageIO.read(MainWindow.class.getResource("icone_application_64.png"))); // 64x64
	setIconImages(liste_icones);
}

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.\nUne seconde ligne d'information.",
    "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é 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.

Message en HTML[modifier | modifier le wikicode]

Il est possible d'utiliser le format HTML pour formater le message affiché. Pour cela, il faut encadrer le message dans une balise <html> et ne pas utiliser de caractères de contrôle dans le message ; par exemple "\n" qui doit être remplacé par <br/>.

Exemple :

JOptionPane.showMessageDialog(frame,
    "<html>Une erreur s'est produite pour illustrer cette section.<br/>" +
    "<b>Ceci est une information importante.</b></html>",
    "Erreur de démonstration", JOptionPane.ERROR_MESSAGE);

Pour plus de détails sur le formatage en HTML et les limitations, voir le chapitre sur le contenu en HTML.

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 toutes 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 : JColorChooser[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 vaut 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.
        }
    });

Vous pouvez utiliser la sous-classe javax.swing.filechooser.FileNameExtensionFilter pour les filtres basés sur l'extension de fichier. Le constructeur prend en paramètre la description du filtre suivie de la liste des extensions de nom des fichiers acceptés :

FileFilter file_filter_image = new FileNameExtensionFilter("Image d'illustration", "png","bmp","jpeg","jpg","gif");

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 :

import java.awt.*;
import java.beans.*;
import java.io.*;

import javax.imageio.*;
import javax.swing.*;

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

    // Méthode de l'interface PropertyChangeListener appelée
    // quand une propriété est modifiée, pour mettre à jour
    // la prévisualisation quand le fichier sélectionné change.
    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(String p)
    {
        if (p==null) return null;
        return getImage(new File(p));
    }

    public static Image getImage(File fpath)
    {
        if (fpath==null) return null;
        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é