Créer des interfaces graphiques avec Java Swing
Créer une interface graphique avec Java Swing, ce n’est pas seulement aligner des boutons et des champs de texte sur une fenêtre. C’est apprendre à dialoguer avec l’utilisateur, à guider ses gestes, à rendre un logiciel plus humain, plus compréhensible, plus agréable à utiliser. Swing fait partie de ces technologies Java qui ont traversé les années avec une robustesse remarquable. Même si l’écosystème Java a évolué et que d’autres frameworks ont pris plus de visibilité dans certains contextes, Swing reste encore aujourd’hui un excellent choix pour des outils de bureau, des applications internes, des utilitaires professionnels, des prototypes solides et des logiciels qui demandent de la stabilité avant tout.
Il y a quelque chose de très formateur dans Swing. On y découvre la hiérarchie des composants, la logique des gestionnaires de disposition, l’importance du thread d’interface, la gestion des événements, les fenêtres modales, les menus, les tables, les arbres, les boîtes de dialogue, les panneaux personnalisés, et même la personnalisation visuelle. Apprendre Swing, c’est en quelque sorte apprendre à penser une interface comme un ensemble cohérent, pas comme une suite d’éléments posés au hasard. Et cette manière de penser rend souvent un développeur meilleur, même en dehors de Java.
Dans cet article, nous allons construire une vision complète de Java Swing. Nous partirons des bases, puis nous monterons progressivement en puissance vers des sujets plus concrets et plus avancés. Vous verrez des exemples de code clairs, des explications détaillées, des astuces de conception, et surtout une logique d’ensemble qui vous aidera à créer des interfaces plus propres et plus professionnelles. L’objectif n’est pas de réciter une documentation sèche, mais de comprendre comment Swing se vit au quotidien, dans un vrai projet.
Pourquoi choisir Java Swing aujourd’hui ?
La première question que l’on se pose souvent est simple : pourquoi utiliser Swing alors qu’il existe d’autres solutions ? La réponse n’est pas “parce que c’est le plus moderne”, mais plutôt “parce que c’est fiable, connu, flexible et parfaitement adapté à certains contextes”. Swing est intégré à Java depuis longtemps. Il ne nécessite pas de dépendance complexe pour démarrer. Il offre une grande variété de composants natifs de la bibliothèque standard. Il permet de construire des interfaces rapidement tout en laissant une marge de personnalisation importante.
Swing conserve aussi un avantage très pratique : sa maturité. Quand on développe des outils métiers, des applications de gestion, des interfaces internes de supervision, des petits logiciels pour équipes techniques ou des solutions de laboratoire, on recherche souvent la stabilité, la simplicité de déploiement et la prévisibilité du comportement. Swing répond bien à ces attentes. De plus, de nombreux projets existants reposent encore sur lui, ce qui fait qu’apprendre Swing reste une compétence utile pour maintenir, améliorer ou faire évoluer des applications déjà en production.
Un autre intérêt de Swing, plus subtil, est son modèle de composants. Il vous apprend à découper une interface en pièces logiques, à réutiliser des panneaux, à séparer la présentation du comportement, à réagir proprement aux actions utilisateur et à respecter le thread de l’interface graphique. Ce sont des habitudes précieuses, quelle que soit la technologie utilisée ensuite.
Les fondations de Swing : fenêtre, composants et conteneurs
Dans Swing, presque tout commence par une fenêtre principale. Cette fenêtre est souvent un JFrame, qui représente le conteneur de premier niveau de l’application. À l’intérieur, on ajoute des composants graphiques comme des boutons (JButton), des étiquettes (JLabel), des champs de texte (JTextField), des zones de texte (JTextArea), des cases à cocher (JCheckBox), des listes (JList), des tableaux (JTable) et bien d’autres.
L’idée essentielle à retenir est que Swing est composé de conteneurs et de composants. Un conteneur peut contenir d’autres éléments. Un composant est un élément d’interface qui remplit une fonction visible ou interactive. Un JFrame contient en réalité un content pane, et c’est ce panneau qu’on utilise généralement pour organiser le reste de l’interface. Cette distinction peut sembler technique au début, mais elle devient vite naturelle.
Voici un premier exemple minimal d’application Swing :
import javax.swing.*;
public class FenetreSimple {
public static void main(String[] args) {
JFrame frame = new JFrame("Ma première fenêtre Swing");
frame.setSize(400, 250);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Bonjour Swing !");
frame.add(label);
frame.setVisible(true);
}
}
Ce code fonctionne, mais il reste très rudimentaire. En pratique, il vaut mieux utiliser l’Event Dispatch Thread, que nous verrons plus loin, et penser au positionnement des composants. Mais ce premier exemple montre déjà la logique de base : créer une fenêtre, lui donner un titre, ajouter un composant, puis l’afficher.
L’un des grands pièges des débutants consiste à croire qu’une interface graphique se construit comme une page web, en plaçant les éléments “à la main” avec des coordonnées fixes. Swing n’encourage pas cette approche. Il utilise des gestionnaires de disposition, ou layout managers, qui s’occupent de placer les composants de façon intelligente selon la taille de la fenêtre, l’espace disponible et les règles choisies. C’est ce qui rend Swing à la fois plus propre et plus portable.
Démarrer correctement une application Swing
Avant de créer des interfaces, il faut comprendre une règle fondamentale : les composants Swing doivent être créés et modifiés sur le thread dédié à l’interface graphique, appelé EDT, pour Event Dispatch Thread. Cela évite des comportements imprévisibles, des blocages ou des bugs difficiles à reproduire.
Voici la manière recommandée de lancer une fenêtre Swing :
import javax.swing.*;
public class ApplicationSwing {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Application Swing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 300);
JPanel panel = new JPanel();
JLabel label = new JLabel("Bienvenue dans Swing");
JButton button = new JButton("Cliquez ici");
panel.add(label);
panel.add(button);
frame.setContentPane(panel);
frame.setVisible(true);
});
}
}
SwingUtilities.invokeLater(...) demande à Java d’exécuter le code graphique sur le thread approprié. Ce détail semble anodin, mais il est essentiel. Une application Swing bien structurée respecte cette règle dès le départ.
Il est également conseillé de choisir dès le début une stratégie simple pour organiser le projet. Dans les petites applications, une seule classe peut suffire. Dans les applications un peu plus sérieuses, il devient préférable de séparer la fenêtre principale, les panneaux, les modèles de données et la logique métier. Ce découpage améliore la lisibilité et facilite les évolutions futures.
Les composants Swing les plus utiles
Swing propose un ensemble très riche de composants. Certains sont basiques et apparaissent dans presque toutes les applications. D’autres sont plus spécialisés, mais extrêmement puissants lorsqu’il s’agit d’afficher ou de manipuler des données.
Les étiquettes et les champs de saisie
JLabel sert à afficher un texte statique ou une petite icône. JTextField permet de saisir une ligne de texte. JPasswordField est une variante adaptée à la saisie de mots de passe. JTextArea est utile pour du texte plus long.
import javax.swing.*;
import java.awt.*;
public class FormulaireSimple {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Formulaire simple");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 200);
JPanel panel = new JPanel(new GridLayout(3, 2, 10, 10));
JLabel nomLabel = new JLabel("Nom :");
JTextField nomField = new JTextField();
JLabel emailLabel = new JLabel("Email :");
JTextField emailField = new JTextField();
JButton envoyerButton = new JButton("Envoyer");
panel.add(nomLabel);
panel.add(nomField);
panel.add(emailLabel);
panel.add(emailField);
panel.add(new JLabel());
panel.add(envoyerButton);
frame.setContentPane(panel);
frame.setVisible(true);
});
}
}
Ce petit formulaire montre déjà un point important : même un simple écran gagne à être pensé comme un ensemble structuré. Les libellés doivent être lisibles, les champs bien alignés, les espacements cohérents.
Les boutons et les actions
JButton est sans doute l’un des composants les plus utilisés. Il déclenche une action lorsque l’utilisateur clique dessus. En Swing, les clics et autres interactions sont gérés par des écouteurs d’événements.
import javax.swing.*;
public class BoutonAction {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Bouton");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 150);
JButton button = new JButton("Dire bonjour");
button.addActionListener(e -> {
JOptionPane.showMessageDialog(frame, "Bonjour, utilisateur !");
});
frame.add(button);
frame.setVisible(true);
});
}
}
Ici, lorsque le bouton est cliqué, une boîte de dialogue s’ouvre. Ce genre d’interaction est au cœur de Swing : un composant réagit à un événement et déclenche une action métier ou visuelle.
Cases à cocher, boutons radio et listes déroulantes
Quand une interface doit proposer plusieurs choix, Swing fournit plusieurs options. JCheckBox convient pour une sélection indépendante. JRadioButton convient pour un choix unique parmi plusieurs possibilités. JComboBox affiche une liste déroulante compacte.
import javax.swing.*;
import java.awt.*;
public class ChoixUtilisateurs {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Choix");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 250);
JPanel panel = new JPanel(new GridLayout(4, 1));
JCheckBox newsletter = new JCheckBox("Recevoir la newsletter");
JCheckBox notifications = new JCheckBox("Activer les notifications");
JRadioButton homme = new JRadioButton("Homme");
JRadioButton femme = new JRadioButton("Femme");
ButtonGroup groupe = new ButtonGroup();
groupe.add(homme);
groupe.add(femme);
JPanel genrePanel = new JPanel();
genrePanel.add(homme);
genrePanel.add(femme);
panel.add(newsletter);
panel.add(notifications);
panel.add(genrePanel);
JComboBox<String> pays = new JComboBox<>(new String[] { "Maroc", "France", "Canada", "Belgique" });
panel.add(pays);
frame.setContentPane(panel);
frame.setVisible(true);
});
}
}
Ce type de composant permet de rendre une interface plus naturelle. L’utilisateur comprend immédiatement les options disponibles, sans effort excessif.
Comprendre les gestionnaires de disposition
Les layout managers sont l’un des piliers de Swing. Ils déterminent comment les composants sont organisés dans un conteneur. Leur rôle est crucial, car une interface réussie n’est pas seulement belle sur votre écran de développement : elle doit aussi s’adapter aux tailles de fenêtre, aux polices du système et aux différences de plateforme.
BorderLayout
BorderLayout découpe l’espace en cinq zones : North, South, East, West et Center. C’est un choix très courant pour les fenêtres principales.
import javax.swing.*;
import java.awt.*;
public class BorderLayoutExemple {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("BorderLayout");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 300);
frame.setLayout(new BorderLayout());
frame.add(new JButton("Haut"), BorderLayout.NORTH);
frame.add(new JButton("Bas"), BorderLayout.SOUTH);
frame.add(new JButton("Gauche"), BorderLayout.WEST);
frame.add(new JButton("Droite"), BorderLayout.EAST);
frame.add(new JButton("Centre"), BorderLayout.CENTER);
frame.setVisible(true);
});
}
}
L’intérêt de BorderLayout est sa simplicité. Il donne immédiatement une structure générale à la fenêtre, ce qui le rend parfait pour de nombreuses applications de bureau.
FlowLayout
FlowLayout aligne les composants les uns à la suite des autres, comme du texte qui s’écoule. C’est utile pour de petits ensembles de boutons ou des barres d’outils simples.
import javax.swing.*;
import java.awt.*;
public class FlowLayoutExemple {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("FlowLayout");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 150);
frame.setLayout(new FlowLayout());
frame.add(new JButton("Nouveau"));
frame.add(new JButton("Ouvrir"));
frame.add(new JButton("Enregistrer"));
frame.add(new JButton("Quitter"));
frame.setVisible(true);
});
}
}
Le rendu est simple et naturel. Les composants s’enchaînent, reviennent à la ligne si nécessaire, et s’adaptent à la taille du conteneur.
GridLayout
GridLayout place les composants dans une grille régulière, avec des cellules de taille égale. C’est très pratique pour les formulaires simples, les claviers numériques ou les tableaux de bord compacts.
import javax.swing.*;
import java.awt.*;
public class GridLayoutExemple {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GridLayout");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setLayout(new GridLayout(3, 3, 5, 5));
for (int i = 1; i <= 9; i++) {
frame.add(new JButton("Bouton " + i));
}
frame.setVisible(true);
});
}
}
Dans les interfaces réelles, GridLayout est souvent combiné avec d’autres gestionnaires, car une seule grille ne suffit pas toujours à organiser des écrans complexes.
BoxLayout et les compositions de panneaux
BoxLayout permet d’empiler les composants verticalement ou horizontalement. Il est souvent utilisé en combinaison avec des panneaux imbriqués pour construire des interfaces propres et flexibles.
import javax.swing.*;
import java.awt.*;
public class BoxLayoutExemple {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("BoxLayout");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(350, 250);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(new JLabel("Nom :"));
panel.add(new JTextField());
panel.add(Box.createVerticalStrut(10));
panel.add(new JLabel("Email :"));
panel.add(new JTextField());
panel.add(Box.createVerticalStrut(10));
panel.add(new JButton("Valider"));
frame.setContentPane(panel);
frame.setVisible(true);
});
}
}
La vraie force de Swing apparaît souvent ici : dans l’assemblage de panneaux imbriqués. Au lieu de tout mettre dans une seule grande fenêtre, on compose l’écran en petits blocs compréhensibles. Cette approche rend le code plus lisible et l’interface plus facile à faire évoluer.
Gérer les événements proprement
Une interface graphique n’est pas seulement une image. C’est un système vivant. L’utilisateur clique, tape, sélectionne, déplace, ferme, ouvre, et chaque geste peut déclencher une réaction. Swing s’appuie pour cela sur le modèle des événements et des écouteurs.
Un bouton utilise par exemple un ActionListener. Un champ de texte peut utiliser un ActionListener pour valider la saisie. Une case à cocher peut déclencher un changement d’état. Une liste peut réagir à la sélection. Le principe est toujours le même : un composant émet un événement, et un écouteur réagit.
Voici un exemple plus réaliste, avec saisie de texte et validation :
import javax.swing.*;
import java.awt.*;
public class ValidationFormulaire {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Validation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 200);
JPanel panel = new JPanel(new BorderLayout(10, 10));
JTextField champ = new JTextField();
JButton valider = new JButton("Valider");
JLabel resultat = new JLabel("Entrez votre nom puis validez.");
valider.addActionListener(e -> {
String nom = champ.getText().trim();
if (nom.isEmpty()) {
resultat.setText("Le champ ne peut pas être vide.");
} else {
resultat.setText("Bonjour, " + nom + " !");
}
});
JPanel center = new JPanel(new BorderLayout(5, 5));
center.add(new JLabel("Nom :"), BorderLayout.WEST);
center.add(champ, BorderLayout.CENTER);
panel.add(center, BorderLayout.CENTER);
panel.add(valider, BorderLayout.SOUTH);
panel.add(resultat, BorderLayout.NORTH);
frame.setContentPane(panel);
frame.setVisible(true);
});
}
}
Ce type de logique est très courant : on récupère l’entrée utilisateur, on la nettoie, on la valide, puis on affiche un retour clair. Une interface bien conçue ne laisse pas l’utilisateur dans le doute. Elle explique ce qui se passe.
Créer des fenêtres professionnelles avec JFrame
Un JFrame est la base de nombreuses applications Swing, mais une fenêtre de qualité ne se limite pas à un simple cadre. Elle doit proposer un titre clair, une taille adaptée, un comportement de fermeture cohérent, une organisation interne logique et éventuellement une barre de menu ou une barre d’outils.
Voici un exemple de fenêtre plus propre :
import javax.swing.*;
import java.awt.*;
public class FenetrePropre {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Gestion de contacts");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(700, 450);
frame.setLocationRelativeTo(null);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
JLabel titre = new JLabel("Liste des contacts");
titre.setFont(new Font("SansSerif", Font.BOLD, 20));
JTextArea zone = new JTextArea();
zone.setText("Amine - amine@example.com\nSara - sara@example.com");
zone.setEditable(false);
JButton ajouter = new JButton("Ajouter un contact");
JButton supprimer = new JButton("Supprimer un contact");
JPanel actions = new JPanel(new FlowLayout(FlowLayout.RIGHT));
actions.add(ajouter);
actions.add(supprimer);
mainPanel.add(titre, BorderLayout.NORTH);
mainPanel.add(new JScrollPane(zone), BorderLayout.CENTER);
mainPanel.add(actions, BorderLayout.SOUTH);
frame.setContentPane(mainPanel);
frame.setVisible(true);
});
}
}
On voit ici plusieurs bonnes pratiques : centrer la fenêtre, utiliser des marges, choisir une typographie lisible, contenir la zone de texte dans un JScrollPane pour permettre le défilement, et séparer clairement le contenu des actions.
Les boîtes de dialogue : informer, demander, confirmer
Les boîtes de dialogue sont indispensables dans une interface bureautique. Elles permettent d’avertir l’utilisateur, de demander une confirmation, de saisir une information ou de signaler une erreur. Swing fournit plusieurs outils simples via JOptionPane.
import javax.swing.*;
public class BoitesDialogue {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Dialogue");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setVisible(true);
JOptionPane.showMessageDialog(frame, "Opération réussie !");
int choix = JOptionPane.showConfirmDialog(
frame,
"Voulez-vous vraiment supprimer cet élément ?",
"Confirmation",
JOptionPane.YES_NO_OPTION
);
if (choix == JOptionPane.YES_OPTION) {
JOptionPane.showMessageDialog(frame, "Élément supprimé.");
} else {
JOptionPane.showMessageDialog(frame, "Suppression annulée.");
}
String saisie = JOptionPane.showInputDialog(frame, "Entrez votre prénom :");
if (saisie != null) {
JOptionPane.showMessageDialog(frame, "Bonjour " + saisie + " !");
}
});
}
}
Ces boîtes de dialogue sont très pratiques pour les interactions rapides. Elles permettent de garder l’interface principale légère tout en gérant les actions sensibles ou ponctuelles.
Menus, barres d’outils et navigation de l’application
Une application de bureau sérieuse dispose souvent d’une barre de menu. Swing propose JMenuBar, JMenu et JMenuItem pour construire des menus riches et cohérents.
import javax.swing.*;
import java.awt.event.KeyEvent;
public class MenuSwing {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Menu Swing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 300);
JMenuBar menuBar = new JMenuBar();
JMenu fichier = new JMenu("Fichier");
fichier.setMnemonic(KeyEvent.VK_F);
JMenuItem nouveau = new JMenuItem("Nouveau");
JMenuItem ouvrir = new JMenuItem("Ouvrir");
JMenuItem quitter = new JMenuItem("Quitter");
quitter.addActionListener(e -> System.exit(0));
fichier.add(nouveau);
fichier.add(ouvrir);
fichier.addSeparator();
fichier.add(quitter);
JMenu aide = new JMenu("Aide");
JMenuItem propos = new JMenuItem("À propos");
propos.addActionListener(e -> JOptionPane.showMessageDialog(frame, "Application Swing v1.0"));
aide.add(propos);
menuBar.add(fichier);
menuBar.add(aide);
frame.setJMenuBar(menuBar);
frame.setVisible(true);
});
}
}
Les menus donnent une architecture de navigation claire. Ils sont utiles dans les logiciels complets, car ils regroupent les fonctions de manière logique. On peut y ajouter des raccourcis clavier, des séparateurs et des sous-menus.
Il existe aussi les barres d’outils, souvent construites avec un simple JToolBar. Elles offrent un accès rapide aux actions fréquentes. Dans un outil métier, le menu et la barre d’outils se complètent très bien.
Les tables avec JTable : un incontournable
Parmi tous les composants Swing, JTable est l’un des plus précieux dans les applications de gestion. Il permet d’afficher des données tabulaires, de les manipuler, de les trier, de les éditer et de les présenter de manière structurée.
Voici un exemple simple de table :
import javax.swing.*;
public class TableSimple {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Table Swing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 300);
String[] colonnes = {"Nom", "Email", "Âge"};
Object[][] donnees = {
{"Amine", "amine@example.com", 28},
{"Sara", "sara@example.com", 25},
{"Youssef", "youssef@example.com", 31}
};
JTable table = new JTable(donnees, colonnes);
JScrollPane scrollPane = new JScrollPane(table);
frame.add(scrollPane);
frame.setVisible(true);
});
}
}
Ce modèle est pratique pour démarrer, mais les vrais projets vont généralement plus loin avec un modèle personnalisé. Le principe de Swing repose alors sur TableModel, ce qui permet de séparer l’affichage des données de la structure interne.
Voici un exemple plus propre avec un modèle de table personnalisé :
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;
class Contact {
private final String nom;
private final String email;
private final int age;
public Contact(String nom, String email, int age) {
this.nom = nom;
this.email = email;
this.age = age;
}
public String getNom() { return nom; }
public String getEmail() { return email; }
public int getAge() { return age; }
}
class ContactTableModel extends AbstractTableModel {
private final List<Contact> contacts = new ArrayList<>();
private final String[] colonnes = {"Nom", "Email", "Âge"};
public ContactTableModel() {
contacts.add(new Contact("Amine", "amine@example.com", 28));
contacts.add(new Contact("Sara", "sara@example.com", 25));
contacts.add(new Contact("Youssef", "youssef@example.com", 31));
}
@Override
public int getRowCount() {
return contacts.size();
}
@Override
public int getColumnCount() {
return colonnes.length;
}
@Override
public String getColumnName(int column) {
return colonnes[column];
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Contact contact = contacts.get(rowIndex);
return switch (columnIndex) {
case 0 -> contact.getNom();
case 1 -> contact.getEmail();
case 2 -> contact.getAge();
default -> null;
};
}
}
public class TableModelePersonnalise {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Table personnalisée");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 300);
JTable table = new JTable(new ContactTableModel());
frame.add(new JScrollPane(table));
frame.setVisible(true);
});
}
}
Ce style de conception est bien plus robuste. Les données sont encapsulées dans des objets, la table sait seulement comment les afficher, et l’application reste extensible.
Les formulaires : une mise en page claire avant tout
Beaucoup d’applications Swing servent à saisir, modifier ou consulter des données. Les formulaires sont donc centraux. Un formulaire réussi dépend moins du nombre de composants que de la clarté de la structure. Les libellés doivent être placés de manière cohérente. Les champs doivent être alignés. Les zones d’action doivent être visibles sans surcharger l’écran.
Voici un exemple de formulaire plus réaliste :
import javax.swing.*;
import java.awt.*;
public class FormulaireContact {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Nouveau contact");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 350);
frame.setLocationRelativeTo(null);
JPanel formulaire = new JPanel(new GridBagLayout());
formulaire.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(8, 8, 8, 8);
gbc.fill = GridBagConstraints.HORIZONTAL;
JLabel nomLabel = new JLabel("Nom :");
JTextField nomField = new JTextField(20);
JLabel emailLabel = new JLabel("Email :");
JTextField emailField = new JTextField(20);
JLabel phoneLabel = new JLabel("Téléphone :");
JTextField phoneField = new JTextField(20);
gbc.gridx = 0;
gbc.gridy = 0;
formulaire.add(nomLabel, gbc);
gbc.gridx = 1;
formulaire.add(nomField, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
formulaire.add(emailLabel, gbc);
gbc.gridx = 1;
formulaire.add(emailField, gbc);
gbc.gridx = 0;
gbc.gridy = 2;
formulaire.add(phoneLabel, gbc);
gbc.gridx = 1;
formulaire.add(phoneField, gbc);
JButton enregistrer = new JButton("Enregistrer");
gbc.gridx = 0;
gbc.gridy = 3;
gbc.gridwidth = 2;
gbc.anchor = GridBagConstraints.CENTER;
formulaire.add(enregistrer, gbc);
frame.setContentPane(formulaire);
frame.setVisible(true);
});
}
}
GridBagLayout demande un peu de pratique, mais il offre une liberté remarquable. Il est particulièrement adapté aux formulaires professionnels où l’on veut un alignement précis et une structure évolutive.
Travailler avec les onglets et les panneaux multiples
Quand une application devient plus riche, il devient utile de répartir l’interface sur plusieurs panneaux ou onglets. JTabbedPane permet de séparer des sections logiques, par exemple “Informations”, “Paramètres”, “Historique” ou “Statistiques”.
import javax.swing.*;
import java.awt.*;
public class OngletsSwing {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Onglets");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 400);
JTabbedPane tabbedPane = new JTabbedPane();
JPanel profilPanel = new JPanel();
profilPanel.add(new JLabel("Informations de profil"));
JPanel securitePanel = new JPanel();
securitePanel.add(new JLabel("Paramètres de sécurité"));
JPanel notificationsPanel = new JPanel();
notificationsPanel.add(new JLabel("Préférences de notification"));
tabbedPane.addTab("Profil", profilPanel);
tabbedPane.addTab("Sécurité", securitePanel);
tabbedPane.addTab("Notifications", notificationsPanel);
frame.add(tabbedPane);
frame.setVisible(true);
});
}
}
Les onglets sont une excellente solution pour éviter de surcharger une seule page. Ils rendent l’interface plus respirable et plus intuitive.
Le rôle du modèle MVC dans une application Swing
Si vous travaillez sur un vrai projet, le découpage MVC, pour Model-View-Controller, devient presque indispensable. Swing ne vous impose pas strictement MVC, mais il s’y prête très bien. Le modèle contient les données et les règles métier. La vue affiche ces données. Le contrôleur gère les interactions et les événements.
Dans une application simple, on peut être tenté de tout mettre dans une seule classe. Cela fonctionne au début, mais cela devient vite difficile à maintenir. Dans un projet sérieux, il faut séparer les responsabilités. Par exemple, une classe Contact peut représenter un contact. Une classe ContactTableModel peut gérer l’affichage des contacts dans une table. Une classe ContactFrame peut construire la fenêtre. Une classe ContactController peut connecter les actions utilisateur à la logique métier.
Cette séparation apporte plusieurs bénéfices concrets. Le code devient plus testable. Les corrections sont plus simples. L’évolution de l’interface ne casse pas directement les données. Et surtout, la lecture du projet devient beaucoup plus agréable. On sait où chercher quand on veut modifier une fonctionnalité.
Personnaliser l’apparence avec Look and Feel
Swing possède une philosophie particulière : les composants sont dessinés par Java, ce qui permet de personnaliser leur apparence. L’un des mécanismes les plus intéressants est le Look and Feel, qui détermine l’aspect visuel général des composants. Par défaut, Swing peut utiliser un style proche du système, ou un thème spécifique.
Voici comment tenter de passer au Look and Feel du système :
import javax.swing.*;
public class LookAndFeelSysteme {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Look and Feel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 200);
frame.add(new JLabel("Interface adaptée au système", SwingConstants.CENTER));
frame.setVisible(true);
});
}
}
Cette personnalisation peut sembler modeste, mais elle influence fortement l’impression générale. Une interface avec un style cohérent inspire confiance. Elle paraît plus intégrée à l’environnement de l’utilisateur.
Au-delà du Look and Feel, vous pouvez aussi régler les polices, les marges, les icônes, les couleurs et l’aspect de certains composants. Il faut cependant garder une certaine retenue. Une bonne interface ne cherche pas à impressionner par excès d’effets, mais à offrir une lecture simple et une utilisation confortable.
Dessiner ses propres composants
L’un des aspects les plus intéressants de Swing est la possibilité de créer des composants personnalisés. Vous pouvez étendre JPanel, redéfinir paintComponent(...) et dessiner exactement ce dont vous avez besoin.
Voici un exemple simple :
import javax.swing.*;
import java.awt.*;
class CerclePanel extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillOval(50, 50, 100, 100);
g.setColor(Color.BLACK);
g.drawString("Cercle personnalisé", 50, 180);
}
}
public class DessinPersonnalise {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Dessin Swing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.add(new CerclePanel());
frame.setVisible(true);
});
}
}
Cette possibilité ouvre la porte à des visualisations plus avancées, des indicateurs de progression personnalisés, des zones de dessin, des graphiques simples, des composants décoratifs ou des écrans de monitoring. Swing n’est pas uniquement une bibliothèque de formulaires. C’est aussi un environnement de rendu graphique très capable.
Gérer le redimensionnement et l’ergonomie
Une bonne interface ne doit pas être figée. Les utilisateurs redimensionnent les fenêtres, utilisent différentes résolutions et changent parfois les réglages d’affichage. Votre interface doit donc rester lisible dans des conditions variées. C’est précisément l’intérêt des gestionnaires de disposition.
Mais le redimensionnement ne suffit pas. Il faut aussi penser à l’ergonomie : une taille de police lisible, des espacements raisonnables, des boutons suffisamment grands, des libellés clairs, une hiérarchie visuelle compréhensible. Une interface peut techniquement fonctionner et pourtant être désagréable à utiliser. La différence entre une application bricolée et une application soignée se joue souvent sur ces détails.
Une astuce simple consiste à toujours tester l’écran dans plusieurs tailles. Réduisez la fenêtre. Agrandissez-la. Vérifiez si les composants se compressent bien. Observez si les textes restent visibles. Un composant mal placé révèle souvent une structure de layout trop rigide.
Les images, icônes et ressources visuelles
L’utilisation d’icônes apporte souvent une aide précieuse à la compréhension. Un bouton avec une icône peut être plus rapide à identifier qu’un texte seul, à condition que l’icône soit claire et cohérente. Swing permet d’utiliser facilement des images via ImageIcon.
import javax.swing.*;
import java.awt.*;
public class IconeSwing {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Icône");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
ImageIcon icon = new ImageIcon("src/main/resources/save.png");
JButton saveButton = new JButton("Enregistrer", icon);
frame.setLayout(new FlowLayout());
frame.add(saveButton);
frame.setVisible(true);
});
}
}
Dans un vrai projet, il vaut mieux charger les ressources proprement depuis le classpath et non depuis des chemins fragiles. L’idée est que l’application puisse fonctionner de manière fiable une fois déployée. Les icônes doivent aussi être cohérentes entre elles : même style, même épaisseur visuelle, même intention.
Construire une application complète : exemple de mini gestionnaire de tâches
Pour donner du sens à tout ce que nous avons vu, prenons un exemple plus concret : une petite application de gestion de tâches. L’idée est simple : l’utilisateur saisit une tâche, l’ajoute à une liste et peut ensuite la visualiser. Même si le projet reste modeste, il permet de réunir plusieurs notions importantes : fenêtre principale, champs de saisie, bouton d’action, liste, panneau organisé et logique utilisateur.
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class GestionnaireTaches {
private final List<String> taches = new ArrayList<>();
private final DefaultListModel<String> listModel = new DefaultListModel<>();
private JTextField champTache;
public void creerInterface() {
JFrame frame = new JFrame("Gestionnaire de tâches");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 400);
frame.setLocationRelativeTo(null);
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
JLabel titre = new JLabel("Mes tâches");
titre.setFont(new Font("SansSerif", Font.BOLD, 24));
champTache = new JTextField();
JButton ajouterButton = new JButton("Ajouter");
JButton supprimerButton = new JButton("Supprimer sélection");
JPanel inputPanel = new JPanel(new BorderLayout(10, 10));
inputPanel.add(champTache, BorderLayout.CENTER);
inputPanel.add(ajouterButton, BorderLayout.EAST);
JList<String> listeTaches = new JList<>(listModel);
JScrollPane scrollPane = new JScrollPane(listeTaches);
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
bottomPanel.add(supprimerButton);
ajouterButton.addActionListener(e -> ajouterTache());
supprimerButton.addActionListener(e -> {
int index = listeTaches.getSelectedIndex();
if (index >= 0) {
listModel.remove(index);
taches.remove(index);
} else {
JOptionPane.showMessageDialog(frame, "Sélectionnez une tâche à supprimer.");
}
});
mainPanel.add(titre, BorderLayout.NORTH);
mainPanel.add(inputPanel, BorderLayout.CENTER);
mainPanel.add(scrollPane, BorderLayout.SOUTH);
mainPanel.add(bottomPanel, BorderLayout.EAST);
frame.setContentPane(mainPanel);
frame.setVisible(true);
}
private void ajouterTache() {
String tache = champTache.getText().trim();
if (tache.isEmpty()) {
JOptionPane.showMessageDialog(null, "La tâche ne peut pas être vide.");
return;
}
taches.add(tache);
listModel.addElement(tache);
champTache.setText("");
champTache.requestFocus();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new GestionnaireTaches().creerInterface());
}
}
Ce petit programme illustre une vraie manière de penser Swing. La logique métier est isolée dans une structure de données. L’interface affiche la liste. Les actions utilisateur modifient l’état. Le code reste compact tout en étant compréhensible. Dans un projet réel, on pourrait encore séparer davantage la vue, le modèle et le contrôleur, mais déjà ici l’architecture est bien plus saine qu’un bloc de code monolithique.
Les bonnes pratiques à adopter dès le début
Quand on apprend Swing, il est tentant de se concentrer uniquement sur le résultat visuel. Pourtant, la qualité d’une interface dépend aussi de la discipline de développement. Quelques habitudes simples font une grande différence.
D’abord, respectez toujours le thread Swing. Ensuite, utilisez les layout managers au lieu de positions absolues. Ensuite, découpez votre interface en panneaux réutilisables. Ensuite, nommez vos composants et vos variables de façon claire. Ensuite, évitez de mettre toute la logique dans les écouteurs. Enfin, testez l’ergonomie avec de vraies interactions. Un bouton trop petit, un message ambigu ou un formulaire confus peuvent ruiner l’expérience utilisateur.
Un autre point essentiel est la cohérence visuelle. Les marges, les espacements, les couleurs, les tailles de police et les alignements doivent raconter la même histoire. Quand l’interface semble “respirer”, l’utilisateur se sent à l’aise. Quand elle paraît serrée et désordonnée, il ressent immédiatement une fatigue visuelle, même s’il ne sait pas l’expliquer.
Gérer les longues opérations sans bloquer l’interface
Une erreur classique consiste à lancer une opération longue directement dans le thread de l’interface. Résultat : la fenêtre se fige, les boutons ne répondent plus et l’utilisateur a l’impression que le logiciel est cassé. Pour éviter cela, Swing propose des mécanismes comme SwingWorker, qui permettent d’exécuter le travail en arrière-plan tout en mettant à jour l’interface proprement.
Voici une illustration simple :
import javax.swing.*;
public class TravailEnArrierePlan {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Téléchargement");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 200);
JButton lancer = new JButton("Lancer la tâche");
JLabel statut = new JLabel("Prêt");
lancer.addActionListener(e -> {
statut.setText("Traitement en cours...");
lancer.setEnabled(false);
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
Thread.sleep(3000);
return null;
}
@Override
protected void done() {
statut.setText("Traitement terminé.");
lancer.setEnabled(true);
}
};
worker.execute();
});
JPanel panel = new JPanel();
panel.add(lancer);
panel.add(statut);
frame.add(panel);
frame.setVisible(true);
});
}
}
Cette idée est essentielle dans les applications réelles. Dès qu’une tâche prend un peu de temps, il faut penser à ne pas bloquer l’interface. Cela vaut pour des requêtes réseau, des chargements de fichiers, des calculs complexes ou des accès à une base de données.
Swing et l’organisation d’un vrai projet
Dans une petite démonstration, on peut se contenter d’une seule classe. Mais dès que le projet grandit, l’organisation devient capitale. Une architecture simple pourrait ressembler à cela : un package pour les modèles, un package pour les vues, un package pour les contrôleurs, un package pour les utilitaires. Les composants réutilisables doivent être isolés. Les règles de validation doivent être centralisées. Les libellés et les messages doivent être uniformes.
Cette discipline aide énormément quand il faut faire évoluer l’application. Par exemple, si vous voulez remplacer une liste simple par une table plus riche, vous pouvez modifier la vue sans casser le cœur du programme. Si vous voulez changer le style des boutons, vous pouvez le faire en un seul endroit. Si vous voulez ajouter une nouvelle fonctionnalité, vous savez déjà où l’insérer.
Un autre avantage de cette organisation est la testabilité. Même si Swing n’est pas toujours aussi simple à tester que d’autres couches logicielles, une bonne séparation permet d’écrire davantage de tests sur la logique métier et moins de code fragile dans l’interface.
Conseils pour écrire une interface agréable à utiliser
Une interface réussie n’est pas seulement fonctionnelle. Elle est rassurante. Elle indique clairement où regarder, quoi faire et ce qui vient de se produire. Pour cela, quelques principes sont très utiles. Les labels doivent être précis. Les boutons doivent utiliser des verbes d’action. Les messages d’erreur doivent être concrets. Les confirmations doivent être réservées aux actions vraiment importantes. Les écrans doivent éviter l’encombrement. Les sections doivent avoir une hiérarchie visuelle claire.
Il faut aussi penser au rythme de l’utilisateur. Une interface trop bavarde fatigue. Une interface trop silencieuse inquiète. Swing vous donne les outils pour trouver un équilibre : zones de statut, boîtes de dialogue, changements visuels, désactivation temporaire de boutons, messages explicites. L’expérience utilisateur se construit souvent à partir de détails très simples.
Quand Swing reste un excellent choix
Swing n’est peut-être pas la technologie la plus tendance du moment, mais cela ne diminue en rien sa valeur. Il reste particulièrement intéressant pour les applications de bureau stables, les outils internes, les interfaces administratives, les petits logiciels d’entreprise, les projets pédagogiques et les programmes qui doivent fonctionner sans dépendre d’une chaîne lourde de composants externes. Pour quelqu’un qui apprend l’interface graphique en Java, Swing est aussi un formidable terrain d’entraînement.
Il apporte une vraie compréhension des principes fondamentaux : événements, conteneurs, disposition, rendu, threading, interaction et séparation des responsabilités. Même si vous utilisez plus tard JavaFX ou une autre technologie, ce que vous aurez appris avec Swing ne sera jamais perdu. Au contraire, vous aurez développé une intuition très utile sur la manière dont une interface se construit réellement.
Conclusion
Créer des interfaces graphiques avec Java Swing, c’est entrer dans un univers à la fois classique et étonnamment riche. On commence avec une simple fenêtre, puis on découvre les composants, les événements, les mises en page, les boîtes de dialogue, les menus, les tables, les onglets, les styles visuels et les bonnes pratiques de conception. Peu à peu, l’interface cesse d’être un assemblage de boutons pour devenir une expérience fluide et structurée.
Le plus beau dans Swing, c’est peut-être sa capacité à vous faire progresser sur deux plans à la fois. D’un côté, vous apprenez à construire des écrans. De l’autre, vous apprenez à penser l’architecture logicielle. Chaque panneau, chaque écouteur, chaque modèle, chaque fenêtre vous pousse à écrire du code plus clair et plus responsable. Et dans un monde où les interfaces sont partout, cette compétence a une vraie valeur.
Si vous débutez, commencez simple. Faites une fenêtre, ajoutez un bouton, puis un champ, puis une liste, puis un formulaire. Testez, corrigez, améliorez. Prenez l’habitude de respecter le thread Swing et d’utiliser les bons gestionnaires de disposition. Ensuite, quand vous serez à l’aise, assemblez des écrans plus complets, intégrez des modèles de données, créez vos propres composants et soignez l’expérience utilisateur. C’est ainsi que l’on passe d’un premier essai à une vraie interface professionnelle.