Programmation Java Swing/Version imprimable
Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Programmation_Java_Swing
Introduction
Principes
modifierLa bibliothèque de composants Swing est définie dans le package javax.swing
et utilise AWT (java.awt
) :
- La gestion des évènements est celle des composants AWT basée sur les mêmes écouteurs d'évènements.
- Les types
Color
,Font
,Dimension
,Insets
... du packagejava.awt
sont utilisés par Swing également. - Les gestionnaires de disposition des composants définis par AWT sont également utilisés par les conteneurs Swing.
- Les composants Swing dérivent tous du composant de base défini par la classe
javax.swing.JComponent
. Cette classe dérive de la classejava.awt.Container
. Cela signifie que tout composant Swing peut contenir d'autres composants. Cependant, le conteneur privilégié est le panel défini par la classejavax.swing.JPanel
.
Le nom des classes Swing reprend le nom de la classe équivalente AWT préfixé par la lettre J : JComponent, JContainer, JPanel, JFrame...
Fenêtres simples
modifierLa fenêtre la plus simple est une classe vide héritant de la classe javax.swing.JFrame
qui est vide par défaut.
Pour les opérations de mise à jour graphiques il vaut mieux utiliser le thread dédié (thread de répartition des évènements graphiques) pour éviter les problèmes de synchronisation, comme illustré par la méthode statique main
ci-dessous pour créer et afficher la fenêtre :
package org.wikibooks.fr.swing;
import java.awt.*;
import javax.swing.*;
/**
* Fenêtre simple.
* @author fr.wikibooks.org
*/
public class FenetreSimple extends JFrame
{
public static void main(String[] args)
{
// Utiliser le thread dédié à l'interface graphique
// pour les opérations graphiques :
EventQueue.invokeLater(new Runnable()
{
public void run()
{
FenetreSimple frame = new FenetreSimple(); // Créer
frame.setVisible(true); // Afficher
}
});
}
}
Pour tester cet exemple :
- Copiez le code source dans un fichier nommé
FenetreSimple.java
dans une hiérarchie de répertoireorg/wikibooks/fr/swing
pour le paquetage déclaré ; - Compilez-le avec la commande
javac org.wikibooks.fr.swing.FenetreSimple
- Lancez le programme avec la commande
java -cp . org.wikibooks.fr.swing.FenetreSimple
- ou comme il n'est pas nécessaire d'avoir une console :
javaw -cp . org.wikibooks.fr.swing.FenetreSimple
La fenêtre ne comporte pas de titre, de taille réduite et située en haut à gauche, le bouton de fermeture permet toutefois de fermer la fenêtre. Cependant, l'application tourne encore. On s'en aperçoit en lançant l'application avec un IDE (tel Eclipse). Le thread de répartition des évènements graphiques continue de tourner.
Pour que l'application s'arrête (fin de tous les threads), il est nécessaire d'ajouter un écouteur d'évènement sur la fermeture de la fenêtre.
L'exemple ci-dessous montre cela et comment définir un titre, la taille et la position de la fenêtre dans le constructeur de la classe.
La méthode main
de lancement de l'application reste la même :
package org.wikibooks.fr.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Fenêtre simple.
* @author fr.wikibooks.org
*/
public class SecondeFenetreSimple extends JFrame
{
public SecondeFenetreSimple()
{
setTitle("Une deuxième fenêtre simple");
setSize(new Dimension(600,400));
setLocation(new Point(200, 100));
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter()
{
/* (non-Javadoc)
* @see java.awt.event.WindowAdapter#windowClosing(java.awt.event.WindowEvent)
*/
@Override
public void windowClosing(WindowEvent e)
{
dispose();
System.exit(0);
}
});
}
public static void main(String[] args)
{
// Utiliser le thread dédié à l'interface graphique
// pour les opérations graphiques :
EventQueue.invokeLater(new Runnable()
{
public void run()
{
SecondeFenetreSimple frame = new SecondeFenetreSimple(); // Créer
frame.setVisible(true); // Afficher
}
});
}
}
Composants
De manière générale, un composant dans une interface graphique possède une zone rectangulaire permettant d'afficher une ou plusieurs informations et permet à l'utilisateur d'interagir avec lui en utilisant la souris ou le clavier.
Divers composants
modifierCette section liste brièvement quelques composants Swing décrits plus en détails dans les sections suivantes. Les composants Swing sont nombreux, seuls les plus courants seront décrits ici.
- JLabel
- Ce composant permet d'afficher du texte et une icône.
- JButton
- Ce composant (bouton) permet de déclencher une action par un clic de souris.
- JToggleButton
- Bouton basculant entre deux états.
- JTextfield
- Un champ de saisie de texte simple.
- JPasswordField
- Un champ de saisie de mot de passe dont les caractères sont cachés.
- JTextArea
- Une zone de saisie de texte.
- JTextPane
- Une zone d'affichage ou de saisie de texte formaté.
- JCheckBox
- Une case à cocher.
- JRadioButton
- Un item sélectionnable parmi un groupe.
- JComboBox
- Une liste déroulante de choix (un seul choix possible).
- JList
- Une liste d'items, un ou plusieurs items sélectionnable.
- JSpinner
- Champ de saisie avec boutons d'incrémentation et de décrémentation (nombre, items dans une liste prédéfinie, ...).
- JSlider
- Sélection d'une valeur numérique par glissement d'un curseur sur une ligne.
- JProgressBar
- Barre de progression.
- JTree
- Arborescence de nœuds pour les données structurées.
- JTable
- Table de données.
- JScrollbar
- Une barre de défilement.
- JPanel
- Un conteneur générique.
- JScrollPane
- Un conteneur permettant de faire défiler le contenu du composant (la vue) lorsque sa taille dépasse les limites du conteneur.
- JSplitPane
- Un conteneur avec deux composants séparés par un mince composant diviseur dont la position est réglable à la souris. Ce diviseur contient par défaut deux boutons permettant de cacher l'un des deux composants pour afficher l'autre sur toute la surface du conteneur.
Architecture logicielle
modifierLes composants Swing utilisent l'architecture MVC (modèle vue contrôleur) :
- La vue (view en anglais) contient la présentation de l'interface graphique.
- La vue est gérée par le composant Swing, par exemple : JTable, JTree...
- Le modèle (model en anglais) contient et gère les données à afficher.
- Le modèle est associé au composant : JTableModel, JTreeModel...
- Le contrôleur (controller en anglais) contient la logique concernant les actions effectuées par l'utilisateur.
- Le rôle du contrôleur est réparti entre le code du composant définissant un comportement interne et les écouteurs d'évènements définis par l'application.
Taille et position
modifierEn général et par défaut, la taille et la position des composants dans un conteneur sont définies par un gestionnaire de disposition des composants.
Elles ne peuvent donc être modifiées qu'en cas d'absence de gestionnaire de disposition (setLayout(null)
).
Cependant, il est recommandé d'utiliser l'un des gestionnaires de disposition des composants pré-existants.
Position
modifierComme en AWT, la position d'un composant est définie par deux coordonnées x et y, qui peuvent être représentées par un objet de classe java.awt.Point
. Cette classe comporte deux champs publics x
et y
de type entier (int
).
void setLocation(int x, int y)
|
Cette méthode définit les coordonnées horizontales et verticales (respectivement) du coin supérieur gauche du composant. Les coordonnées sont relatives au conteneur ; c'est à dire que (0,0) correspond au bord supérieur gauche du conteneur, éventuellement espacé de la largeur du bord du conteneur. |
void setLocation(Point p)
|
Cette méthode définit les coordonnées en utilisant un objet de classe java.awt.Point .
|
Point getLocation()
|
Cette méthode retourne la position du composant. |
Taille
modifierLa taille d'un composant est définie par la largeur (width en anglais) et la hauteur (height en anglais), qui peuvent être représentées par un objet de classe java.awt.Dimension
. Cette classe comporte deux champs publics width
et height
de type entier (int
).
void setSize(int width, int height)
|
Cette méthode définit la taille du composant. |
void setLocation(Dimension size)
|
Cette méthode définit la taille en utilisant un objet de classe java.awt.Dimension .
|
Dimension getSize()
|
Cette méthode retourne la taille du composant. |
Utiliser un objet au lieu de spécifier directement les valeurs permet de réutiliser les mêmes valeurs pour d'autres composants, ou pour définir la taille minimales, maximales et préférées d'un composants, ou de réutiliser les valeurs retournées par un composant.
Taille et position
modifierLa taille et la position peuvent être définies en une fois en utilisant les méthodes ci-dessous, soit en spécifiant les 4 valeurs, soit en utilisant un objet de classe java.awt.Rectangle
. Cette classe comporte quatre champs publics de type entier (int
) : x
et y
pour la position, et width
et height
pour la taille.
void setBounds(int x, int y, int width, int height)
|
Cette méthode définit la taille et la position du composant. |
void setBounds(Rectangle bounds)
|
Cette méthode définit la taille et la position en utilisant un objet de classe java.awt.Rectangle .
|
Rectangle getBounds()
|
Cette méthode retourne la taille et la position du composant. |
Propriétés d'apparence
modifierCertaines propriétés communes aux composants changent leur apparence :
void setBackground(Color c)
/Color getBackground()
- Couleur de fond du composant.
void setForeground(Color c)
/Color getForeground()
- Couleur de premier plan, utilisée pour le texte.
void setFont(Font f)
/Font getFont()
- Police de caractères par défaut pour le texte.
void setText(String s)
/String getText()
- Texte affiché (label, bouton, champ de saisie, ...).
void setBorder(Border b)
/Border getBorder()
- Ajoute un espace autour du composant pour y dessiner une bordure. La classe
BorderFactory
définit des méthodes statiques permettant de créer des bordures. void setTooltipText(String s)
/String getTooltipText()
- Texte affiché temporairement au survol de la souris sur le composant.
Propriétés de comportement (état)
modifier- La méthode
void setVisible(boolean b)
permet de changer la visibilité du composant. La méthodeboolean isVisible()
retourne l'état de visibilité du composant. - La méthode
void setEnabled(boolean b)
permet de désactiver/griser (false
) ou (ré)activer (true
) un composant. La méthodeboolean isEnabled()
retourne l'état d'activation du composant. - La méthode
void setEditable(boolean b)
permet de désactiver (false
) ou (ré)activer (true
) la modification de la valeur dans un champ de saisie dérivant de la classeJTextComponent
(classesJTextArea
,JTextField
,JFormattedTextField
,JPasswordField
,JEditorPane
,JTextPane
, ...). La méthodeboolean isEditable()
retourne l'état d'activation de modification de la valeur.
Suite à la réactivation d'un champ texte, soit par la méthode void setEnabled(true)
, soit par la méthode void setEditable(true)
, il peut arriver que le curseur n'apparaisse pas, même quand le focus est sur le champ.
La solution, après réactivation, est de rendre visible le curseur explicitement :
textinput.getCaret().setVisible(true);
Composants
modifierPour tester rapidement les composants des sections qui suivent, voici un code source Java créant une fenêtre de test.
package org.wikibooks.fr.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
/**
* Fenêtre pour tester les composants.
* @author fr.wikibooks.org
*/
public class FenetreTestComposants extends JFrame
{
public FenetreTestComposants()
{
// Configurer la fenêtre
setTitle("Composants");
setSize(new Dimension(600,400)); // Taille initiale de la fenêtre
setLocation(new Point(200, 100)); // Position initiale de la fenêtre
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Création du panel pour le contenu de la fenêtre
JPanel contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
// Copier/Écrire ici le code pour créer le composant comme celui donné dans les sections suivantes.
// ...
// composant = new ... ( ... );
// ...
contentPane.add(composant); // remplacer "composant" par le nom de la variable utilisée.
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
FenetreTestComposants frame = new FenetreTestComposants(); // Créer
frame.setVisible(true); // Afficher
}
});
}
}
JLabel
modifierCe composant permet d'afficher du texte et une icône.
// Convention : l_ pour label
// Un label pour le nom de l'application :
JLabel l_nom_app = new JLabel("Apprentissage Java Swing sur fr.wikibooks.org");
// Les lignes suivantes sont optionnelles mais permettent de configurer le composant
l_nom_app .setFont(new Font("Tahoma", Font.BOLD, 36)); // Police "Tahoma", en gras, 36 pixels
Dans la fenêtre de test, une largeur de 600 pixels ne suffit pas à afficher tout le texte. Le composant coupe le texte avant 600 pixels et affiche des points de suspension.
Pour afficher un texte sur plusieurs lignes, l'utilisation de caractère de retour à la ligne \n
ou \r
n'ont aucun effet.
Il faut utiliser un format HTML :
// Convention : l_ pour label
// Un label pour le nom de l'application :
JLabel l_nom_app = new JLabel("<html>Apprentissage Java Swing<br>sur fr.wikibooks.org</html>");
// Les lignes suivantes sont optionnelles mais permettent de configurer le composant
l_nom_app .setFont(new Font("Tahoma", Font.BOLD, 36)); // Police "Tahoma", en gras, 36 pixels
Le constructeur a trois paramètres optionnels :
JLabel(String text, Icon icon, int horizontalAlignment)
.
text
- Texte affiché, aucun si absent.
icon
- Image affichée, aucune si absent. Si le texte est aussi spécifié, l'image est à gauche du texte par défaut.
- La classe
ImageIcon
est une sous-classeIcon
et définit l'icône à partir d'une image de classeImage
. horizontalAlignment
- Alignement du contenu :
SwingConstants.LEFT // À gauche
SwingConstants.CENTER // Centré
SwingConstants.RIGHT // À droite
SwingConstants.LEADING // En début de ligne (gauche / droite)
SwingConstants.TRAILING // En fin de ligne (droite / gauche)
Quand un texte et une image sont affichés, l'image est à gauche du texte par défaut.
La méthode setHorizontalTextPosition(int textPosition)
permet de choisir la position du texte relativement à l'image :
SwingConstants.LEFT // À gauche
SwingConstants.CENTER // Centré
SwingConstants.RIGHT // À droite
SwingConstants.LEADING // Du côté du début de ligne (gauche / droite)
SwingConstants.TRAILING // Du côté de la fin de ligne (droite / gauche)
Label activant un composant
modifierUn label peut être utilisé pour activer le composant associé. Par exemple, mettre le focus sur un champ de saisie quand l'utilisateur tape la touche mnémonique associée au label.
setDisplayedMnemonic(int key)
- Spécifier un code de touche mnémonique à afficher.
setLabelFor(Component c)
- Spécifier le composant associé au label.
JButton
modifierCe composant permet de déclencher une action par un clic de souris.
// Convention : b_ pour button
JButton b_configurer = new JButton("Configurer...");
b_configurer.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{ configurer(); }
});
// N.B. : Définir la méthode configurer() appelée par le bouton dans la classe
JTextfield
modifierUn champ de saisie de texte simple.
La classe JTextfield
hérite de JTextComponent
définissant les méthodes pour les champs de saisie.
// Convention : tf_ pour text field
JTextField tf_titre_livre = new JTextField();
// Ajouter une bulle d'information pour l'utilisateur s'affichant au survol de la souris :
tf_titre_livre.setToolTipText("Titre d'un livre");
String livre = "Programmation Java Swing";
tf_titre_livre.setText(livre); // Initialiser la valeur dans le champ de saisie
// Plus tard, lors d'une action :
livre = tf_titre_livre.getText(); // Récupérer la valeur du champ saisie par l'utilisateur
JPasswordField
modifierUn champ permettant la saisie d'un mot de passe, cachant les caractères tapés.
La classe JPasswordField
hérite de JTextComponent
définissant les méthodes pour les champs de saisie.
Cette classe possède donc les mêmes méthodes que JTextfield
.
// Convention : pf_ pour password field
JPasswordField pf_connexion = new JPasswordField(); // Mot de passe de connexion
// Ajouter une bulle d'information pour l'utilisateur s'affichant au survol de la souris :
pf_connexion.setToolTipText("Entrez le mot de passe de connexion");
pf_connexion.setText(""); // Initialiser la valeur dans le champ de saisie
// Plus tard, lors d'une action :
// String mot = pf_connexion.getText(); // Récupérer la valeur du champ saisie par l'utilisateur
// Obsolète, ne pas utiliser dans les applications sécurisées, voir ci-dessous.
char[] mot = pf_connexion.getPassword(); // Récupérer la valeur du champ saisie par l'utilisateur
// Comme mentionné dans la documentation de la méthode getPassword(),
// pour plus de sécurité, il est recommandé d'effacer le contenu
// du tableau retourné après usage en le remplissant de zéro.
Arrays.fill(mot, '\0');
// Ceci n'est pas possible avec le type String immuable, expliquant pourquoi
// il ne faut pas utiliser la méthode getText().
JTextArea
modifierUne zone de saisie de texte.
JTextPane
modifierUne zone d'affichage ou de saisie de texte formaté.
JCheckBox
modifierUne case à cocher.
// Convention : cb_ pour check box
cb_resume = new JCheckBox("Avec résumé");
// Ajouter une bulle d'information pour l'utilisateur s'affichant au survol de la souris :
cb_resume.setToolTipText("Générer un résumé du livre");
cb_resume.setSelected(true); // Initialiser la case : cochée [X]
cb_resume.setSelected(false); // ou non cochée [ ]
// Plus tard, lors d'une action :
boolean avec_resume = cb_resume.isSelected(); // Récupérer l'état choisi par l'utilisateur
Ce composant gère deux états, mais pas d'état intermédiaire (coché partiellement ou indéterminé). Mais il est possible de créer un composant ayant ce troisième état : voir le chapitre avancé « Créer une case à cocher à trois états ».
JRadioButton
modifierUn bouton radio, permettant de sélectionner un item parmi plusieurs dans un groupe.
La classe JRadioButton
possède plusieurs constructeurs qui permettent d'initialiser le texte affiché, l'image d'icône, et l'état initial (booléen, sélectionné ou non).
new JRadioButton(String text, Icon icon, boolean selected)
- Tous les paramètres sont optionnels.
new JRadioButton(Action action)
- Paramétrage avec une action.
La classe ButtonGroup
n'est pas un composant mais représente un groupe de boutons abstraits (JRadioButton, JToggleButton, ...) dont seul l'un d'entre eux peut être sélectionné à la fois.
La sélection de l'un d'eux entraîne la dé-sélection des autres.
Exemple :
// Convention : bg_ pour button group
ButtonGroup bg_theme = new ButtonGroup();
// Convention : rb_ pour radio button
JRadioButton rb_theme_clair = new JRadioButton("Thème clair");
bg_theme.add(rb_theme_clair); // Ajout au groupe
JRadioButton rb_theme_sombre = new JRadioButton("Thème sombre");
bg_theme.add(rb_theme_sombre); // Ajout au groupe
JRadioButton rb_theme_sepia = new JRadioButton("Thème sépia");
bg_theme.add(rb_theme_sepia); // Ajout au groupe
Gestion de sélection :
bg_theme.clearSelection(); // Tous dé-sélectionnés
// Sélectionner un bouton radio :
rb_theme_clair.setSelected(true);
// Connaître l'état de sélection :
boolean avec_theme_clair = rb_theme_clair.isSelected();
JComboBox
modifierUne liste déroulante de choix (un seul choix possible).
JList
modifierUne liste d'items, un ou plusieurs items sélectionnable.
Le constructeur accepte un tableau d'objets (leur méthode toString()
est appelé pour le texte à afficher) ou un modèle de liste JListModel
.
L'utilisation d'un modèle de liste est recommandée quand la liste des items est modifiable par l'utilisateur.
final Object[] JOURS = { "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche" };
// Indice : 0 1 2 3 4 5 6
// Convention : ls_ pour list
JList ls_jours = new JList(JOURS);
Ce composant a plusieurs modes de sélection :
- un seul item sélectionnable :
// Mode de sélection : un seul ls_jours.setselectionMode(ListSelectionModel.SINGLE_SELECTION); ls_jours.setSelectedIndex(2); // Mercredi // Plus tard, durant une action : int index_jour = ls_jours.getSelectedIndex();
- un seul intervalle continu sélectionnable :
// Mode de sélection : un seul intervalle ls_jours.setselectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); ls_jours.setSelectionInterval(2, 4); // Du mercredi au vendredi inclus // Plus tard, durant une action : int[] indexs_jour = ls_jours.getSelectedIndices();
- plusieurs intervalles sélectionnables :
// Mode de sélection : plusieurs intervalles ls_jours.setselectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // Soit tous les indices : ls_jours.setSelectedIndices(new int[]{0,1,2, 4,5}); // Lundi, mardi, mercredi, vendredi, samedi // Soit les intervalles : ls_jours.setSelectionInterval(0, 2); // Du lundi au mercredi inclus ls_jours.addSelectionInterval(4, 5); // et ajouter du vendredi au samedi inclus // Plus tard, durant une action : int[] indexs_jour = ls_jours.getSelectedIndices();
La taille de la liste est déterminée par le nombre d'items. Ce composant ne gère pas le défilement. Il faut l'englober dans un composant JScrollPane pour gérer le défilement d'une longue liste dans un espace plus réduit.
JSpinner
modifierChamp de saisie avec boutons d'incrémentation et de décrémentation (nombre, items dans une liste prédéfinie, ...).
JSlider
modifierSélection d'une valeur numérique par glissement d'un curseur sur une ligne.
JProgressBar
modifierBarre de progression.
JTree
modifierArborescence de nœuds pour les données structurées.
JTable
modifierTable de données.
JScrollbar
modifierUne barre de défilement.
Ce composant est rarement utilisé directement car il est de bas niveau et est utilisé notamment par JScrollPane qui gère la vue partielle, le défilement et les entêtes.
JPanel
modifierUn conteneur générique, comme le contentPane
de la fenêtre de test.
Un panel, comme sa classe de base java.awt.Container
, peut contenir tout type de composant, y compris d'autres panels.
L'imbrication de panels permet de subdiviser un panel en plusieurs panels indépendant et réutilisables ayant leur propre configuration (gestionnaire de disposition, couleur de fond, ...).
// Convention : p_ pour panel
JPanel p_outils = new JPanel(); // un panel pour les outils de l'application
// Configuration :
p_outils.setBorder(new EmptyBorder(5, 5, 5, 5)); // bordure vide de 5 pixels dans les 4 directions
p_outils.setLayout(new FlowLayout()); // gestionnaire de disposition
JScrollPane
modifierUn conteneur permettant de faire défiler le contenu du composant (la vue) lorsque sa taille dépasse les limites du conteneur. Ce type de panel est notamment utilisé avec les listes ayant plus de 3 ou 5 items, les tables, les arborescences, les panels ayant un contenu occupant une grande surface.
Ce composant comporte en fait neufs zones illustrées ci-dessous :
Coin
supérieur gauche |
Entête de colonne
|
Coin
supérieur droit |
Entête de ligne
|
Partie principale du composant |
Barre de défilement vertical
|
Coin
inférieur gauche |
Barre de défilement horizontal |
Coin
inférieur droit |
- Partie principale du composant
- Il s'agit en général du composant lui-même, dont la vue suit le défilement horizontal et vertical des barres situées en bas et à droite respectivement.
- Le composant affiché est celui passé au constructeur :
new JScrollPane(table_pages) // Pour la table des pages
- Il peut être spécifié ou modifié après construction en utilisant la méthode
setViewportView
:// Convention : sp_ pour scroll pane JScrollPane sp_pages = new JScrollPane(); sp_pages.setViewportView(table_pages); // configurer la vue (view) du JViewport
- Les trois zones de vues avec défilement (principal, entête de colonne, entête de ligne) sont en fait encapsulées dans un
JViewport
qui gère l'affichage partiel. Il est possible de spécifier un autre type deJViewport
en utilisant la méthodesetViewport
.
- Entête de colonne
- Un entête toujours affiché en haut, mais pouvant être défilé horizontalement pour suivre le contenu principal.
- Il peut s'agir d'un composant quelconque. Cependant, pour les tables, il s'agit souvent de l'entête des colonnes de la table que l'on obtient par appel de la méthode
getTableHeader
:sp_pages.setColumnHeaderView(table_pages.getTableHeader()); // configurer l'entête de colonne
- Les trois zones de vues avec défilement (principal, entête de colonne, entête de ligne) sont en fait encapsulées dans un
JViewport
qui gère l'affichage partiel. Il est possible de spécifier un autre type deJViewport
en utilisant la méthodesetColumnHeader
.
- Entête de ligne
- Un entête toujours affiché à gauche, mais pouvant être défilé verticalement pour suivre le contenu principal.
- Il s'agit d'un composant quelconque :
sp_pages.setRowHeaderView(p_row); // configurer l'entête de ligne
- Les trois zones de vues avec défilement (principal, entête de colonne, entête de ligne) sont en fait encapsulées dans un
JViewport
qui gère l'affichage partiel. Il est possible de spécifier un autre type deJViewport
en utilisant la méthodesetRowHeader
.
- Barres de défilement
- Les barres de défilement sont affichées quand nécessaire par défaut. Ce comportement peut être modifié soit en spécifiant le mode pour les barres verticales et horizontales au constructeur, ou en appelant les méthodes
setVerticalScrollBarPolicy( ... )
etsetHorizontalScrollBarPolicy( ... )
:
setVerticalScrollBarPolicy( ... )
setHorizontalScrollBarPolicy( ... )
Quand afficher la barre de défilement vertical / horizontal : ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
Quand nécessaire (contenu plus large que la zone de vue). ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
Jamais (vue tronquée et non défilable). ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS
Toujours.
- Coins
- Il peut y avoir jusqu'à quatre coins affichés, qui sont par défaut vides. Il est possible d'y placer des composants de petite taille, en général décoratifs seulement, afin de combler le vide.
setCorner(String key, Component component)
- key
- Nom du coin où placer le composant. Ce nom doit être l'un de ceux listés ci-dessous :
- Coin absolu :
ScrollPaneConstants.UPPER_LEFT_CORNER // supérieur gauche
ScrollPaneConstants.UPPER_RIGHT_CORNER // supérieur droit
ScrollPaneConstants.LOWER_LEFT_CORNER // inférieur gauche
ScrollPaneConstants.LOWER_RIGHT_CORNER // inférieur
- Coin relatif au sens de lecture :
ScrollPaneConstants.UPPER_LEADING_CORNER // supérieur début de ligne (gauche/droit)
ScrollPaneConstants.UPPER_TRAILING_CORNER // supérieur fin de ligne (droit/gauche)
ScrollPaneConstants.LOWER_LEADING_CORNER // inférieur début de ligne (gauche/droit)
ScrollPaneConstants.LOWER_TRAILING_CORNER // inférieur fin de ligne (droit/gauche)
- Coin absolu :
- component
- Composant à placer.
Arbre
Swing possède une classe de composant graphique permettant l'affichage d'une arborescence de données, de manière similaire à un explorateur de répertoires, nommée JTree
.
Modèle
modifierLe modèle de l'arbre est géré par une classe implémentant l'interface javax.swing.tree.TreeModel
.
Cette interface permet d'obtenir l'arborescence des objets à afficher, la notification de modification des objets, et d'enregistrer un écouteur d'évènement de changement d'arborescence.
Elle est implémentée par la classe javax.swing.tree.DefaultTreeModel
gérant des nœuds implémentant l'interface javax.swing.tree.TreeNode
.
Cette interface est implémentée par la classe javax.swing.tree.DefaultMutableTreeNode
.
Liens
modifier- (anglais) How to Use Trees : http://docs.oracle.com/javase/tutorial/uiswing/components/tree.html
Fenêtres
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
modifierLes 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
modifierUne 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
modifierChaque 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
modifierLa 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
modifierLes 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
modifierLa 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
modifierLa 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
modifierLa 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
modifierL'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
modifierUne 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
modifierSwing 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
modifierLa 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
modifierCe 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 estnull
, 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
modifierIl 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
modifierCe 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
modifierCe 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
modifierLes 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
modifierLa 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
modifierLa 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é
modifierAvant 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
modifierIl 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
modifierLe 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
modifierLa 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
modifierLa 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é
Menus
Types de menu
modifierSwing supporte deux types de menu :
- les menus de type
javax.swing.JMenu
attaché à une fenêtre via sa barre de menujavax.swing.JMenuBar
généralement située en haut de la fenêtre. - les menus de type
javax.swing.JPopupMenu
attaché à tout composant, dont l'affichage est, par exemple, déclenché par un clic droit de la souris.
Création d'un menu
modifierCes deux types de menu (javax.swing.JMenu
et javax.swing.JPopupMenu
) s'utilisent de la même façon.
Un menu est initialement vide, et l'ajout d'un item se fait en appelant l'une des méthodes suivantes :
add(JMenuItem item)
- Ajouter un item de menu.
add(Action action)
- Ajouter un item de menu créé à partir d'une action.
La seconde méthode est préférable car une même action peut être attachée à différents composants (menu, bouton, ...) ce qui simplifie la gestion de son état (état activé ou désactivé, texte, icône, raccourci clavier, ...) dont la modification de répercute sur tous les composants liés.
Il est possible d'ajouter d'autres éléments à un menu :
- La méthode
addSeparator()
permet d'ajouter un séparateur entre deux items de menu. - La méthode
add(JMenuItem item)
accepte aussi un autre menu afin d'ajouter un sous-menu, car la classejavax.swing.JMenu
dérive dejavax.swing.JMenuItem
.
Exemple :
package org.wikibooks.fr.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Créer une fenêtre avec un menu.
* @author fr.wikibooks.org
*/
public class ExempleMenuDeFenetre extends JFrame
{
private void menuOuvrir()
{
// Mettre ici le code pour ouvrir un fichier
}
private void menuEnregistrer()
{
// Mettre ici le code pour sauvegarder un fichier
}
public ExempleMenuDeFenetre()
{
// Création de la fenêtre
setTitle("Démonstration de création de menu pour une fenêtre Swing");
Dimension d = new Dimension(500, 200);
setMinimumSize(d);
setSize(d);
// Création des actions
Action a_ouvrir = new AbstractAction("Ouvrir un fichier...")
{
@Override
public void actionPerformed(ActionEvent e)
{
menuOuvrir();
}
};
a_ouvrir.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); // Ctrl-O
Action a_enregistrer = new AbstractAction("Enregistrer le fichier...")
{
@Override
public void actionPerformed(ActionEvent e)
{
menuEnregistrer();
}
};
a_enregistrer.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); // Ctrl-S
// Création du menu
JMenuBar mb = new JMenuBar();
JMenu m = new JMenu("Fichier"); // Fichier :
mb.add(m);
m.add(new JMenuItem(a_ouvrir)); // - Ouvrir
m.addSeparator(); // ----------------
m.add(new JMenuItem(a_enregistrer)); // - Enregistrer
setJMenuBar(mb); // Attacher la barre de menu à la fenêtre
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
ExempleMenuDeFenetre frame = new ExempleMenuDeFenetre();
frame.setVisible(true);
}
});
}
}
Gestion d'un menu contextuel
modifierUn menu contextuel (classe javax.swing.JPopupMenu
) a la particularité de s'afficher à l'endroit où l'utilisateur a déclenché son affichage (Par exemple, clic droit avec la souris sous Windows ou Linux, ou la touche Menu).
Le déclenchement
modifierLe déclenchement de l'ouverture du menu dépend de la plateforme : il peut se faire lors de l'appui du bouton de souris, ou lors de son relâchement (comme sous Windows par exemple).
La classe MouseEvent
possède une méthode isPopupTrigger()
permettant de tester si l'évènement est celui qui déclenche l'ouverture du menu pour la plateforme.
L'affichage du menu contextuel doit se faire avec la méthode show(JComponent, int, int)
pour que le comportement du menu soit correct : mise en évidence de l'item survolé, gestion de la disparition du menu en cliquant ailleurs, ...
L'utilisation des méthodes setLocation
et setVisible
n'est pas recommandée car le menu peut ne pas fonctionner correctement.
public class UnComposant extends JComponent
{
// ...
private JPopupMenu menu_contextuel;
private boolean ouvrirMenuContextuel(MouseEvent e)
{
if (e.isPopupTrigger())
{
// L'ajustement du menu contextuel est possible ici si besoin, avant l'affichage.
menu_contextuel.show(this, e.getX(), e.getY());
return true; // menu contextuel ouvert
}
return false; // pas d'ouverture pour cet évènement
}
public UnComposant()
{
// ...
menu_contextuel = new JPopupMenu();
// ... ajouter des items au menu ici ...
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent e)
{
// si l'évènement n'est pas pour ouvrir le menu contextuel...
if (!ouvrirMenuContextuel(e))
{
// ... alors faire une autre action
}
}
@Override
public void mousePressed(MouseEvent e)
{
// si l'évènement n'est pas pour ouvrir le menu contextuel...
if (!ouvrirMenuContextuel(e))
{
// ... alors faire une autre action
}
}
});
}
}
Actions
Une action permet de préconfigurer plusieurs propriétés.
Quand un composant (JRadiobutton, JButton, JMenuItem, ...) est lié à une action, ses propriétés suivent l'évolution de celles de l'action.
Notamment, cela permet de désactiver/activer une action, de configurer un listener d'action (ActionListener
) une seule fois.
Cela est pratique quand une même action est disponible à la fois dans un menu et par un bouton, par exemple.
Exemple pour une action de menu, ou de bouton :
// Création d'une action
// Convention : a_ pour action
Action a_ouvrir = new AbstractAction("Ouvrir un fichier...")
{
@Override
public void actionPerformed(ActionEvent e)
{
menuOuvrir();
}
};
a_ouvrir.setAccelerator(KeyStroke.getKeyStroke(
KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); // Ctrl-O
// Action ajoutée au menu fichier :
JMenu m = new JMenu("Fichier"); // Fichier :
m.add(new JMenuItem(a_ouvrir)); // - Ouvrir
// Et aussi pour un bouton directement dans la fenêtre :
JButton b_ouvrir = new JButton(a_ouvrir);
a_ouvrir.setEnabled(false); // --> désactive à la fois le menu et le bouton en un appel
Types de données et constantes
Les interfaces graphiques en Swing utilisent les mêmes types de données qu'en AWT. Ces types sont utilisés abondamment dans l'API pour définir des informations graphiques :
- Couleur
- Dimensions
- Police de caractères
- Position
- Orientation
- Curseur de souris
Couleurs
modifierUn objet de classe java.awt.Color
représente une couleur au format RVBA (Rouge, Vert, Bleu et Alpha) à quatre composantes dont la valeur est entre 0 et 255.
La composante alpha définit l'opacité de la couleur : 0 = transparente, 255 = opaque (valeur par défaut).
Le constructeur de la classe java.awt.Color
permet de spécifier la couleur avec les trois composantes et éventuellement la valeur alpha pour la transparence, de type int
entre 0 et 255 ou de type float
entre 0.0 et 1.0.
Exemples :
public static final Color
C_BLEU_GRIS = new Color(150,150,180), // alpha=255 pour 100% d'opacité par défaut
C_BLEU_VERT = new Color(0,170,220, 192); // alpha=192 pour 75% d'opacité
La classe java.awt.Color
définit quelques couleurs utilisées fréquemment :
BLUE(0,0,255) | GREEN(0,255,0) | RED(255,0,0) | ORANGE(255,200,0) | |
YELLOW(255,255,0) | MAGENTA(255,0,255) | CYAN(0,255,255) | PINK(255,175,175) | |
BLACK(0,0,0) | DARK_GRAY(64,64,64) | GRAY(128,128,128) | LIGHT_GRAY(192,192,192) | WHITE(255,255,255) |
Police de caractères
modifierUn objet de classe java.awt.Font
représente la police de caractère utilisée pour dessiner le texte.
La classe possède deux constructeurs :
Font(String name, int style, int size)
- Les attributs sont définis par les arguments suivants :
- name : Nom de la police de caractères. Il peut aussi s'agir d'un nom symbolique défini par Java disponible sur toutes les plateformes : Dialog, DialogInput, Monospaced, ...
- style : Style de police, parmi les constantes définies par la classe : PLAIN, BOLD, ITALIC, BOLD_ITALIC.
- size : Taille de la police de caractères en points.
Font(Map<? extends Attributes, ?> attributes)
- Les attributs sont définis par un dictionnaire (Map) dont les clés doivent être celles définies par la classe
java.awt.font.TextAttribute
. Ce constructeur permet de définir davantage d'attributs.
La taille des polices de caractères en Java est en points pour 72 DPI (Dot Per Inch), quel que soit le DPI du système.
S'il est configuré convenablement au niveau système, le nombre de pixels par pouce (DPI) permet d'avoir un affichage de taille physique constant quel que soit le moyen d'affichage utilisé. Sachant qu'un point (pt) vaut 1/72 pouce (1/72 in), une police de caractère de 16pt devrait donc être affichée sur une hauteur de 21.333 pixels environ sur un écran à 96 DPI[1] :
Cependant, Java utilise 72 DPI, ce qui signifie que la taille donnée en point sera la même en pixels : 16pt affiché sur 16 pixels. Les polices de caractères apparaissent donc plus petites dans les applications Java par rapport aux autres applications (traitement de texte, logiciel de dessin, navigateur...) sur les écrans ayant un DPI supérieur et plus grandes sur les écrans avec un DPI inférieur à 72.
Pour obtenir un affichage identique aux autres applications, en ajoutant un paramètre dans l'application pour configurer le nombre de points par pouce, il faut ajuster la taille de la police.
Pour l'exemple d'une police de caractères de 16 points sur un écran de 96 DPI, il faut utiliser une taille de 21,333.
Cependant, le constructeur acceptant un nombre à virgule flottante pour la taille au lieu d'un entier est privé, mais la méthode deriveFont
autorise un nombre à virgule flottante pour la taille.
int dpi = 96;
static final int dpi_java = 72;
// La taille initiale (entier) passée au constructeur n'a pas d'importance.
Font f_16_points = new Font("Arial", Font.PLAIN, 16).deriveFont(16f*dpi/dpi_java);
Cependant, au lieu d'utiliser une valeur fixe, il est préférable d'utiliser la valeur retournée par la méthode getScreenResolution()
de la classe java.awt.Toolkit
:
int dpi = java.awt.Toolkit.getDefaultToolkit().getScreenResolution();
static final int dpi_java = 72;
Font f_16_points = new Font("Arial", Font.PLAIN, 16).deriveFont(16f*dpi/dpi_java);
Le nombre de points par pouce n'est toutefois pas toujours bien configuré.
La solution est de prévoir un paramètre de configuration pour changer la valeur de densité, en utilisant la valeur retournée par la méthode getScreenResolution()
comme valeur par défaut.
Dimensions
modifierUn objet de classe java.awt.Dimension
définit la taille d'un composant avec deux attributs publics, qui peuvent être initialisés en passant leur valeur en argument du constructeur :
int width
- Largeur en pixels (0 par défaut).
int height
- Hauteur en pixels (0 par défaut).
Position
modifierUn objet de classe java.awt.Point
définit la position d'un composant avec deux attributs publics, qui peuvent être initialisés en passant leur valeur en argument du constructeur :
int x
- Position horizontale en pixels (0 par défaut), relative au bord gauche du conteneur.
int y
- Position vertical en pixels (0 par défaut), relative au bord supérieur du conteneur.
Rectangle
modifierUn objet de classe java.awt.Rectangle
définit à la fois la la position d'un composant et sa taille avec quatre attributs publics, qui peuvent être initialisés en passant leur valeur en argument du constructeur :
int x
- Position horizontale en pixels (0 par défaut), relative au bord gauche du conteneur.
int y
- Position vertical en pixels (0 par défaut), relative au bord supérieur du conteneur.
int width
- Largeur en pixels (0 par défaut).
int height
- Hauteur en pixels (0 par défaut).
La classe Rectangle
a aussi des constructeurs acceptant une position (java.awt.Point
) ou une taille (java.awt.Dimension
) ou les deux.
Rectangle zone = new Rectangle(20, 10, 300, 200); // position x=20, y=10 ; taille width=300, height=200
Point position = new Point(20, 10);
Dimension taille = new Dimension(300, 200);
Rectangle zone = new Rectangle(position, taille);
Cette classe permet aussi quelques opérations :
Rectangle zone1 = new Rectangle(20, 10, 300, 200); // position x=20, y=10 ; taille width=300, height=200
Rectangle zone2 = new Rectangle(40, 50, 320, 250); // position x=40, y=50 ; taille width=320, height=250
Rectangle zone_englobante = zone1.union(zone2);
Rectangle zone_commune = zone1.intersection(zone2);
Constantes de position et orientation
modifierL'interface javax.swing.SwingConstants
est une collection de constantes relatives à la position et l'orientation.
Les tableaux ci-dessous montrent leur valeur et ce qu'elle représente.
Le centre a la valeur nulle, et les autres constantes de positionnement sont distribuées ensuite dans le sens trigonométrique (anti-horaire) en partant du haut.
TOP = 1 | ||
LEFT = 2 | CENTER = 0 | RIGHT = 4 |
BOTTOM = 3 |
Le centre a la valeur nulle, et les autres constantes de direction sont distribuées ensuite dans le sens horaire (anti-trigonométrique) en partant du haut.
NORTH_WEST = 8 | NORTH = 1 | NORTH_EAST = 2 |
WEST = 7 | CENTER = 0 | EAST = 3 |
SOUTH_WEST = 6 | SOUTH = 5 | SOUTH_EAST = 4 |
Les autres constantes définies sont les constantes d'orientation :
HORIZONTAL = 0
VERTICAL = 1
les constantes de position selon l'orientation de lecture de la langue courante :
LEADING = 10
avant dans le sens de lecture.
- Cela correspond à gauche (respectivement droite) si la langue courante se lit de gauche à droite (respectivement de droite à gauche),
TRAILING = 11
après dans le sens de lecture.
- Cela correspond à droite (respectivement gauche) si la langue courante se lit de gauche à droite (respectivement de droite à gauche).
et les constantes de direction dans une séquence :
NEXT = 12
PREVIOUS = 13
Curseur de souris
modifierUn objet de classe java.awt.Cursor
définit le curseur utilisé quand la souris survole un composant.
Le curseur par défaut est défini en fonction du composant : une flèche par défaut, une ligne verticale pour un champ textuel, ...
La méthode setCursor(Cursor c)
est définie pour toutes les classes de composants et fenêtres.
Afin de conserver une cohérence avec les autres applications, le changement de curseur n'est conseillé que pour certains cas exceptionnels et la création de nouveaux composants.
Plusieurs méthodes statiques permettent d'utiliser un curseur prédéfini :
Cursor.getPredefinedCursor(int type)
- Retourne un curseur typique (disponible sur toutes les plateformes Java) parmi ceux-ci :
Cursor.DEFAULT_CURSOR
Curseur par défaut (flèche),Cursor.CROSSHAIR_CURSOR
Curseur en croix (ligne verticale + ligne horizontale),Cursor.TEXT_CURSOR
Curseur pour les champs de texte,Cursor.WAIT_CURSOR
Curseur d'attente durant une action en cours (sablier, ...), peut être animé selon la plateforme,Cursor.
direction_RESIZE_CURSOR
Groupe de curseurs de la souris de type flèche directionnelle ou bidirectionnelle utilisé généralement pour le redimensionnement des fenêtres, quand la souris approche du bord : (N=North (nord), S=South (sud), E=East (est), W=West (ouest))SW_RESIZE_CURSOR, SE_RESIZE_CURSOR, NW_RESIZE_CURSOR, NE_RESIZE_CURSOR, S_RESIZE_CURSOR, N_RESIZE_CURSOR, W_RESIZE_CURSOR, E_RESIZE_CURSOR,
Cursor.MOVE_CURSOR
Curseur de type flèche dans les 4 directions cardinales, généralement utilisé lors du déplacement d'un élément,Cursor.HAND_CURSOR
Curseur de type main, généralement utilisé pour les actions ouvrant une fenêtre ou une page (lien) ;
Cursor.getDefaultCursor()
- Retourne le curseur par défaut (flèche) ;
- Équivaut à :
Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
Cursor.getSystemCustomCursor(String name)
- Retourne un curseur spécifique au système, à partir d'un nom (par exemple "Invalid.32x32").
- Ces curseurs sont définis dans le fichier de configuration
cursors.properties
du JRE (ou le sous-répertoire "jre" du JDK) localisé dans le répertoirelib/images/cursors
. Les fichiers images référencés sont situés dans le même répertoire. - Exemple pour JDK 1.8 sous Windows, extrait du fichier C:\Program Files\Java\jdk1.8.0_05\jre\lib\images\cursors\cursors.properties :
# Cursor.LinkNoDrop.32x32.File=win32_LinkNoDrop32x32.gif Cursor.LinkNoDrop.32x32.HotSpot=6,2 Cursor.LinkNoDrop.32x32.Name=LinkNoDrop32x32 # Cursor.Invalid.32x32.File=invalid32x32.gif Cursor.Invalid.32x32.HotSpot=6,2 Cursor.Invalid.32x32.Name=Invalid32x32
Il est également possible de créer un curseur personnalisé à partir d'une image et d'un point dans l'image. Ce point détermine quel pixel de l'image correspond à la position de la souris.
Toolkit toolkit = Toolkit.getDefaultToolkit(); Cursor c = toolkit.createCustomCursor(image, new Point(x,y), nom_du_curseur);
Notes de bas de page
modifier- ↑ Exemple d'un écran de 23 pouces en résolution 1980x1080 (diagonale approximative de 2229.12 pixels) : pixels par pouce.
Gestion de la disposition des composants
La disposition des composants n'est pas définie directement par le conteneur mais par le gestionnaire de disposition des composants (Layout manager en anglais).
Informations utilisées et contrainte associée
modifierLe gestionnaire peut utiliser différentes informations fournies par le composant :
Dimension getPreferredSize()
- La taille préférée du composant, qui dépend en général de son contenu et du moteur de rendu utilisé.
Dimension getMinimumSize()
- La taille minimale du composant. C'est la taille en dessous de laquelle le composant est jugé non utilisable (pas assez de place pour tout afficher, ...).
Dimension getMaximumSize()
- La taille maximale du composant.
Les gestionnaires de disposition ne respectent pas tous les trois contraintes précédentes définies par le composant.
Le code typique de création d'un conteneur avec gestionnaire de disposition est le suivant :
// Créer le conteneur et lui associer le gestionnaire de disposition Conteneur c = new Conteneur() c.setLayout(new GestionnaireDeDisposition(arguments...));
// Ajouter les composants avec une contrainte de disposition c.add(composant, contrainte);
Exemple :
JPanel p_main = new JPanel();
p_main.setLayout(new BorderLayout());
// Titre en haut (au nord)
JLabel label_titre = new JLabel("Titre ici");
p_main.add(label_titre, BorderLayout.NORTH);
// Panel de vue des données au centre
JPanel p_view = new JPanel();
p_main.add(p_view, BorderLayout.CENTER);
// Panel des boutons en bas (au sud)
JPanel p_boutons = new JPanel();
p_main.add(p_boutons, BorderLayout.SOUTH);
Gestionnaires de disposition prédéfinis
modifierLes interfaces java.awt.LayoutManager
et java.awt.LayoutManager2
sont définies par une variété de classes définissant différentes façon de disposer les composants :
- FlowLayout
- Les composants sont dimensionnés à leur taille préférée, et disposés en ligne, avec passage à la ligne suivante dès que la largeur restante ne suffit plus. Les lignes peuvent être justifiées à gauche, à droite ou centrées dans le contenant.
new FlowLayout() // composants alignés à gauche par défaut
new FlowLayout(FlowLayout.LEFT) // composants alignés à gauche
new FlowLayout(FlowLayout.RIGHT) // composants alignés à droite
new FlowLayout(FlowLayout.CENTER) // composants centrés
- L'alignement peut aussi prendre en compte le sens de lecture de gauche à droite ou de droite à gauche, leading pour le début (gauche / droite) et trailing pour la fin (droite / gauche) :
new FlowLayout(FlowLayout.LEADING) // composants alignés en début de ligne
new FlowLayout(FlowLayout.TRAILING) // composants alignés en fin de ligne
- Le constructeur peut prendre deux paramètres supplémentaires, espacement horizontal et vertical, pour spécifier l'espacement entre les composants et entre le conteneur et les composants :
new FlowLayout(FlowLayout.RIGHT, 10, 5) // composants alignés à droite espacés de 10 pixels en horizontal et 5 en vertical.
- BorderLayout
- Le conteneur est divisé verticalement en trois zones, de haut en bas : nord (BorderLayout.NORTH), centrale et sud (BorderLayout.SOUTH). La partie centrale est elle-même divisée horizontalement en trois zones, de gauche à droite : ouest (BorderLayout.WEST), centre (BorderLayout.CENTER) et est (BorderLayout.EAST).
- Le constructeur peut prendre deux paramètres, espacement horizontal et vertical, pour spécifier l'espacement entre les composants :
new BorderLayout() // composants non espacés
new BorderLayout(10, 5) // composants espacés de 10 pixels en horizontal et 5 en vertical.
- GridLayout
- Les composants sont dimensionnés selon la taille du conteneur divisée en colonnes et lignes dont le nombre et l'espacement est indiqué à l'appel du constructeur. Les composants sont positionnés dans l'ordre de leur ajout au conteneur, de gauche à droite en partant de la ligne supérieure.
- GridBagLayout
- Les composants sont dimensionnés à leur taille préférée si la contrainte associée le permet. La contrainte associée est de classe
GridBagConstraint
et définit la position dans la grille, le nombre de cellules occupées, et les contraintes de taille et d'alignement du composant dans la cellule.
- CardLayout
- Les composants sont dimensionnés à la taille du conteneur, et un seul des composants n'est visible à la fois. Cette disposition est utilisée pour implémenter les onglets.
- GroupLayout
- Les composants sont dimensionnés à leur taille préférée si possible, et groupés ensemble soit parallèlement, soit séquentiellement, en vertical et en horizontal. Cela signifie que les composants sont ajoutés deux fois par le gestionnaire. Ils ne sont pas ajoutés directement au conteneur.
- BoxLayout
- Les composants sont dimensionnés à leur taille préférée si possible, et alignés verticalement ou horizontalement. L'alignement selon l'autre axe est définie par l'alignement préférée du composant. Ce gestionnaire tient compte également de la taille minimale et maximale de chaque composant.
Le gestionnaire de disposition doit être assigné au conteneur soit en le passant au constructeur, soit en appelant la méthode setLayout
après construction du conteneur. Exemple:
JPanel p_main = new JPanel(new BorderLayout());
Pour BoxLayout, l'utilisation de la méthode setLayout
est obligatoire car le constructeur a besoin de l'instance du conteneur :
JPanel p_main = new JPanel();
p_main.setLayout(new BoxLayout(p_main, BoxLayout.Y_AXIS));
// L'appel à setLayout doit se faire avec le même conteneur que celui passé à BoxLayout
// sinon l'exception suivante est levée :
// java.awt.AWTError: BoxLayout can't be shared
Disposition par défaut des conteneurs
modifierUn conteneur possède un gestionnaire de disposition par défaut.
- JPanel
- FlowLayout est le gestionnaire de disposition par défaut.
- JTabbedPane
- CardLayout est le gestionnaire de disposition utilisé car ce conteneur est une vue à onglets. Les onglets sont visibles en haut du conteneur par défaut, et cliquable par l'utilisateur pour changer l'onglet visible.
Gestion des évènements
Un composant peut recevoir divers évènements issus de l'interaction avec l'utilisateur :
- Avec la souris : enfoncement et relâchement d'un bouton, déplacement de la souris, défilement de la molette.
- Avec le clavier : enfoncement et relâchement d'une touche, génération d'un caractère.
- Avec d'autres périphériques d'entrée.
Il peut aussi en générer :
- Un bouton peut notifier le déclenchement d'une action.
- La sélection d'un item dans une liste peut déclencher un évènement.
- ...
Principes de gestion des évènements
modifierDe manière générale, la gestion des évènements en Java se fait de la manière suivante :
- Les classes voulant recevoir une notification d'un évènement enregistrent un écouteur auprès de la source d'évènements. L'écouteur est un objet dont la classe implémente une interface particulière pour traiter un évènement comportant en général une seule méthode appelée pour notifier l'évènement qui s'est produit.
- La source d'évènement (un composant, un périphérique d'entrée : souris ou clavier, ...) notifie les écouteurs enregistrés en appelant la méthode de l'interface particulière. Cette méthode ne comporte en général qu'un seul argument dont la classe dérive de la classe
java.util.EventObject
.
L'exemple ci-dessous illustre comment recevoir un évènement d'action sur un bouton, qui se déclenche au clic avec la souris, ou au clavier quand le bouton a le focus.
JButton b_ok = new JButton("OK");
b_ok.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
// ... Action du bouton OK ici
System.out.println("Clic du bouton OK");
}
});
La convention de gestion des évènements est basée sur celle des JavaBeans. Pour un évènement de type type (MouseWheel, Key, Mouse, ...) :
- Une classe d'évènement
typeEvent
dérivant de la classejava.util.EventObject
contient toutes les informations sur l'évènement notifié. - Un écouteur doit implémenter l'interface
typeListener
dont la ou les méthode(s) ont comme seul argument un objet de classetypeEvent
. - La source d'évènement possède deux méthodes publiques :
addtypeListener
pour ajouter un écouteur de l'évènement.removetypeListener
pour retirer un écouteur.
- En général, en interne, la source d'évènement notifie les écouteurs enregistrés avec une méthode privée ou protégée nommée
firetypeEvent
.
Les différents types d'évènements
modifierIl existe un grand nombre de types d'évènements représentés par des classes dérivant de la classe java.util.EventObject
.
Cette section ne citera que les principaux types parmi ceux concernant les interfaces graphiques avec Swing.
La hiérarchie des types d'évènements contient un certain nombre de classes, chacune pouvant ajouter une ou plusieurs informations sur l'évènement.
Classe | Information(s) sur l'évènement définie(s)/ajoutée(s) par la classe | |||||
---|---|---|---|---|---|---|
java.util.EventObject
|
Source de l'évènement : public Object getSource();
| |||||
└─ | java.awt.AWTEvent
|
Identification de l'évènement (ID), consommation de l'évènement | ||||
├─ | java.awt.event.ActionEvent
|
Heure de l'évènement et modificateurs (boutons de souris, touches enfoncées : Shift, Alt, Ctrl...) | ||||
│ | (ID: ACTION_PERFORMED) | |||||
└─ | java.awt.event.ComponentEvent
|
Composant source de l'évènement (conversion de type) | ||||
└─ | java.awt.event.InputEvent
|
Heure de l'évènement et modificateurs (boutons de souris, touches enfoncées : Shift, Alt, Ctrl...) | ||||
├─ | java.awt.event.KeyEvent
|
Code de la touche, caractère généré | ||||
│ | (ID: KEY_TYPED, KEY_PRESSED, KEY_RELEASED) | |||||
└─ | java.awt.event.MouseEvent
|
Position de la souris, nombre de clics, indicateur de menu popup | ||||
│ | (ID: MOUSE_CLICKED, MOUSE_PRESSED, MOUSE_RELEASED, MOUSE_MOVED, MOUSE_ENTERED, MOUSE_EXITED, MOUSE_DRAGGED, MOUSE_WHEEL) | |||||
└─ | java.awt.event.MouseWheelEvent
|
Rotation de la molette de la souris (nombre, pixels de défilement) |
Les sous-classes de la classe java.awt.AWTEvent
ont un attribut (ID) qui identifie une notification particulière d'un évènement, chacune correspondant à une méthode de l'interface d'écoute de l'évènement.
Par exemple, pour les évènements provenant de la souris, les méthodes de l'interface java.awt.event.MouseListener
sont appelées avec un objet évènement de classe java.awt.event.MouseEvent
dont la valeur de l'identificateur (ID) correspond à celles-ci :
public void mouseReleased(MouseEvent e); // e.getID() == MouseEvent.MOUSE_RELEASED
public void mousePressed(MouseEvent e); // e.getID() == MouseEvent.MOUSE_PRESSED
public void mouseExited(MouseEvent e); // e.getID() == MouseEvent.MOUSE_EXITED
public void mouseEntered(MouseEvent e); // e.getID() == MouseEvent.MOUSE_ENTERED
public void mouseClicked(MouseEvent e); // e.getID() == MouseEvent.MOUSE_CLICKED
Même principe pour les méthodes de l'interface java.awt.event.MouseMotionListener
pour intercepter les évènements de mouvement de la souris :
public void mouseMoved(MouseEvent e); // e.getID() == MouseEvent.MOUSE_MOVED (Souris déplacée sans bouton enfoncé)
public void mouseDragged(MouseEvent e); // e.getID() == MouseEvent.MOUSE_DRAGGED (Souris déplacée avec bouton enfoncé)
Cela s'applique aussi aux interfaces qui n'ont qu'une seule méthode, comme l'interface java.awt.event.MouseWheelListener
pour la notification de rotation de molette de la souris :
public void mouseWheelMoved(MouseWheelEvent e); // e.getID() == MouseEvent.MOUSE_WHEEL
Cette redondance apparente d'identification du type d'évènement (par la méthode appelée et par la valeur de l'attribut ID) facilite la réutilisation d'une même méthode pour traiter plusieurs évènements de façon similaire.
Implémentation partielle
modifierLes interfaces d'écoute définissant au moins deux méthodes possèdent une classe dont le nom est celui de l'interface où l'on remplace le suffixe Listener
par Adapter
.
Cette classe définit des méthodes vides et permet aux applications de la surcharger pour ne définir que certaines méthodes de l'interface.
Exemple :
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
fermerFenetre();
}
});
Gestion des actions asynchrones
La création d'une interface graphique pour une application en Java entraîne la création de threads pour gérer les tâches de fond comme la gestion des évènements qui créer une boucle de réception d'évènements et leur distribution aux composants visibles.
Les actions déclenchées par l'utilisateur peuvent prendre beaucoup de temps : recherche de fichiers, calculs complexes, ... Lorsque de telles actions sont appelées directement depuis les écouteurs d'évènements, l'interface graphique est bloquée durant les secondes ou minutes d'exécution. Pour éviter ce problème, il faut gérer correctement les appels à ces actions de manière asynchrone.
Action asynchrone
modifierPour éviter qu'une action dont l'exécution est longue empêche l'utilisateur d'utiliser l'interface graphique, il faut créer un thread spécifique.
Exemple : Une action longue exécutée par clic sur un bouton :
JButton b_action = new JButton("Démarrer l'action")
b_action.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e) { demarrerAction(); }
});
La méthode demarrerAction()
crée et démarre un thread pour exécuter l'action et retourne sans attendre la fin d'exécution afin de ne pas bloquer l'interface graphique :
private void demarrerAction()
{
new Thread(run_action_longue, "longue-action").start();
}
// L'action du thread : appeler la méthode actionLongue()
Runnable run_action_longue = new Runnable()
{
@Override
public void run()
{ actionLongue(); }
};
private void actionLongue()
{
// Ici l'action longue...
// Pour tester, attendre 10 secondes :
try{ Thread.sleep(10000); }
catch(InterruptedException ex){ }
}
Mise à jour de l'interface
modifierUne action asynchrone peut avoir besoin de mettre à jour l'interface graphique : indiquer que l'action est terminée, afficher le fichier ou l'image chargée ou le résultat d'un calcul, ...
Pour cela, les méthodes des composants doivent être appelées par le thread de gestion des évènements afin d'éviter les conflits.
Il suffit pour cela d'appeler la méthode statique invokeLater
de la classe javax.swing.SwingUtilities
.
private void actionLongue()
{
// Ici l'action longue...
// resultat = ...
// Afficher le résultat :
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{ afficher(resultat); }
});
}
Apparence de l'interface
L'apparence et le comportement (look and feel en anglais) des composants Swing d'une application sont modifiables et configurables par le gestionnaire d'apparence et de comportement. L'apparence et le comportement varient plus largement pour les boîtes de dialogue standards telle celle de l'ouverture de fichiers où les composants utilisés diffèrent :
Apparence de l'application
modifierLes composants Swing ont une apparence et un comportement (look and feel en anglais) modifiables définis par le gestionnaire d'apparence et de comportement. L'apparence peut ensuite être modifiée pour chaque composant en appelant les méthodes communes :
setBackground(Color)
: Changer la couleur de fond.setForeground(Color)
: Changer la couleur de premier plan (texte, ligne en général).setFont(Font)
: Changer la police de caractères, si le composant affiche du texte.setBorder(Border)
: Changer la bordure autour du composant (aucune par défaut en général).
Les méthodes get
correspondantes existent également permettant d'obtenir la valeur courante.
Le gestionnaire d'apparence et de comportement par défaut est Metal, disponible quel que soit la plateforme.
La classe UIManager
permet de changer l'apparence et le comportement en appelant la méthode setLookAndFeel
et en lui passant le nom de la classe implémentant l'apparence et le comportement voulus.
Si l'application effectue un changement, il faut le faire avant la création des fenêtres concernées. Par exemple dans la méthode main :
protected static String laf_selected = null;
// Sélection par nom de classe
private static boolean selectClass(String classname)
{
try
{
UIManager.setLookAndFeel(classname);
laf_selected = classname;
return true;
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
// Sélection par nom d'affichage, recherche parmi les looks & feels disponibles.
public static boolean select(String name)
{
for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels() )
{
if (name.equalsIgnoreCase(laf.getName()))
return selectClass(laf.getClassName());
}
return false;
}
public static void main(String[] args)
{
// Choisir l'apparence avant de créer une fenêtre
select("Nimbus");
//selectClass(UIManager.getSystemLookAndFeelClassName()); // Apparence de l'OS
//selectClass(UIManager.getCrossPlatformLookAndFeelClassName()); // Apparence cross-plateforme (Metal)
EventQueue.invokeLater(new Runnable()
{
public void run()
{
DemonstrationApparence frame = new DemonstrationApparence();
frame.setVisible(true);
}
});
}
Si l'application effectue le changement après création des fenêtres, la mise à jour doit se faire explicitement en appelant la méthode statique SwingUtilities.updateComponentTreeUI
pour chaque fenêtre à mettre à jour en argument.
Aperçu
modifierLes copies d'écran ci-dessous montrent l'apparence de divers composants Swing selon l'apparence sélectionnée. Les apparences disponibles dépendent du système d'exploitation sous lequel tourne l'application Java. L'apparence peut modifier les couleurs, la police de caractère par défaut, la taille, les marges des différents composants. Par contre, l'apparence du bord de la fenêtre ne change pas.
Sous windows 7 :
-
Metal
-
Nimbus
-
CDE/Motif
-
Windows
-
Windows Classic
L'aspect de tous les composants changent, mais celui du bord de la fenêtre ne change pas par défaut. Le changement de l'aspect du bord de fenêtre n'est pas supporté par toutes les apparences. La seule apparence à le supporter est le look&feel Metal. Pour activer le changement d'apparence de décoration des fenêtres et boîtes de dialogue, il faut appeler ces méthodes (les deux en général) :
JDialog.setDefaultLookAndFeelDecorated(true);
JFrame.setDefaultLookAndFeelDecorated(true);
Ces deux méthodes doivent être appelées avant la création des fenêtres et boîtes de dialogue. On peut obtenir le même effet sur une fenêtre individuelle :
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
Pour une boîte de dialogue :
JDialog dialog = new JDialog();
dialog.setUndecorated(true);
dialog.getRootPane().setWindowDecorationStyle(JRootPane.PLAIN_DIALOG);
La personnalisation de l'apparence des bords de fenêtres est effectivement géré par JRootPane, dans une fenêtre sans décoration. Si le changement d'apparence de décoration des fenêtres et boîtes de dialogue est activé avec une apparence qui ne le supporte pas, la fenêtre n'a plus de bord.
L'apparence peut être configurée avant la création de fenêtre.
Par exemple, il est possible de désactiver les polices en gras de l'apparence Metal :
UIManager.put("swing.boldMetal", Boolean.FALSE);
Code source
modifierLe code source ci-dessous est celui de l'application ayant permis les captures d'écrans de ce chapitre.
package org.wikibooks.fr.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Démonstration du changement d'apparence.
* @author fr.wikibooks.org
*/
public class DemonstrationApparence extends JFrame
{
private String[] laf_classnames;
private String[] laf_names;
private int index_cross_platform = -1;
private int index_system = -1;
private int index_selected = -1;
private void changerApparence(int index)
{
if (index != index_selected)
{
index_selected = index;
if (selectClass(laf_classnames[index]))
SwingUtilities.updateComponentTreeUI(this);
else
JOptionPane.showMessageDialog(this,
"Impossible de changer l'apparence à "+laf_names[index],
"Erreur", JOptionPane.ERROR_MESSAGE);
}
}
public DemonstrationApparence()
{
// Lister les apparences disponibles
UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels();
laf_classnames = new String[lafs.length];
laf_names = new String[lafs.length];
String laf_system = UIManager.getSystemLookAndFeelClassName();
String laf_cross_platform = UIManager.getCrossPlatformLookAndFeelClassName();
index_selected = -1;
for(int i=0 ; i<lafs.length ; i++)
{
UIManager.LookAndFeelInfo laf = lafs[i];
String classname = laf.getClassName();
laf_classnames[i] = classname;
laf_names[i] = laf.getName();
if (laf_selected!=null && laf_selected.equals(classname))
index_selected = i;
if (laf_system.equals(classname))
index_system = i;
if (laf_cross_platform.equals(classname))
{
index_cross_platform = i;
if (laf_selected==null) index_selected = i;
}
}
// Création de la fenêtre
setTitle("Démonstration de l'apparence des composants Swing");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension d = new Dimension(450, 440);
setMinimumSize(d);
setSize(d);
Container c = getContentPane();
c.setLayout(new BorderLayout());
JPanel p_main = new JPanel();
p_main.setLayout(new BoxLayout(p_main, BoxLayout.Y_AXIS));
p_main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
c.add(p_main, BorderLayout.WEST);
JLabel l_apparence = new JLabel("Choisissez l'apparence :");
p_main.add(l_apparence);
// Un seul bouton radio appartenant au groupe ne pourra être sélectionné
ButtonGroup group = new ButtonGroup();
JRadioButton rb_sel = null;
for(int i=0 ; i<lafs.length ; i++)
{
String name = laf_names[i];
if (i==index_cross_platform) name = name+" (Cross-platform)";
if (i==index_system) name = name+" (System)";
JRadioButton rb = new JRadioButton(name);
p_main.add(rb);
group.add(rb);
if (i == index_selected) rb_sel = rb;
final int rb_index = i;
rb.addItemListener(new ItemListener()
{
@Override
public void itemStateChanged(ItemEvent e)
{
if (rb.isSelected())
changerApparence(rb_index);
}
});
}
if (rb_sel!=null) rb_sel.setSelected(true);
JPanel p_comp = new JPanel();
p_comp.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
p_comp.setLayout(new BoxLayout(p_comp, BoxLayout.Y_AXIS));
c.add(p_comp, BorderLayout.CENTER);
// Quelques composants pour montrer leur apparence
// espacés de 10 pixels verticalement
p_comp.add(new JCheckBox("Case à cocher"));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JSpinner(new SpinnerNumberModel(50, 0, 100, 1)));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JComboBox<String>(new String[]{"Choix 1", "Choix 2", "Choix 3"}));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JTextField("Champ de saisie"));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JScrollPane(new JTextArea("Zone de saisie\nSur plusieurs lignes\n1 2 3 4 5 6 7 8 9\n...")));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JButton("Un bouton"));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JToggleButton("Un bouton basculable"));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(new JScrollPane(new JTable(
new Object[][]
{
{ "pi", 3.14159, true },
{ "phi", 1.61803, false },
{ "e", 2.71828, true },
{ "square root of 2", 1.41421, false },
{ "square root of 3", 1.73205, false },
{ "square root of 5", 2.23607, false },
{ "square root of 7", 2.64575, false },
},
new Object[]{ "Nom", "Valeur", "Utiliser" })));
p_comp.add(Box.createVerticalStrut(10));
p_comp.add(Box.createVerticalGlue());
}
protected static String laf_selected = null;
private static boolean selectClass(String classname)
{
try
{
UIManager.setLookAndFeel(classname);
laf_selected = classname;
return true;
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
public static boolean select(String name)
{
for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels() )
{
if (name.equalsIgnoreCase(laf.getName()))
return selectClass(laf.getClassName());
}
return false;
}
public static void main(String[] args)
{
// Choisir l'apparence avant de créer une fenêtre
select("Nimbus");
//selectClass(UIManager.getSystemLookAndFeelClassName());
//selectClass(UIManager.getCrossPlatformLookAndFeelClassName());
EventQueue.invokeLater(new Runnable()
{
public void run()
{
DemonstrationApparence frame = new DemonstrationApparence();
frame.setVisible(true);
}
});
}
}
Contenu en HTML
La plupart des composants Swing supporte le texte en HTML. Cela permet d'ajouter facilement du texte formaté pour améliorer la présentation de l'interface graphique.
Affichage en HTML
modifier- JLabel, JButton
- Pour un rendu HTML, le texte doit être encadré par
<html>
et</html>
. - Exemple :
JLabel l_title = new JLabel("<html>Le titre avec <b>une partie en gras</b>,"+ " et <span style=\"color:blue;\">une autre partie en bleu</span>.</html>");
- JTextPane
- En plus d'encadrer le texte par
<html>
et</html>
, il faut spécifier le type de contenu, car ce composant supporte plusieurs types de document. - Exemple :
JTextPane tp_exemple = new JTextPane(); tp_exemple.setContentType("text/html"); tp_exemple.setText("<html><head><style>p { margin: 20px 0; }</style></head>"+ "<body><h1>Titre</h1><p>Le texte avec <b>une partie en gras</b>,"+ " et <span style=\"color:blue;\">une autre partie en bleu</span>.</p></body></html>");
- Ce composant mémorise la feuille de style définie par l'entête (head) du source HTML.
- Il faut forcer le changement de type pour la supprimer comme montré ci-dessous :
// Force la suppression de la feuille de style mémorisée : tp_exemple.setContentType("text/plain"); // Utiliser les propriétés du composant comme style par défaut (couleurs, police de caractères) : tp_exemple.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true); tp_exemple.setContentType("text/html"); tp_exemple.setText("<html><h1>Autre titre</h1><p>Autre texte.</p></html>");
Limitations
modifierSwing supporte HTML 4 et CSS 1.0 de manière limitée.
Limitations HTML
modifierÉlément(s) | Supporté(s) |
---|---|
<p> <span> <div> | Oui |
<b> <i> <u> <sup> <sub> | Oui |
<strong> <em> | Oui |
<br> <hr> | Oui |
<ins> <del> | Oui, mais rendu spécifique |
<pre> | Non |
Attribut(s) | Supporté(s) |
---|---|
href src | Oui |
class style | Oui |
Limitations CSS
modifierAttribut(s) | Supporté(s) |
---|---|
color background | Oui |
font font-family | Oui |
border border-spacing | Oui |
border-top border-bottom border-left border-right border-collapse | Non |
margin padding | Oui |
margin-top margin-bottom margin-left margin-right | Non |
padding-top padding-bottom padding-left padding-right | Non |
Tester l'affichage en HTML
modifierL'application dont le code source est ci-dessous permet de tester l'affichage en HTML avec un composant JTextPane.
Aperçu
modifierCode source
modifierpackage org.wikibooks.fr.swing.html;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
/**
* Fenêtre pour tester le rendu HTML en Swing avec un JTextPane.
* @author fr.wikibooks.org
*/
public class FenetreRenduHtml extends JFrame
{
private JPanel p_content;
private JTextArea ta_css;
private JTextArea ta_html_body;
private JTextPane tp_html;
private void renderHtml(String html)
{
tp_html.setContentType("text/plain");
tp_html.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true);
tp_html.setContentType("text/html");
tp_html.setText("<html>"+html+"</html>");
}
private void renderHtml(String html_body, String style)
{
if (style==null) renderHtml(html_body);
else renderHtml("<head><style>\n"+style+"\n</style></head><body>"+html_body+"</body>");
}
private void doHtmlRender()
{
String
t_html_body = ta_html_body.getText(),
t_css = ta_css.getText();
renderHtml(t_html_body, t_css);
}
public FenetreRenduHtml()
{
setTitle("Test d'affichage en HTML");
Dimension d = new Dimension(800, 600);
setSize(d);
setMinimumSize(d);
setLocation(new Point(200, 100));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
p_content = new JPanel();
p_content.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(p_content);
GridBagLayout gbl = new GridBagLayout();
gbl.columnWidths = new int[]{0, 0, 0};
gbl.rowHeights = new int[]{0, 0, 0, 0, 0, 0};
gbl.columnWeights = new double[]{1.0, 1.0, Double.MIN_VALUE};
gbl.rowWeights = new double[]{0.0, 1.0, 0.0, 1.0, 0.0, Double.MIN_VALUE};
p_content.setLayout(gbl);
Font f_mono = new Font("monospaced", Font.PLAIN, 14);
Border text_border = BorderFactory.createEmptyBorder(4, 4, 4, 4);
JLabel l_css = new JLabel("Contenu CSS");
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 5);
gbc.gridx = 0;
gbc.gridy = 0;
p_content.add(l_css, gbc);
}
JScrollPane sp_css = new JScrollPane();
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 0, 5);
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 0;
gbc.gridy = 1;
p_content.add(sp_css, gbc);
}
ta_css = new JTextArea();
ta_css.setFont(f_mono);
ta_css.setBorder(text_border);
ta_css.setText("/* Style CSS ici */");
sp_css.setViewportView(ta_css);
JLabel l_html = new JLabel("HTML source");
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 5);
gbc.gridx = 0;
gbc.gridy = 2;
p_content.add(l_html, gbc);
}
JScrollPane sp_html_body = new JScrollPane();
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 0, 5);
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 0;
gbc.gridy = 3;
p_content.add(sp_html_body, gbc);
}
ta_html_body = new JTextArea();
ta_html_body.setFont(f_mono);
ta_html_body.setBorder(text_border);
sp_html_body.setViewportView(ta_html_body);
JButton b_disp_html = new JButton("Afficher \u2192"); // "Afficher ->"
b_disp_html.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{ doHtmlRender(); }
});
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 5);
gbc.gridx = 0;
gbc.gridy = 4;
p_content.add(b_disp_html, gbc);
}
JLabel l_disp_html = new JLabel("HTML affiché");
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 5, 0);
gbc.gridx = 1;
gbc.gridy = 0;
p_content.add(l_disp_html, gbc);
}
JScrollPane sp_html = new JScrollPane();
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 1;
gbc.gridy = 1;
gbc.gridheight = 3;
p_content.add(sp_html, gbc);
}
tp_html = new JTextPane();
sp_html.setViewportView(tp_html);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
FenetreRenduHtml frame = new FenetreRenduHtml();
frame.setVisible(true);
}
});
}
}
Images
modifierLe contenu HTML peut faire référence à des images. Par défaut, les chemins relatifs ne sont pas supportés, seul un chemin absolu permet l'affichage de l'image. Les URLs distantes sont également supportées pour les protocoles supportés par Java (HTTP, HTTPS).
Les chemins et URL relatifs sont supportés quand une URL de base est fournie à la méthode setPage(url)
(url de type java.lang.String
ou java.net.URL
) qui charge la page correspondante.
Cette méthode permet le chargement de pages HTML simples (HTML 4 et CSS 1), sans Javascript.
Elle ne peut donc pas servir à afficher un site web.
Créer un composant
La bibliothèque Swing propose une riche collection de composants répondant aux besoins les plus courants concernant la création d'une interface graphique. Cependant, certaines applications peuvent avoir besoin de composants spécifiques, qu'il soit basé sur un composant existant (comme une case à cocher à trois états par exemple), ou qu'il ne soit basé sur aucun composant existant. C'est par exemple le cas d'un feu tricolore utilisé dans un jeu sur la circulation routière, qui sera développé dans ce chapitre par étapes.
Création de la classe
modifierLa première version de la classe ci-dessous définit les couleurs utilisées et l'état courant du feu tricolore. La méthode statique principale crée une fenêtre pour tester le composant. Le composant n'est pas visible pour le moment avec cette première version du code.
package org.wikibooks.fr.swing.component;
import java.awt.*;
import javax.swing.*;
/**
* Composant de feu tricolore.
* @author fr.wikibooks.org
*/
public class FeuTricolore extends JComponent
{
// Couleurs du feu
private static final Color// R V B
FEU_ETEINT = new Color( 32, 32, 32),
FEU_VERT = new Color( 0, 180, 0),
FEU_ORANGE = new Color(255, 160, 0),
FEU_ROUGE = new Color(255, 0, 0);
static enum EtatFeu
{
ETEINT,
VERT,
ORANGE,
ROUGE
};
private EtatFeu etat_courant = EtatFeu.ETEINT;
public FeuTricolore()
{
Dimension d = new Dimension(40,120);
setMinimumSize(d);
setPreferredSize(d);
setBackground(Color.BLACK);
setForeground(Color.DARK_GRAY);
}
// ...
// Méthode main utilisée pour tester le composant dans une fenêtre
public static void main(String[] args)
{
// Créer la fenêtre
JFrame f = new JFrame("Feu tricolore");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Personaliser le panneau du contenu
JPanel p_content = new JPanel();
p_content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
f.setContentPane(p_content);
// Ajouter un feu tricolore
p_content.add(new FeuTricolore());
f.pack();
f.setVisible(true);
}
}
État du composant
modifierL'état du composant est représenté par une énumération de type EtatFeu
static enum EtatFeu
{
ETEINT,
VERT,
ORANGE,
ROUGE
};
private EtatFeu etat_courant = EtatFeu.ETEINT;
On ajoute également des accesseurs permettant de lire et écrire l'état courant du feu :
/**
* Obtenir l'état courant du feu.
* @return L'état courant du feu.
*/
public EtatFeu getEtatCourant()
{
return etat_courant;
}
/**
* Définir l'état courant du feu.
* @param etat_courant Le nouvel état courant du feu.
*/
public void setEtatCourant(EtatFeu etat_courant)
{
this.etat_courant = etat_courant;
repaint();
}
La méthode de changement d'état appelle la méthode repaint()
pour mettre à jour l'affichage aussitôt.
Dessin du composant
modifierLe dessin d'un composant Swing est effectué par la méthode publique void paint(Graphics g)
.
Cette méthode délègue le travail à trois autres méthodes protégées appelées dans l'ordre suivant :
void paintComponent(Graphics g)
- Affiche le contenu du composant lui-même.
void paintBorder(Graphics g)
- Affiche les bords du composant dans la zone définie par les marges de type
Insets
. void paintChildren(Graphics g)
- Affiche les composants contenus dans celui-ci, par appel à leur méthode
paint
.
Cet ordre assure que les composants contenus soit affichés au-dessus du composant.
Pour définir l'apparence du composant, une sous-classe ne doit redéfinir que la méthode void paintComponent(Graphics g)
.
Celle-ci doit éviter de dessiner dans la zone définie par les marges de type Insets
qui est réservée aux bords (Border
).
Il existe aussi une méthode void paintComponents(Graphics g)
permettant de redessiner les composants contenus, au lieu du composant. Si le composant n'affiche rien, vérifiez qu'il n'y a pas de 's' à la fin du nom de la méthode. Cette erreur peut arriver facilement avec un IDE proposant la complétion automatique.
Le code ci-dessous ajoute la méthode de dessin du feu tricolore. Elle utilise un tableau à deux dimensions définissant la couleur de chaque feu pour chaque état possible.
// Couleur des feux selon l'état courant
private Color[][] COULEUR_FEUX = // [état][feu]
{
{ FEU_ETEINT, FEU_ETEINT, FEU_ETEINT }, // ETEINT
{ FEU_ETEINT, FEU_ETEINT, FEU_VERT }, // VERT
{ FEU_ETEINT, FEU_ORANGE, FEU_ETEINT }, // ORANGE
{ FEU_ROUGE, FEU_ETEINT, FEU_ETEINT }, // ROUGE
};
@Override
protected void paintComponent(Graphics g)
{
Dimension d = getSize();
// Le fond
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
// Dimension d'un feu selon la taille du composant
int fw = d.width, fh = d.height/3;
// ...de forme carrée :
if (fw>fh) fw=fh; else fh = fw;
int dm = fw*3/4, // Diamètre : 75% de la taille disponible
ecart = fw-dm, // Décalage de 25% = 100%-75%
// Position centrée avec décalage
x = (d.width - fw + ecart)/2,
y = (d.height - 3*fh + ecart)/2;
// Les trois feux de haut en bas
Color[] couleurs = COULEUR_FEUX[etat_courant.ordinal()];
for(int i=0 ; i<3 ; i++,y+=fh)
{
g.setColor(couleurs[i]);
g.fillOval(x, y, dm, dm);
g.setColor(getForeground());
g.drawOval(x, y, dm-1, dm-1); // dm-1 pour ne pas déborder
}
}
Gestion des évènements
modifierDans l'état actuel du code, le feu affiche l'état courant initial éteint. Cette section définit le code pour changer l'état courant sur réception d'un évènement de clic de souris.
On ajoute la méthode qui met l'état suivant selon l'état courant :
protected void cycleEtatCourant()
{
switch(etat_courant)
{
case ETEINT: setEtatCourant(EtatFeu.ROUGE); break;
case VERT: setEtatCourant(EtatFeu.ORANGE); break;
case ORANGE: setEtatCourant(EtatFeu.ROUGE); break;
case ROUGE: setEtatCourant(EtatFeu.VERT); break;
}
}
Cette méthode est appelée par le gestionnaire de clic de souris ajouté au composant dans le constructeur de la classe :
addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
cycleEtatCourant();
}
});
Améliorations
modifierLa classe peut être améliorée sur divers points abordés dans cette section.
Changement d'état
modifierAfin que le feu ait un comportement plus proche de la réalité, le changement d'état du feu devrait se faire sans intervention de l'utilisateur, au bout d'un certain temps dépendant de l'état courant.
Tout d'abord, afin d'éviter de réafficher le composant trop souvent, la méthode de changement d'état est modifiée comme ci-dessous :
public void setEtatCourant(EtatFeu etat_courant)
{
// Seulement si changement d'état
if (etat_courant != this.etat_courant)
{
this.etat_courant = etat_courant;
repaint();
}
}
On supprime donc le changement d'état par clic de souris, c'est à dire le code ajouté dans la section « Gestion des évènements » :
- Supprimer la méthode
cycleEtatCourant()
, - Et supprimer l'appel à la méthode
addMouseListener
dans le constructeur.
La gestion d'un croisement implique plusieurs feux, affectés à différentes voies de circulation (à double sens ou non). Il est donc préférable que le changement d'état d'un feu soit géré par une nouvelle classe afin de synchroniser les feux du carrefour. Il faut donc déplacer l'énumération dans une classe à part :
package org.wikibooks.fr.swing.component;
public enum EtatFeu
{
ETEINT,
VERT,
ORANGE,
ROUGE
}
Ensuite, il faut créer une nouvelle classe qui sera nommée GestionnaireFeu
, qui va gérer une liste de feux par voie de circulation, définir la voie de circulation active (cycle rouge-vert-orange-rouge) les autres étant inactives (feu rouge), et définir un thread mettant à jour le cycle courant :
package org.wikibooks.fr.swing.component;
import java.util.*;
/**
Gestionnaire de l'état des feux tricolores d'un carrefour.
<h3>Fonctionnement</h3>
Une seule voie est active à la fois, les autres
voies ont le feu au rouge.
La voie active effectue un cycle où le feu prend
successivement les couleurs suivantes :<ul>
<li>rouge pendant marge_duree_rouge cycles élémentaires,</li>
<li>vert pendant duree_vert cycles élémentaires,</li>
<li>orange pendant duree_orange cycles élémentaires,</li>
<li>rouge pendant marge_duree_rouge cycles élémentaires.</li>
</ul>
À la fin de ce cycle, la voie devient inactive,
et la voie suivante devient active.
* @author fr.wikibooks.org
*/
public class GestionnaireFeu
{
private int duree_cycle_elementaire = 1000; // ms
/** Couleur de feu courante pour la voie active. */
private EtatFeu etat_voie_active = EtatFeu.ROUGE; // Valeur initiale pour la sécurité
/** Index de la voie active. */
private int voie_active = 0;
/** Liste des feux pour chaque voie. */
private ArrayList<FeuTricolore>[] feux_par_voie;
private final int duree_vert, duree_orange, marge_duree_rouge;
private final int total_cycle, total_period;
private final Object lock = new Object();
private void boucleCycleFeux()
{
try
{
long time = System.currentTimeMillis(), t;
int index_cycle = 0;
for(;;)
{
// Mise à jour de l'état
voie_active = index_cycle / total_cycle;
int n = index_cycle % total_cycle;
if (n < marge_duree_rouge) etat_voie_active = EtatFeu.ROUGE;
else
{
n -= marge_duree_rouge;
if (n < duree_vert) etat_voie_active = EtatFeu.VERT;
else
{
n -= duree_vert;
if (n < duree_orange) etat_voie_active = EtatFeu.ORANGE;
else etat_voie_active = EtatFeu.ROUGE;
}
}
// Mise à jour des feux
synchronized(lock)
{
for(int voie=0 ; voie<feux_par_voie.length; voie++)
{
EtatFeu etat = voie==voie_active ? etat_voie_active : EtatFeu.ROUGE;
for(FeuTricolore feu : feux_par_voie[voie])
feu.setEtatCourant(etat);
}
}
// Cycle élémentaire suivant
index_cycle = (index_cycle+1) % total_period;
// Attendre jusqu'à l'heure du prochain cycle élémentaire
time += duree_cycle_elementaire;
t = System.currentTimeMillis();
if (t<time) Thread.sleep(time - t);
}
}
catch(InterruptedException ex)
{ /* Thread interrompu */ }
}
public void add(int voie, FeuTricolore feu)
{
if (voie<0 || voie>=feux_par_voie.length)
throw new IllegalArgumentException("Voie non valide : "+voie);
if (feu == null)
throw new NullPointerException("Pas de feu tricolore spécifié");
synchronized(lock)
{
feux_par_voie[voie].add(feu);
EtatFeu etat = voie==voie_active ? etat_voie_active : EtatFeu.ROUGE;
feu.setEtatCourant(etat);
}
}
public GestionnaireFeu()
{
this(2);
}
public GestionnaireFeu(int nombre_de_voies)
{
this(nombre_de_voies, 8, 2, 1);
}
/**
* Construire un gestionnaire de feux.
* @param nombre_de_voies Nombre de voies de circulation.
* @param duree_vert Durée du feu au vert en nombre de cycles élémentaires (1 seconde par défaut).
* @param duree_orange Durée du feu au orange en nombre de cycles élémentaires (1 seconde par défaut)
* @param marge_duree_rouge Marge de durée du feu au rouge en nombre de cycles élémentaires (1 seconde par défaut)
*/
public GestionnaireFeu(int nombre_de_voies,
int duree_vert, int duree_orange, int marge_duree_rouge)
{
if (nombre_de_voies<2 ||
duree_vert<1 ||
duree_orange<1 ||
marge_duree_rouge<0)
throw new IllegalArgumentException("Un argument est invalide");
this.duree_vert = duree_vert;
this.duree_orange = duree_orange;
this.marge_duree_rouge = marge_duree_rouge;
this.total_cycle = 2*marge_duree_rouge + duree_vert + duree_orange;
this.total_period = this.total_cycle * nombre_de_voies;
feux_par_voie = new ArrayList[nombre_de_voies];
for(int i=0 ; i<feux_par_voie.length ; i++)
feux_par_voie[i] = new ArrayList<FeuTricolore>();
// Le thread mettant à jour les feux en tâche de fond :
Thread th = new Thread(new Runnable()
{
@Override
public void run()
{ boucleCycleFeux(); }
}, "Gestion des feux tricolores");
th.setDaemon(true); // Ne maintient pas l'application en vie.
th.start();
}
}
La méthode statique de lancement permettant de montrer trois feux sur trois voies de circulation différentes est la suivante :
public static void main(String[] args)
{
// Créer la fenêtre
JFrame f = new JFrame("Feu tricolore");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Personnaliser le panneau du contenu
JPanel p_content = new JPanel();
p_content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
p_content.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 20));
f.setContentPane(p_content);
int nb_voies = 3;
GestionnaireFeu gestionnaire = new GestionnaireFeu(nb_voies);
// Ajouter un feu tricolore par voie
for(int i=0 ; i<nb_voies ; i++)
{
FeuTricolore feu = new FeuTricolore();
p_content.add(feu);
gestionnaire.add(i, feu);
}
f.pack();
f.setVisible(true);
}
Les cycles des feux sur les trois voies pour les temps par défaut sont résumés dans le tableau ci-dessous :
Voie 1 active | Voie 2 active | Voie 3 active | ||||||||||||||||||||||||||||||||||
Voie 1 | ||||||||||||||||||||||||||||||||||||
Voie 2 | ||||||||||||||||||||||||||||||||||||
Voie 3 |
Qualité de l'affichage du composant
modifierLe dessin circulaire du composant montre un crénelage causé par le tracé pixel par pixel. Il est possible d'améliorer l'affichage en utilisant l'antialiasing, en modifiant légèrement le code.
Tout d'abord, il faut définir les propriétés de rendu dans un dictionnaire utilisant des clés de la classe RenderingHints
.
La partie de code ci-dessous est à ajouter dans la classe FeuTricolore
, et montre les clés modifiables.
Celles sur l'affichage du texte (KEY_TEXT_ANTIALIASING, KEY_FRACTIONALMETRICS) ne sont pas nécessaires pour cette classe mais illustrent les possibilités.
protected static final HashMap<Key, Object> QUALITY_HINTS =
new HashMap<RenderingHints.Key, Object>();
static
{
QUALITY_HINTS.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// QUALITY_HINTS.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
QUALITY_HINTS.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
QUALITY_HINTS.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
QUALITY_HINTS.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
QUALITY_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
QUALITY_HINTS.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
QUALITY_HINTS.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
}
On peut aussi améliorer le tracé en utilisant une plus grande épaisseur de trait.
Les paramètres de trait sont gérés par la classe abstraite Stroke
, dont la classe BasicStroke
est une implémentation.
private Stroke STYLE_TRAIT = new BasicStroke(2); // Épaisseur : 2 pixels
Il ne reste plus qu'à utiliser ces éléments dans la méthode paintComponent
.
Cependant ils ne sont utilisables qu'avec une instance de la classe Graphics2D
, qui est la classe réelle de l'objet Graphics
passé à la méthode.
Il faut donc convertir l'objet pour définir les options de rendu et le style des traits.
@Override
protected void paintComponent(Graphics graphics)
{
Graphics2D g = (Graphics2D)graphics;
Dimension d = getSize();
g.addRenderingHints(QUALITY_HINTS);
g.setStroke(STYLE_TRAIT);
// ... la suite reste inchangée, identique à la version précédente.
}
Préservation du contexte graphique
modifierLa méthode paint
étant implémentée par appels à d'autres méthodes, elle leur passe le même contexte graphique de classe Graphics2D
.
Cela signifie que le dernier changement de paramètre (couleur, police de caractère, style de trait, région clip, ...) est celui par défaut pour la méthode appelée après.
Il est donc recommandé de restaurer les valeurs d'origine avant de terminer.
Cependant, cela implique de lire la valeur au début de la méthode pour la restaurer à la fin, ce qui complique le codage vu le nombre possible de paramètres auquel cela s'applique.
La solution est de créer un contexte dérivé de celui passé en paramètre, et de ne plus utiliser l'original ensuite.
Il faudra cependant libérer les ressources prises par le nouveau contexte en appelant sa méthode dispose()
à la fin.
@Override
protected void paintComponent(Graphics graphics)
{
Graphics2D g = (Graphics2D)graphics.create();
try
{
Dimension d = getSize();
g.addRenderingHints(QUALITY_HINTS);
g.setStroke(STYLE_TRAIT);
// ... la suite reste inchangée, identique à la version précédente, indentée d'un cran.
}
finally
{
g.dispose();
}
}
Orientation du composant
modifierLe feu tricolore utilisé dans une simulation de circulation réaliste avec vue de dessus d'un carrefour devrait pouvoir être positionné horizontalement de haut en bas et inversement, ou verticalement de droite à gauche et inversement.
Génération d'évènements spécifiques
modifierDans une utilisation réaliste du composant, le feu tricolore utilisé pour simuler la circulation devrait être capable de notifier ses changements d'états aux automobilistes pour qu'ils puissent s'arrêter ou redémarrer.
Créer une case à cocher à trois états
Ce chapitre complète le sujet du précédent en montrant comment créer un composant à partir d'un composant existant. Ce chapitre décrira la création d'une case à cocher à trois états :
- Coché : L'option est sélectionnée,
- Non coché : L'option n'est pas sélectionnée,
- Inconnu ou partiel : Il s'agit d'un état utilisé soit quand l'option n'a pas d'état défini par l'utilisateur, soit parce qu'une partie des éléments qu'implique la sélection de l'option est sélectionnée (sélection partielle).
Base du composant
modifierLa case à cocher sera basée sur un label (classe JLabel
) pour afficher le texte, associé à une icône pour afficher l'état.
L'état du composant dépendra de l'état de la case, mais aussi du fait que la souris soit au-dessus du composant ou non.
Le tableau ci-dessous présente les différentes images utilisées, avec le nom du fichier utilisé par le code du composant pour charger l'image.
Téléchargez les images sous le nom indiqué, dans le répertoire du package de la classe.
État | Non coché | Inconnu / partiel | Coché |
---|---|---|---|
Normal |
|
|
|
Survol de la souris |
|
|
|
État du composant
modifierLe composant a trois états possibles. Contrairement à une case à cocher normale, le type booléen ne suffit plus à représenter l'état. Il faut donc créer une énumération :
package org.wikibooks.fr.swing.component;
/**
* Trois états d'une case à cocher.
* @author fr.wikibooks.org
*/
public enum ETristate
{
/** Non coché */
UNCHECKED,
/** Inconnu ou sélection partielle. */
PARTIAL,
/** Coché */
CHECKED;
// ...
}
On ajoute deux méthodes retournant l'état suivant, selon deux modes de transition différents :
- Mode trois états : Non coché → Coché → Inconnu → Non coché.
- Mode deux états (case à cocher classique) : Inconnu → Non coché → Coché → Noncoché.
/**
* Get next state in the following tri-state cycle: {@link #UNCHECKED} -> {@link #CHECKED} -> {@link #PARTIAL} -> {@link #UNCHECKED}.
* @return The next state in the tri-state cycle.
* @see #getNextDual()
*/
public ETristate getNext()
{
switch(this)
{
case UNCHECKED: return CHECKED;
case PARTIAL: return UNCHECKED;
default: return PARTIAL;
}
}
/**
* Get next state in the following dual-state cycle: {@link #PARTIAL} -> {@link #UNCHECKED} -> {@link #CHECKED} -> {@link #UNCHECKED}.
* @return The next state in the dual-state cycle.
* @see #getNext()
*/
public ETristate getNextDual()
{
switch(this)
{
case UNCHECKED: return CHECKED;
default: return UNCHECKED;
}
}
Classe de la case à cocher
modifierLe code ci-dessous présente la première version de la classe du composant, complétée au fil des sections de ce chapitre.
La classe implémente l'interface java.awt.ItemSelectable
des objets contenant des éléments sélectionnables (la case à cocher elle-même).
package org.wikibooks.fr.swing.component;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
/**
* Case à cocher à trois états.
* @author fr.wikibooks.org
*/
public class TristateCheckbox extends JLabel
implements ItemSelectable
{
private static Icon[] icons;
static
{
// Chargement des icônes représentant l'état de la case à cocher :
icons = new Icon[6];
for(int i=0,j=0;i<3;i++)
{
icons[j++] = new ImageIcon(TristateCheckbox.class.getResource("checkbox_"+i+".png"));
icons[j++] = new ImageIcon(TristateCheckbox.class.getResource("checkbox_"+i+"_over.png"));
}
}
// L'état de la case à cocher
protected ETristate state = ETristate.UNCHECKED;
// Indicateur de souris survolant le composant, et de mode à deux ou trois états.
protected boolean mouse_over = false, dual_state = false;
private int i_state = 0; // Index de l'état courant
// Écouteur d'évènements pour la case à cocher :
ArrayList<ItemListener> ll_item = new ArrayList<ItemListener>();
protected void fireItemEvent(boolean selected)
{
ItemEvent e = new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
this, selected ? ItemEvent.SELECTED : ItemEvent.DESELECTED);
for(ItemListener l : ll_item)
{
l.itemStateChanged(e);
}
}
@Override
public void addItemListener(ItemListener l)
{ ll_item.add(l); }
@Override
public Object[] getSelectedObjects()
{ return state==ETristate.UNCHECKED?null:new Object[]{this}; }
@Override
public void removeItemListener(ItemListener l)
{ ll_item.remove(l); }
// ... à compléter
}
Gestion de l'état du composant
modifierLa méthode suivante est appelée par plusieurs autres méthodes pour mettre à jour l'icône affichée en fonction de l'état et des indicateurs de la case à cocher.
/**
* Met à jour l'icône si nécessaire.
*/
protected void updateState()
{
int n = i_state;
i_state = (mouse_over?1:0) | (state.ordinal()<<1);
if (i_state != n)
setIcon(icons[i_state]);
// Notifier les écouteurs enregistrés du changement d'état :
fireItemEvent(state!=ETristate.UNCHECKED);
}
Elle est utilisée par exemple par la méthode modifiant l'indicateur de survol de la souris :
private void setMouseOver(boolean b)
{
mouse_over = b;
updateState();
}
Et également par les méthodes modifiant l'état de la case à cocher, en mode trois états ou en mode deux états.
/**
* Get the current checkbox state.
* @return The current state.
*/
public ETristate getState()
{
return state;
}
// Définir l'état, en mode trois états
public void setState(ETristate state)
{
this.state = state;
this.dual_state = false;
updateState();
}
// Définir l'état, en mode deux états
public void setDualState(ETristate state)
{
this.state = state;
this.dual_state = true;
updateState();
}
// Définir l'état, en mode deux états, à moins que l'état soit inconnu/partiel
public void setMultipleState(ETristate state)
{
this.state = state;
this.dual_state = state != ETristate.PARTIAL;
updateState();
}
La méthode nextState()
met la case à cocher dans l'état suivant, selon le mode de cycle courant.
protected void nextState()
{
// Tester si le composant est activé, car cette méthode est appelée au clic de souris.
if (isEnabled())
{
state = dual_state ? state.getNextDual() : state.getNext();
updateState();
}
}
Déclencher une action
modifierBeaucoup de composants Swing permettent de déclencher une action sur certains évènements. Les méthodes ci-dessous fournissent une implémentation pour définir un nom d'action et enregistrer des écouteurs de déclenchement d'action.
protected String action_command = "";
public void setActionCommand(String action_command)
{
this.action_command = action_command;
}
public String getActionCommand()
{
return action_command;
}
ArrayList<ActionListener> ll_action = new ArrayList<ActionListener>();
public void addActionListener(ActionListener l)
{
ll_action.add(l);
}
public void removeActionListener(ActionListener l)
{
ll_action.remove(l);
}
protected void fireAction(String command, int modifiers)
{
ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
command, System.currentTimeMillis(), modifiers);
for(ActionListener l : ll_action)
l.actionPerformed(e);
}
Constructeurs
modifierLe constructeur de la case à cocher définit l'écouteur d'évènements de la souris qui appelle les méthodes vues précédemment.
// Constructeurs définissant des valeurs par défaut:
public TristateCheckbox()
{ this("", JLabel.LEFT); }
public TristateCheckbox(String label)
{ this(label, JLabel.LEFT); }
public TristateCheckbox(int align)
{ this("", align); }
// Contructeur principal
public TristateCheckbox(String label, int align)
{
super(label, icons[0], align); // Non coché initialement
setFocusable(true); // Peut obtenir le focus
addMouseListener(new MouseListener()
{
public void mouseReleased(MouseEvent e)
{
// Vérifier que la souris survole encore le composant
// au moment de relâcher le bouton.
if (mouse_over) nextState();
fireAction(action_command, e.getModifiersEx());
}
public void mousePressed(MouseEvent e)
{
setMouseOver(true);
}
public void mouseExited(MouseEvent e)
{
setMouseOver(false);
}
public void mouseEntered(MouseEvent e)
{
setMouseOver(true);
}
public void mouseClicked(MouseEvent e)
{ }
});
}
Test du composant
modifierLe code source pour tester la case à cocher est présenté ci-dessous :
// Méthode main utilisée pour tester le composant dans une fenêtre
public static void main(String[] args)
{
// Créer la fenêtre
JFrame f = new JFrame("Case à cocher");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Personnaliser le panneau du contenu
JPanel p_content = new JPanel();
p_content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
p_content.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 20));
f.setContentPane(p_content);
TristateCheckbox tcb = new TristateCheckbox("Test");
p_content.add(tcb);
f.pack();
f.setVisible(true);
}
Dessin d'un composant
La création d'un composant directement à partir de la classe javax.swing.JComponent
nécessite de définir une méthode pour redessiner le composant quand il est visible.
Ce chapitre décrit comment le faire en détails.
Le dessin d'un composant Swing est effectué par la méthode publique void paint(Graphics g)
.
Cette méthode délègue le travail à trois autres méthodes protégées appelées dans l'ordre suivant :
void paintComponent(Graphics g)
- Affiche le contenu du composant lui-même.
void paintBorder(Graphics g)
- Affiche les bords du composant dans la zone définie par les marges de type
Insets
. void paintChildren(Graphics g)
- Affiche les composants contenus dans celui-ci, par appel à leur méthode
paint
.
Cet ordre assure que les composants contenus soit affichés au-dessus du composant.
Pour définir l'apparence du composant, une sous-classe ne doit redéfinir que la méthode void paintComponent(Graphics g)
.
Celle-ci doit éviter de dessiner dans la zone définie par les marges de type Insets
qui est réservée aux bords (Border
).
Contexte graphique
modifierUn objet de classe java.awt.Graphics
est passé aux méthodes d'affichage du composant.
Il s'agit du contexte graphique, gérant les attributs courants de dessin (couleur, style des lignes, police de caractères, transformations...) et possédant des méthodes de tracé de formes, de texte, d'images...
La classe réelle du contexte graphique passé aux méthode d'affichage est java.awt.Graphics2D
possédant des méthodes supplémentaires améliorant l'affichage en 2D.
Il est donc nécessaire de convertir l'objet fourni pour pouvoir appeler ces méthodes avancées.
Attributs courants de dessin
modifierLe contexte graphique possède des accesseurs de lecture et écriture pour les attributs de dessin à utiliser par les prochaines méthodes appelées :
- Couleur courante pour dessiner :
setColor(Color)
- Police de caractère courante :
setFont(Font)
Attributs supplémentaires ajoutés par la classe java.awt.Graphics2D
:
- Couleur courante pour effacer :
setBackgroundColor(Color)
- Composition avec les pixels existant :
setComposite(Composite)
- Style des lignes (épaisseur, continu/tirets/..., extrémités, jointure de lignes d'un polygone) :
setStroke(Stroke)
- Motif de remplissage :
setPaint(Paint)
- Déformation des coordonnées (translation, rotation...) :
setTransform(AffineTransform)
Exemple :
@Override
protected void paintComponent(Graphics graphics)
{
Graphics2D g = (Graphics2D)graphics;
g.setColor(Color.BLUE);
// ... dessiner en bleu
g.setColor(Color.WHITE);
// ... dessiner en blanc
g.setStroke(new BasicStoke(1.5f));
// ... dessiner des lignes d'un pixel et demi d'épaisseur
}
Dessin de formes
modifierDans les exemples suivants, g représente le contexte graphique de classe java.awt.Graphics
.
- Méthode de tracé de contour de formes
- Les méthodes dont le nom commence par
draw
dessine le contour de formes et utilise la couleur et le style de tracé courants du contexte graphique. - Méthode de tracé de formes remplies
- Les méthodes dont le nom commence par
fill
dessine la surface remplie de formes et utilise la couleur et le motif courants du contexte graphique.
Les formes fermées possèdent à la fois une méthode de tracé du contour et une méthode de tracé de la forme remplie.
- Tracer une ligne entre deux points
g.drawLine(x1,y1, x2,y2)
- La méthode
drawLine
trace une ligne entre les deux points dont les coordonnées sont spécifiées en arguments, en utilisant le style courant de la ligne qui par défaut est une ligne continue d'un pixel d'épaisseur.
- Tracer un rectangle
g.drawRect(x,y, width,height)
- La méthode
drawRect
trace un rectangle de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Cette méthode trace un rectangle qui s'étend sur la largeur et la hauteur spécifiées additionnées à l'épaisseur courante de la ligne (1 pixel par défaut). Les largeurs ou hauteurs négatives sont acceptées. - Remplir un rectangle
g.fillRect(x,y, width,height)
- La méthode
fillRect
trace un rectangle rempli de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Les largeurs ou hauteurs négatives ne sont pas supportées.
- Tracer un rectangle 3D
g.drawRect3D(x,y, width,height, boolean raised)
- La méthode
drawRect
trace un rectangle de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Cette méthode trace un rectangle avec un bord donnant un effet 3D d'élévation quandraised=true
ou d'abaissement quandraised=false
. Ce rectangle s'étend sur la largeur et la hauteur spécifiées additionnées à l'épaisseur courante de la ligne (1 pixel par défaut). Les largeurs ou hauteurs négatives sont acceptées. - Remplir un rectangle 3D
g.fillRect3D(x,y, width,height, boolean raised)
- La méthode
fillRect3D
remplit un rectangle de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Cette méthode remplit un rectangle avec un bord donnant un effet 3D d'élévation quandraised=true
ou d'abaissement quandraised=false
. Ce rectangle s'étend sur la largeur et la hauteur spécifiées additionnées à l'épaisseur courante de la ligne (1 pixel par défaut). Les largeurs ou hauteurs négatives ne sont pas supportées.
- Tracer un rectangle arrondi
g.drawRect(x,y, width,height, arc_width, arc_height)
- La méthode
drawRect
trace un rectangle arrondi de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Les coins du rectangle sont arrondis avec des quarts d'ellipse dont les diamètres horizontaux et verticaux sont spécifiés par les argumentsarc_width
etarc_height
respectivement. Cette méthode trace un rectangle qui s'étend sur la largeur et la hauteur spécifiées additionnées à l'épaisseur courante de la ligne (1 pixel par défaut). Les largeurs ou hauteurs négatives sont acceptées. - Remplir un rectangle arrondi
g.fillRect(x,y, width,height, arc_width, arc_height)
- La méthode
fillRect
trace un rectangle arrondi rempli de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Les coins du rectangle sont arrondis avec des quarts d'ellipse dont les diamètres horizontal et vertical sont spécifiés par les argumentsarc_width
etarc_height
respectivement. Les largeurs ou hauteurs négatives ne sont pas supportées.
- Tracer un ovale
g.drawOval(x,y, width,height)
- La méthode
drawOval
trace un ovale contenu dans un rectangle de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Cette méthode trace un ovale qui s'étend sur la largeur et la hauteur spécifiées additionnées à l'épaisseur courante de la ligne (1 pixel par défaut). Les largeurs ou hauteurs négatives sont acceptées. - Remplir un ovale
g.fillOval(x,y, width,height)
- La méthode
fillOval
trace un ovale rempli contenu dans un rectangle de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Les largeurs ou hauteurs négatives ne sont pas supportées.
- Tracer un arc
g.drawArc(x,y, width,height, start_angle, ext_angle)
- La méthode
drawArc
trace un arc comme une partie d'un ovale contenu dans un rectangle de la taille spécifiée à partir des coordonnées du point supérieur gauche spécifiées. Cette méthode trace l'arc en démarrant à l'angle spécifié en radians, et qui s'étend sur le nombre de radians spécifié. Les largeurs ou hauteurs négatives sont acceptées.
- Tracer un polygone
g.drawPolygon(int[] x, int[] y, int point_count)
g.drawPolygon(Polygon p)
- La méthode
drawPolygon
trace un polygone à partir des deux tableaux de coordonnées spécifiés soit directement, soit indirectement en utilisant un objet de classejava.awt.Polygon
. Le dernier point est relié au premier pour fermer le polygone. - Remplir un polygone
g.fillPolygon(int[] x, int[] y, int point_count)
g.fillPolygon(Polygon p)
- La méthode
fillPolygon
remplit un polygone à partir des deux tableaux de coordonnées spécifiés soit directement, soit indirectement en utilisant un objet de classejava.awt.Polygon
. Le dernier point est relié au premier pour fermer le polygone.
- Tracer une ligne multipoints
g.drawPolyline(int[] x, int[] y, int point_count)
- La méthode
drawPolyline
trace une ligne entre les points à partir des deux tableaux de coordonnées spécifiés directement. Le dernier point n'est pas relié au premier.
Dessin de forme quelconque
modifierL'interface java.awt.Shape
définit les méthodes communes des formes pour tester l'appartenance d'un point ou d'un rectangle, tester si la forme intersecte un rectangle, obtenir le rectangle englobant, et obtenir un itérateur de chemin parcourant le contour de la forme.
Ce dernier groupe de méthodes est utilisé pour le dessin de la forme.
- Tracer une forme quelconque
g.draw(Shape)
- La méthode
draw
trace le contour de la forme spécifiée. - Remplir une forme quelconque
g.fill(Shape)
- La méthode
fill
remplit la surface de la forme spécifiée.
Différentes classes du package java.awt.geom
implémentent cette interface.
Celles dont le nom se termine par 2D sont des classes abstraites possédant deux sous-classes concrètes statiques internes appelées Double
et Float
permettant d'utiliser des coordonnées de type double
et float
respectivement.
Line2D
,Line2D.Double
,Line2D.Float
Rectangle2D
,Rectangle2D.Double
,Rectangle2D.Float
Rectangle
dérive de la classeRectangle2D
pour des coordonnées de typeint
.RoundRectangle2D
,RoundRectangle2D.Double
,RoundRectangle2D.Float
Polygon
Path2D
,Path2D.Double
,Path2D.Float
Arc2D
,Arc2D.Double
,Arc2D.Float
Ellipse2D
,Ellipse2D.Double
,Ellipse2D.Float
CubicCurve2D
,CubicCurve2D.Double
,CubicCurve2D.Float
QuadCurve2D
,QuadCurve2D.Double
,QuadCurve2D.Float
Area
englobe une autre forme et permet des opérations ensemblistes pour composer une aire complexe : union (méthodeadd
), intersection (méthodeintersect
), soustraction (méthodesubtract
), soustraction symétrique (méthodeexclusiveOr
).
Dessin de texte
modifierLes méthodes de tracé de texte utilisent la couleur et la police de caractères courantes du contexte graphique. Les coordonnées spécifiées sont celles du point du début de ligne (à gauche pour le sens d'écriture de gauche à droite, ou à droite pour l'autre sens) situé au niveau de la ligne de base des caractères.
- Tracer une chaîne de caractères
g.drawString(String, int x, int y)
g.drawString(String, float x, float y)
g.drawString(AttributedCharacterIterator, int x, int y)
g.drawString(AttributedCharacterIterator, float x, float y)
- La méthode
drawString
dessine la chaîne de caractère qu'elle soit spécifiée sous la forme d'une chaînejava.lang.String
, d'un séquence de caractères avec attributsjava.text.AttributedCharacterIterator
. La classejava.awt.Graphics2D
surcharge les méthodes pour autoriser des coordonnées sous forme de nombres à virgule flottante. - Tracer un tableau de caractères
g.drawChars(char[], int offset, int length, int x, int y)
- La méthode
drawChars
dessine la chaîne de caractère représenté par le tableau de caractères spécifié. - Tracer un tableau d'octets
g.drawBytes(byte[], int offset, int length, int x, int y)
- La méthode
drawChars
dessine la chaîne de caractère représenté par le tableau de caractères spécifié.
Police de caractères
modifierLa police de caractères utilisée est celle définie par la méthode void setFont(Font f)
du contexte graphique.
Elle est initialisée avec celle assignée au composant.
- Voir police de caractères
Gras, italique, soulignement et barré
modifierLes styles gras et italiques sont gérés par la classe java.awt.Font
(constantes PLAIN, BOLD, ITALIC, BOLD_ITALIC).
Le soulignement doit être géré par le code de dessin après ou avant avoir tracé le texte.
La classe java.awt.Font
fournit des informations pour le soulignement (distance et épaisseur de trait) via une instance de la classe java.awt.font.LineMetrics
.
Le code ci-dessous affiche un texte souligné, en exploitant ces informations et en utilisant l'objet java.awt.FontMetrics
pour mesurer le texte et obtenir la longueur de la ligne de soulignement du texte.
String texte_souligne = "Voici comment souligner le texte."
int tx = 10, ty = 20;
g.drawString(texte_souligne, tx, ty);
FontMetrics fm = g.getFontMetrics();
int tw = fm.stringWidth(texte_souligne);
LineMetrics lm = g.getFont().getLineMetrics(texte_souligne, g.getFontRenderContext());
float line_offset = lm.getUnderlineOffset();
float line_thickness = lm.getUnderlineThickness();
int ly = (int)(ty+line_offset + line_thickness /2); // Position
g.setStroke(new BasicStroke(line_thickness)); // Épaisseur
g.drawLine(tx, ly, tx+tw, ly);
Le style barré utilise le même principe en exploitant les informations retournées par les méthodes getStrikethroughOffset()
et getStrikethroughThickness()
.
Dans le code précédent, il suffit de remplacer les valeurs affectées aux variables line_offset
et line_thickness
:
float line_offset = lm.getStrikethroughOffset();
float line_thickness = lm.getStrikethroughThickness();
Les styles souligné et barré ne sont pas gérés directement par la classe java.awt.Font
car il s'agit d'éléments graphiques (lignes) séparés du texte, pouvant avoir une couleur différente du texte, ou une épaisseur différente de celle indiquée par la classe java.awt.font.LineMetrics
.
Afficher une image
modifierLe contexte graphique possède plusieurs variantes de la méthode drawImage
permettant d'afficher une image (instance de java.awt.Image
) :
drawImage(image, x, y, observateur)
- Afficher l'image à sa taille normal aux coordonnées indiquées pour le coin supérieur gauche.
drawImage(image, x, y, width, height, observateur)
- Afficher l'image à la taille spécifiée par
width
(largeur) etheight
(hauteur) aux coordonnées indiquées pour le coin supérieur gauche. drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observateur)
- Afficher une portion de l'image définie par les coordonnées source des coins de la zone rectangulaire
sx1
,sy1
,sx2
,sy2
dans la zone destination définie par les coordonnées source des coins de la zone rectangulairedx1
,dy1
,dx2
,dy2
. Cette méthode permet donc à la fois d'afficher une partie de l'image et de la redimensionner si nécessaire.
Avec ces méthodes, les pixels transparents laissent voir ce qui était dessiné avant.
Paramètres communs :
- image
- Image à afficher (instance de
java.awt.Image
). - observateur
- Implémentation de l'interface
java.awt.image.ImageObserver
à notifier de la progression du chargement de l'image. Si l'image est déjà chargée auparavant, ce paramètre peut êtrenull
.
Exemple :
// Image chargée auparavant, en dehors de toute méthode paint, par exemple :
URL url_fond = new URL("https://upload.wikimedia.org/wikipedia/commons/a/a6/VST_image_of_the_Hercules_galaxy_cluster.jpg");
Image image_fond = ImageIO.read(url_fond);
// ...
g.drawImage(image_fond, 10, 10, null);
Ces méthodes possèdent une variante acceptant une couleur à afficher comme couleur de fond pour les images transparentes.
Transformations
modifierComposant transparent
modifierPar défaut, un composant est opaque, ce qui signifie qu'il doit dessiner toute la zone rectangulaire correspondant à sa taille.
Si la méthode paint
ou la méthode paintComponent
ne dessine pas toute la zone, pour laisser paraître le fond du contenant (un composant non rectangulaire, comme un bouton arrondi par exemple), des artifacts peuvent apparaître de manière sporadique ou non car Swing réutilise le même contexte graphique pour peindre plusieurs composants.
Dans ce cas, il ne faut pas oublier de déclarer le composant comme transparent pour indiquer qu'il faut un contexte graphique propre pour peindre le composant, dans le constructeur de la classe du composant :
setOpaque(false);
Qualité de l'affichage
modifierPar défaut, les algorithmes de dessin des méthodes du contexte graphique privilégient la vitesse de rendu à la qualité.
La méthode addRenderingHints(Map<?, ?> hints)
de la classe java.awt.Graphics2D
permet de changer différentes options pour améliorer la qualité.
Le paramètre hints
est un dictionnaire associant des clés définies par les constantes KEY_
de la classe java.awt.RenderingHints
à des valeurs définies par les constantes VALUE_
.
Quand les paramètres de qualité ne changent pas, il est recommandé de créer le dictionnaire une seule fois, comme dans l'exemple suivant :
protected static final HashMap<Key, Object> QUALITY_HINTS =
new HashMap<RenderingHints.Key, Object>();
static
{
QUALITY_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
QUALITY_HINTS.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
QUALITY_HINTS.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
QUALITY_HINTS.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
QUALITY_HINTS.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
QUALITY_HINTS.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
QUALITY_HINTS.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
Ce dictionnaire est ensuite utilisable dans la méthode d'affichage du composant :
@Override
protected void paintComponent(Graphics graphics)
{
Graphics2D g = (Graphics2D)graphics;
g.addRenderingHints(QUALITY_HINTS);
// ... dessin du composant avec qualité améliorée
}
Propriétés générales de qualité
modifierLes propriétés générales de qualités s'appliquent quand aucune autre propriété équivalente plus spécifique n'est applicable.
RenderingHints.KEY_RENDERING
: Qualité générale d'affichage.
Valeur | Description |
---|---|
RenderingHints.VALUE_RENDER_DEFAULT
|
Option par défaut. |
RenderingHints.VALUE_RENDER_SPEED
|
Privilégier la vitesse d'affichage. |
RenderingHints.VALUE_RENDER_QUALITY
|
Privilégier la qualité d'affichage. |
RenderingHints.KEY_DITHERING
: Gestion des couleurs sur un écran où leur nombre est limité.
Valeur | Description |
---|---|
RenderingHints.VALUE_DITHER_DEFAULT
|
Option par défaut. |
RenderingHints.VALUE_DITHER_DISABLE
|
Pas de dithering, les couleurs sont arrondies à la plus proche sans propagation d'erreur. |
RenderingHints.VALUE_DITHER_ENABLE
|
Activer le dithering permettant la propagation d'erreur d'arrondi des couleurs sur les pixels voisins. |
RenderingHints.KEY_ALPHA_INTERPOLATION
: Qualité de la transparence des couleurs (canal alpha)
Valeur | Description |
---|---|
RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT
|
Option par défaut. |
RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED
|
Privilégier la vitesse de calcul de la transparence. |
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY
|
Privilégier la qualité de calcul de la transparence. |
Qualité des lignes
modifierUne partie des propriétés de qualité concerne le tracé des lignes par les méthodes dessinant un contour : drawLine
, drawRect
, drawOval
, drawShape
...
Cela concerne les méthodes dont le nom commence par draw
sauf drawImage
, drawRenderedImage
, drawRenderableImage
et drawString
, drawBytes
, drawChars
.
RenderingHints.KEY_ANTIALIASING
: option de réduction des effets pixelisés des lignes et des bords des formes géométriques.
Valeur | Description |
---|---|
RenderingHints.VALUE_ANTIALIAS_DEFAULT
|
Utiliser l'option par défaut |
RenderingHints.VALUE_ANTIALIAS_OFF
|
Sans antialiasing |
RenderingHints.VALUE_ANTIALIAS_ON
|
Avec antialiasing |
RenderingHints.KEY_STROKE_CONTROL
: Autoriser une correction de la géométrie pour améliorer la qualité du tracé.
Valeur | Description |
---|---|
RenderingHints.VALUE_STROKE_DEFAULT
|
Utiliser l'option par défaut |
RenderingHints.VALUE_STROKE_NORMALIZE
|
Normaliser pour améliorer l'uniformité ou l'espacement des lignes et l'esthétique globale. |
RenderingHints.VALUE_STROKE_PURE
|
Géométrie non modifiée, affiché tel quel avec une précision en dessous du pixel. |
Aperçu pour une épaisseur de 1.0
modifierRenderingHints.VALUE_ANTIALIAS_OFF
|
RenderingHints.VALUE_ANTIALIAS_ON
| |
---|---|---|
RenderingHints.
|
||
RenderingHints.
|
Aperçu pour une épaisseur de 1.5
modifierRenderingHints.VALUE_ANTIALIAS_OFF
|
RenderingHints.VALUE_ANTIALIAS_ON
| |
---|---|---|
RenderingHints.
|
||
RenderingHints.
|
Qualité du texte
modifierTrois autres propriétés de qualité concerne le tracé de texte, qu'il soit sous forme de chaîne de caractère (drawString
), d'un tableau d'octets (drawBytes
) ou d'un tableau de caractères (drawChars
).
RenderingHints.KEY_FRACTIONALMETRICS
: utiliser une précision en dessous du pixel pour positionner les caractères selon le vecteur d'avance mis à l'échelle de la taille de la police de caractères ou bien arrondir à l'entier le plus proche.
Valeur | Description |
---|---|
RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT
|
Utiliser l'option par défaut |
RenderingHints.VALUE_FRACTIONALMETRICS_OFF
|
Utiliser des mesures fractionnaires. |
RenderingHints.VALUE_FRACTIONALMETRICS_ON
|
Arrondir à l'entier le plus proche. |
RenderingHints.KEY_TEXT_ANTIALIASING
: utiliser de l'antialiasing pour le texte ou bien arrondir au pixel le plus proche.
Valeur | Description |
---|---|
RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT
|
Utiliser l'option par défaut |
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF
|
Utiliser l'antialias. |
RenderingHints.VALUE_TEXT_ANTIALIAS_ON
|
Sans antialias. |
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP
|
Utiliser l'antialias (VALUE_TEXT_ANTIALIAS_ON ) ou non (VALUE_TEXT_ANTIALIAS_OFF ) selon l'information de la table GASP[1] présente dans la police de caractères pour la taille utilisée. Cette information est en général présente dans les polices TrueType, cependant, si l'information n'est pas présente, la valeur par défaut de l'algorithme est utilisée (VALUE_TEXT_ANTIALIAS_DEFAULT ).
Cette option peut ne pas avoir un comportement consistant quand une police de caractères logique est utilisée vu qu'elle est composée de plusieurs polices physiques. |
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB
|
Utiliser l'antialias sur écran LCD, dans le sens horizontal, avec l'ordre de couleur des sous-pixels RGB (rouge, vert, bleu). |
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR
|
Utiliser l'antialias sur écran LCD, dans le sens horizontal, avec l'ordre de couleur des sous-pixels BGR (bleu, vert, rouge). |
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB
|
Utiliser l'antialias sur écran LCD, dans le sens vertical, avec l'ordre de couleur des sous-pixels RGB (rouge, vert, bleu). |
RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VBGR
|
Utiliser l'antialias sur écran LCD, dans le sens vertical, avec l'ordre de couleur des sous-pixels BGR (bleu, vert, rouge). |
RenderingHints.KEY_TEXT_LCD_CONTRAST
: ajustement du contraste pour l'antialiasing sur écran LCD.
Valeur | Description |
---|---|
int
|
Valeur entre 100 et 250 pour ajuster le contraste du texte. Typiquement en noir sur fond blanc, la valeur 100 donne un contraste fort, et 250 un contraste faible. L'intervalle typique est entre 140 et 180. |
Le réglage de l'antialiasing du texte dont le nom de la valeur contient LCD est réservé aux écrans LCD ou assimilés où les pixels voisins sont tellement proches qu'il est possible que la composante bleue du pixel se mélange à la rouge du pixel voisin. Ce mélange permettant d'augmenter artificiellement la densité de pixels est la technique utilisée pour l'antialiasing LCD du texte, avec différentes variantes selon la disposition des composantes de couleur composant les pixels de l'écran. La valeur à utiliser dépend donc du modèle de l'écran.
Dans l'image ci-dessus, l'antialiasing de texte est RenderingHints.VALUE_TEXT_ANTIALIAS_HRGB
: H pour Horizontal, RGB représentant l'ordre des composants des pixels de gauche à droite : rouge, vert, bleu.
La couleur rouge borde le côté gauche des lettres en noir pour se mélanger au fond blanc, tandis que du bleu borde le côté opposé des lettres.
Les côtés des couleurs rouges et bleues serait inversés pour un texte blanc sur fond noir.
Zoom sur le bord gauche des lettres en noir sur fond blanc | Zoom sur le bord droit des lettres en noir sur fond blanc | ||||
blanc | rouge | noir | noir | bleu | blanc |
Sur un écran LCD dont l'ordre des composants correspond, la composante bleue se mélange au rouge et vert à proximité, idem pour le rouge à droite avec les composantes bleue et verte.
Sur un écran LCD dont l'ordre des composants serait l'ordre inverse cela donnerait un effet indésirable de tracé parallèle rendant le texte flou :
Zoom sur le bord gauche des lettres en noir sur fond blanc | Zoom sur le bord droit des lettres en noir sur fond blanc | ||||
blanc | rouge | noir | noir | bleu | blanc |
Il faut dans ce cas utiliser l'ordre inverse :
Zoom sur le bord gauche des lettres en noir sur fond blanc | Zoom sur le bord droit des lettres en noir sur fond blanc | ||||
blanc | bleu | noir | noir | rouge | blanc |
Sur un écran non LCD, où les pixels seraient trop éloignés pour que la composante d'un pixel puissent se mélanger au pixel voisin, ou dont les pixels n'ont pas une haute densité, l'effet ne fonctionne pas correctement et un bord rouge ou bleu est visible.
Pour ce cas, il est recommandé d'utiliser RenderingHints.VALUE_TEXT_ANTIALIAS_ON
.
Aperçu de l'antialiasing et des mesures fractionnaires
modifierUn aperçu de l'effet des différentes valeurs (lignes) pour RenderingHints.KEY_TEXT_ANTIALIASING
(sans le préfixe commun RenderingHints.VALUE_TEXT_ANTIALIAS_
, et pour le contraste par défaut) est présenté ci-dessous, combiné aux effets de RenderingHints.KEY_FRACTIONALMETRICS
(colonnes).
RenderingHints.VALUE_FRACTIONALMETRICS_OFF
|
RenderingHints.VALUE_FRACTIONALMETRICS_ON
| |
---|---|---|
OFF
|
||
ON
|
||
LCD_HRGB
|
||
LCD_HBGR
|
||
LCD_VRGB
|
||
LCD_VBGR
|
Aperçu de l'ajustement du contraste
modifierDifférentes valeurs d'ajustement du contraste pour VALUE_TEXT_ANTIALIAS_LCD_HRGB
100
|
|
---|---|
140
|
|
180
|
|
250
|
Qualité des images
modifierLes autres propriétés de qualité concerne l'affichage des images par les méthodes drawImage
, drawRenderedImage
et drawRenderableImage
.
RenderingHints.KEY_INTERPOLATION
: option d'affichage d'images redimensionnées.
Valeur | Description |
---|---|
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
|
Pixel voisin le plus proche. |
RenderingHints.VALUE_INTERPOLATION_BILINEAR
|
La couleur d'un pixel est déterminée linéairement par les 4 pixels voisins les plus proches. |
RenderingHints.VALUE_INTERPOLATION_BICUBIC
|
La couleur d'un pixel est déterminée par une fonction cubique sur les 9 pixels voisins les plus proches. |
RenderingHints.KEY_COLOR_RENDERING
: option de qualité d'affichage des couleurs.
Valeur | Description |
---|---|
RenderingHints.VALUE_COLOR_RENDER_DEFAULT
|
Option par défaut. |
RenderingHints.VALUE_COLOR_RENDER_SPEED
|
Conversion rapide des couleurs. |
RenderingHints.VALUE_COLOR_RENDER_QUALITY
|
Conversion des couleurs privilégiant la qualité. |
Références
modifier- ↑ Grid-fitting and Scan-conversion Procedure Table https://docs.microsoft.com/en-us/typography/opentype/spec/gasp
Dessin d'une étoile
Le dessin d'un composant utilise différente formes de base : lignes, rectangles, polygones. Le but de cette section est de dessiner des étoiles de cette forme :
Afin d'avoir un composant générique permettant d'afficher le texte sous une série d'étoiles correspondant à un niveau d'appréciation, un niveau de qualité, ...
Composant
modifierLa classe DisplayLevel
ci-dessous définit le composant : état courant et couleurs utilisées.
Elle sera complétée progressivement au cours de ce chapitre.
package org.wikibooks.fr.components;
import java.awt.*;
import javax.swing.*;
/**
* Afficher un niveau avec une série d'étoiles.
* @author fr.wikibooks.org
*/
public class DisplayLevel extends JComponent
{
// Couleurs prédéfinies
protected static final Color
C_LINE = Color.BLACK,
C_LINE_OFF = Color.LIGHT_GRAY,
C_TEXT = Color.BLACK,
C_FILL = new Color(240,176,20);
protected int
maxlevel, // Le nombre total d'étoiles.
level; // Le nombre d'étoiles pleines.
protected String
level_name; // Texte de niveau d'appréciation/qualité/...
// Couleurs utilisées
protected Color
c_line = C_LINE,
c_line_off = C_LINE_OFF,
c_text = C_TEXT;
public DisplayLevel()
{
setForeground(C_FILL);
setMinimumSize(new Dimension(400,50));
setPreferredSize(new Dimension(500,50));
setSize(new Dimension(500,50));
}
/**
* Modifier le niveau affiché.
* @param level Niveau affiché.
* @param max Niveau max.
* @param name Texte affiché.
*/
public void setLevel(int level, int max, String name)
{
this.maxlevel = max;
this.level = level;
this.level_name = name;
repaint();
}
// ...
}
Dessin d'une étoile
modifierLes lignes de la forme n'étant pas courbées, le dessin d'un polygone sera utilisé.
Le polygone sera composé de 10 points :
- 5 points pour les pointes, à la distance r du centre du cercle englobant (voir image ci-contre).
- 5 points entre les pointes, à la distance s du centre du cercle englobant (voir image ci-contre).
La distance r représente le rayon du cercle englobant qui déterminera la taille des étoiles dessinées. La distance s est calculable à partir du rayon r par la formule suivante :
Avec
Les points étant radialement espacés régulièrement, l'angle entre deux points est de degrés.
Coordonnées du polygone
modifierLes coordonnées des points du polygone sont pré-calculées et stockées dans un tableau pour un rayon de 10000 pixels. Ces coordonnées de base servent au calcul des coordonnées selon la taille d'étoile voulue, et sont relatives au centre du cercle situé aux coordonnées (0,0).
private static final int[]
SHAPE_X={ 9511, 2245, 0, -2245, -9511, -3633, -5878, 0, 5878, 3633 },
SHAPE_Y={ -3090, -3090, -10000, -3090, -3090, 1180, 8090, 3820, 8090, 1180 };
Un objet de la classe java.awt.Polygon
est utilisé pour représenter le polygone adapté à la taille de l'étoile selon celle du composant.
Les points sont calculés en multipliant les coordonnées pré-calculées des 10 points par le rayon voulu et en divisant le résultat par 10000.
Pour les points intermédiaires, on peut aussi ajouter un coefficient pour changer la forme de l'étoile :
-
Dessin d'une étoile avec coefficient de 80%.
-
Dessin d'une étoile avec coefficient de 100%.
-
Dessin d'une étoile avec coefficient de 150%.
private Polygon shape; // Polygone calculé pour une certaine taille, null signifiant à recalculer.
private static final int SHAPE_NORMAL = 100; // Coefficient nominal pour la forme de l'étoile.
private int
shape_size = -1, // La taille courante pour le polygone shape.
shape_x, // Abscisse du point de référence.
shape_y, // Ordonnée du point de référence.
star_shape = SHAPE_NORMAL; // Facteur de forme de l'étoile.
/**
* Modifier la forme de l'étoile dessinée.
* @param factor_percent Facteur de forme en pourcentage :<ul>
* <li>inférieur à 100 pour une étoile plus fine,</li>
* <li>supérieur à 100 pour une étoile plus grosse.</li>
* </ul>
*/
public void setStarShape(int factor_percent)
{
if (star_shape != factor_percent)
{
star_shape = factor_percent;
shape = null; // Polygone à recalculer
shape_size = -1; // Polygone à recalculer
repaint();
}
}
Calcul du polygone
modifierLe polygone est calculé, si besoin, au moment de dessiner le composant :
/**
* Obtenir le polygone à utiliser pour la taille voulue.
* Si besoin, cette méthode recalcule le polygone.
* @param size Taille d'étoile.
* @return Le polygone à utiliser.
*/
private Polygon getShape(int size)
{
if (shape_size<0 || size!=shape_size)
{
// Polygone à recalculer car la taille est différente :
shape_size = size;
// Les 11 points du poloygone fermé, le dernier étant identique au premier.
int[] xp=new int[11], yp=new int[11];
final int
r = size/2, // Rayon
shpr = r*star_shape, // Coefficient pour les points intermédiaires.
shpmax = 10000*SHAPE_NORMAL; // Diviseur pour les points intermédiaires.
for(int i=0 ; i<10 ; i++)
{
if ((i&1)==0)
{
xp[i] = SHAPE_X[i]*r/10000;
yp[i] = SHAPE_Y[i]*r/10000;
}
else
{
xp[i] = SHAPE_X[i]*shpr/shpmax;
yp[i] = SHAPE_Y[i]*shpr/shpmax;
}
}
xp[10] = xp[0];
yp[10] = yp[0];
shape_x = -r;
shape_y = -r;
shape = null;
shape = new Polygon(xp, yp, 11); // Polygone à onze points.
}
return shape;
}
Les coordonnées dans le polygone shape
sont fixes, mais le composant a besoin de le dessiner plusieurs fois à différents endroits.
La méthode moveShapeTo
ci-dessous déplace les points en mettant à jour le point de référence servant à calculer le vecteur de translation à appliquer :
/**
* Déplacer le polygone au point spécifié par translation géométrique.
* @param x Abscisse du nouveau point de référence.
* @param y Ordonnée du nouveau point de référence.
*/
private void moveShapeTo(int x, int y)
{
if (x!=shape_x || y!=shape_y)
{
shape.translate(x-shape_x, y-shape_y);
shape_x = x;
shape_y = y;
}
}
Dessiner le composant
modifierIl ne reste plus que le dessin du composant, utilisant plusieurs fois le polygone pour afficher les étoiles et affiche également le texte.
@Override
protected void paintComponent(Graphics gg)
{
Dimension d = getSize();
Graphics2D g = (Graphics2D) gg;
if (isOpaque())
{
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
}
applyHintsTo(g);
final String s = level_name;
g.setFont(getFont());
FontMetrics fm = g.getFontMetrics();
int y = 2, fha = fm.getMaxAscent(), fhb = fm.getMaxDescent(),
hb = d.height-8-(s==null?0:fha+fhb),
x = (d.width+4-(hb+4)*maxlevel)/2;
if (hb>4 && maxlevel>0)
{
Polygon p = getShape(hb);
for(int l=0 ; l<maxlevel ; l++)
{
moveShapeTo(x, y);
if (l<level)
{
g.setColor(getForeground());
g.fillPolygon(p);
g.setColor(c_line);
}
else g.setColor(c_line_off);
g.drawPolygon(p);
x += hb+4;
}
}
if (s!=null)
{
y += hb+4+fha;
g.setColor(c_text);
int tw = fm.stringWidth(s);
x = (d.width-tw)/2;
g.drawString(s, x, y);
}
super.paintComponent(gg);
}
La méthode applyHintsTo
ajuste la configuration pour améliorer la qualité d'affichage :
protected static void applyHintsTo(Graphics2D g)
{
g.addRenderingHints(hm_hints);
}
private static HashMap<Key, Object> hm_hints = new HashMap<Key, Object>();
static
{
hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
}
Code source complet et utilisation
modifierLe code source complet de la classe DisplayLevel
est ci-dessous.
package org.wikibooks.fr.components;
import java.awt.*;
import java.awt.RenderingHints.Key;
import java.util.*;
import javax.swing.*;
/**
* Afficher un niveau avec une série d'étoiles.
* @author fr.wikibooks.org
*/
public class DisplayLevel extends JComponent
{
// Couleurs prédéfinies
protected static final Color
C_LINE = Color.BLACK,
C_LINE_OFF = Color.LIGHT_GRAY,
C_TEXT = Color.BLACK,
C_FILL = new Color(240,176,20);
protected int
maxlevel, // Le nombre total d'étoiles.
level; // Le nombre d'étoiles pleines.
protected String
level_name; // Texte de niveau d'appréciation/qualité/...
// Couleurs utilisées
protected Color
c_line = C_LINE,
c_line_off = C_LINE_OFF,
c_text = C_TEXT;
public DisplayLevel()
{
setForeground(C_FILL);
setMinimumSize(new Dimension(400,50));
setPreferredSize(new Dimension(500,50));
setSize(new Dimension(500,50));
}
/**
* Modifier le niveau affiché.
* @param level Niveau affiché.
* @param max Niveau max.
* @param name Texte affiché.
*/
public void setLevel(int level, int max, String name)
{
this.maxlevel = max;
this.level = level;
this.level_name = name;
repaint();
}
private static final int[]
SHAPE_X={ 9511, 2245, 0, -2245, -9511, -3633, -5878, 0, 5878, 3633 },
SHAPE_Y={ -3090, -3090, -10000, -3090, -3090, 1180, 8090, 3820, 8090, 1180 };
private Polygon shape; // Polygone calculé pour une certaine taille, null signifiant à recalculer.
private static final int SHAPE_NORMAL = 100; // Coefficient nominal pour la forme de l'étoile.
private int
shape_size = -1, // La taille courante pour le polygone shape.
shape_x, // Abscisse du point de référence.
shape_y, // Ordonnée du point de référence.
star_shape = SHAPE_NORMAL; // Facteur de forme de l'étoile.
/**
* Modifier la forme de l'étoile dessinée.
* @param factor_percent Facteur de forme en pourcentage :<ul>
* <li>inférieur à 100 pour une étoile plus fine,</li>
* <li>supérieur à 100 pour une étoile plus grosse.</li>
* </ul>
*/
public void setStarShape(int factor_percent)
{
if (star_shape != factor_percent)
{
star_shape = factor_percent;
shape = null; // Polygone à recalculer
shape_size = -1; // Polygone à recalculer
repaint();
}
}
/**
* Obtenir le polygone à utiliser pour la taille voulue.
* Si besoin, cette méthode recalcule le polygone.
* @param size Taille d'étoile.
* @return Le polygone à utiliser.
*/
private Polygon getShape(int size)
{
if (shape_size<0 || size!=shape_size)
{
// Polygone à recalculer car la taille est différente :
shape_size = size;
// Les 11 points du poloygone fermé, le dernier étant identique au premier.
int[] xp=new int[11], yp=new int[11];
final int
r = size/2, // Rayon
shpr = r*star_shape, // Coefficient pour les points intermédiaires.
shpmax = 10000*SHAPE_NORMAL; // Diviseur pour les points intermédiaires.
for(int i=0 ; i<10 ; i++)
{
if ((i&1)==0)
{
xp[i] = SHAPE_X[i]*r/10000;
yp[i] = SHAPE_Y[i]*r/10000;
}
else
{
xp[i] = SHAPE_X[i]*shpr/shpmax;
yp[i] = SHAPE_Y[i]*shpr/shpmax;
}
}
xp[10] = xp[0];
yp[10] = yp[0];
shape_x = -r;
shape_y = -r;
shape = null;
shape = new Polygon(xp, yp, 11); // Polygone à onze points.
}
return shape;
}
/**
* Déplacer le polygone au point spécifié par translation géométrique.
* @param x Abscisse du nouveau point de référence.
* @param y Ordonnée du nouveau point de référence.
*/
private void moveShapeTo(int x, int y)
{
if (x!=shape_x || y!=shape_y)
{
shape.translate(x-shape_x, y-shape_y);
shape_x = x;
shape_y = y;
}
}
@Override
protected void paintComponent(Graphics gg)
{
Dimension d = getSize();
Graphics2D g = (Graphics2D) gg;
if (isOpaque())
{
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
}
applyHintsTo(g);
final String s = level_name;
g.setFont(getFont());
FontMetrics fm = g.getFontMetrics();
int y = 2, fha = fm.getMaxAscent(), fhb = fm.getMaxDescent(),
hb = d.height-8-(s==null?0:fha+fhb),
x = (d.width+4-(hb+4)*maxlevel)/2;
if (hb>4 && maxlevel>0)
{
Polygon p = getShape(hb);
for(int l=0 ; l<maxlevel ; l++)
{
moveShapeTo(x, y);
if (l<level)
{
g.setColor(getForeground());
g.fillPolygon(p);
g.setColor(c_line);
}
else g.setColor(c_line_off);
g.drawPolygon(p);
x += hb+4;
}
}
if (s!=null)
{
y += hb+4+fha;
g.setColor(c_text);
int tw = fm.stringWidth(s);
x = (d.width-tw)/2;
g.drawString(s, x, y);
}
super.paintComponent(gg);
}
protected static void applyHintsTo(Graphics2D g)
{
g.addRenderingHints(hm_hints);
}
private static HashMap<Key, Object> hm_hints = new HashMap<Key, Object>();
static
{
hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
}
}
Le programme ci-dessous illustre comment le composant peut être utilisé.
package org.wikibooks.fr.test;
import org.wikibooks.fr.components.*;
import java.awt.*;
import javax.swing.*;
/**
*
* @author fr.wikibooks.org
*/
public class DisplayTest
{
public static void main(String[] args)
{
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(500, 140);
Container c = f.getContentPane();
DisplayLevel niveau = new DisplayLevel();
niveau.setSize(400, 100);
c.add(niveau);
niveau.setStarShape(120);
niveau.setOpaque(true);
niveau.setBackground(Color.WHITE);
niveau.setFont(new Font("Dialog", Font.BOLD, 16));
niveau.setLevel(3, 5, "Ce chapitre est bien"); // 3 étoiles sur 5.
f.setVisible(true);
}
}
Dessin de texte et effets
Le dessin d'un composant est réalisé par les méthodes paint
de la classe du composant (en Swing, la méthode void paintComponent(Graphics g)
) utilisant un contexte graphique.
Dessin de texte
modifierLes méthodes de tracé de texte utilisent la couleur et la police de caractères courantes du contexte graphique. Les coordonnées spécifiées sont celles du point du début de ligne (à gauche pour le sens d'écriture de gauche à droite, ou à droite pour l'autre sens) situé au niveau de la ligne de base des caractères.
- Tracer une chaîne de caractères
g.drawString(String, int x, int y)
g.drawString(String, float x, float y)
g.drawString(AttributedCharacterIterator, int x, int y)
g.drawString(AttributedCharacterIterator, float x, float y)
- La méthode
drawString
dessine la chaîne de caractère qu'elle soit spécifiée sous la forme d'une chaînejava.lang.String
, d'un séquence de caractères avec attributsjava.text.AttributedCharacterIterator
. La classejava.awt.Graphics2D
surcharge les méthodes pour autoriser des coordonnées sous forme de nombres à virgule flottante. - Tracer un tableau de caractères
g.drawChars(char[], int offset, int length, int x, int y)
- La méthode
drawChars
dessine la chaîne de caractère représenté par le tableau de caractères spécifié. - Tracer un tableau d'octets
g.drawBytes(byte[], int offset, int length, int x, int y)
- La méthode
drawChars
dessine la chaîne de caractère représenté par le tableau de caractères spécifié.
Police de caractères
modifierLa police de caractères utilisée est celle définie par la méthode void setFont(Font f)
du contexte graphique.
Elle est initialisée avec celle assignée au composant.
- Voir police de caractères
Gras, italique, soulignement et barré
modifierLes styles gras et italiques sont gérés par la classe java.awt.Font
(constantes PLAIN, BOLD, ITALIC, BOLD_ITALIC).
Le soulignement doit être géré par le code de dessin après ou avant avoir tracé le texte.
La classe java.awt.Font
fournit des informations pour le soulignement (distance et épaisseur de trait) via une instance de la classe java.awt.font.LineMetrics
.
Le code ci-dessous affiche un texte souligné, en exploitant ces informations et en utilisant l'objet java.awt.FontMetrics
pour mesurer le texte et obtenir la longueur de la ligne de soulignement du texte.
String texte_souligne = "Voici comment souligner le texte."
int tx = 10, ty = 20;
g.drawString(texte_souligne, tx, ty);
FontMetrics fm = g.getFontMetrics();
int tw = fm.stringWidth(texte_souligne);
LineMetrics lm = g.getFont().getLineMetrics(texte_souligne, g.getFontRenderContext());
float line_offset = lm.getUnderlineOffset();
float line_thickness = lm.getUnderlineThickness();
int ly = (int)(ty+line_offset + line_thickness /2); // Position
g.setStroke(new BasicStroke(line_thickness)); // Épaisseur
g.drawLine(tx, ly, tx+tw, ly);
Le style barré utilise le même principe en exploitant les informations retournées par les méthodes getStrikethroughOffset()
et getStrikethroughThickness()
.
Dans le code précédent, il suffit de remplacer les valeurs affectées aux variables line_offset
et line_thickness
:
float line_offset = lm.getStrikethroughOffset();
float line_thickness = lm.getStrikethroughThickness();
Les styles souligné et barré ne sont pas gérés directement par la classe java.awt.Font
car il s'agit d'éléments graphiques (lignes) séparés du texte, pouvant avoir une couleur différente du texte, ou une épaisseur différente de celle indiquée par la classe java.awt.font.LineMetrics
.
Exemple : Texte en dégradé de couleurs
modifierLa méthode de remplissage est représentée par une instance de la classe java.awt.Paint
.
L'instance par défaut remplit les formes avec une couleur de manière unie.
Il existe d'autres classes de remplissage dont celles permettant de peindre une forme en dégradé de couleurs.
Le tracé de texte est en fait un remplissage et il est dont possible d'utiliser un dégradé de couleurs.
Le code ci-dessous affiche une ligne de texte en dégradé de couleurs (voir image ci-dessus).
package org.wikibooks.fr.test;
import java.awt.*;
import java.util.*;
import javax.swing.*;
/**
* Test affichage en dégradé d'un texte.
* @author fr.wikibooks.org
*/
public class TestPaint extends JComponent
{
public static void main(String[] args)
{
JFrame f = new JFrame("Test de rendu avec Java Swing");
f.setSize(800, 300);
f.add(new TestPaint());
f.setVisible(true);
}
/** Police de caractères utilisée. */
private static final Font F_TEXTE = new Font("Dialog", Font.BOLD, 80);
/** Couleurs utilisées. */
private static final Color
C_BG = Color.BLACK,
// Orange -> Rouge
C_FG_TEXT_1 = new Color(220,190,0),
C_FG_TEXT_2 = new Color(250,50,0);
// Bleu -> Vert
// C_FG_TEXT_1 = new Color(40,120,240),
// C_FG_TEXT_2 = new Color(80,210,170);
/** Options pour améliorer l'affichage. */
private HashMap<RenderingHints.Key, Object> hm_hints = new HashMap<RenderingHints.Key, Object>();
private TestPaint()
{
hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
setBackground(C_BG);
}
@Override
protected void paintComponent(Graphics gg)
{
Graphics2D g = (Graphics2D) gg;
g.setRenderingHints(hm_hints);
// Selon les dimensions du composant
Dimension d = getSize();
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
g.setFont(F_TEXTE);
FontMetrics fm = g.getFontMetrics();
// Hauteurs des caractères (au dessus et en dessous de la ligne de base)
int ha = fm.getMaxAscent(), hb = fm.getMaxDescent();
// Texte à afficher :
String s = "fr.wikibooks.org";
// Largeur du texte, et calcul de la position centrée du texte
int ws = fm.stringWidth(s),
x = (d.width-ws)/2, y = (d.height+ha-hb)/2;
// Dégradé linéaire vertical débutant à 5 pixels du haut et se terminant à 5 pixels du bas du texte.
GradientPaint gp = new GradientPaint(new Point(x,y-ha+5), C_FG_TEXT_1, new Point(x,y+hb-5), C_FG_TEXT_2);
Paint p = g.getPaint(); // Pour restaurer
g.setPaint(gp);
g.drawString(s, x, y);
g.setPaint(p); // Restauration
}
}
Utiliser la forme des caractères
modifierPour un affichage de texte plus évolué, il est possible d'exploiter la forme des caractères.
La forme des caractères est représentée par une instance de la classe java.awt.font.GlyphVector
.
Cette classe possède des méthodes retournant des instances de la classe java.awt.Shape
qui peuvent donc être manipulées, et notamment utilisées avec les méthodes draw
et fill
du contexte graphique.
Le texte tel que montré par l'image ci-dessus est dessiné par le code ci-dessous et reprend la première partie du code de la section précédente, en remplaçant l'affichage du texte par la récupération de la forme des caractères.
Celle-ci est obtenue en appelant la méthode createGlyphVector
de la police de caractère, en lui passant le contexte de rendu et le texte.
Cette forme des caractères est ensuite utilisée :
- pour obtenir les rectangles autour du caractère w (en position 3),
- pour tracer le contour des glyphes du texte en pointillé.
package org.wikibooks.fr.test;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.util.*;
import javax.swing.*;
/**
* Test affichage en dégradé d'un texte.
* @author fr.wikibooks.org
*/
public class TestPaint extends JComponent
{
public static void main(String[] args)
{
JFrame f = new JFrame("Test de rendu avec Java Swing");
f.setSize(800, 300);
f.add(new TestPaint());
f.setVisible(true);
}
/** Police de caractères utilisée. */
private static final Font F_TEXTE = new Font("Dialog", Font.BOLD, 80);
/** Couleurs utilisées. */
private static final Color
C_BG = Color.BLACK,
C_LIGNE = new Color(210,160,40),
C_RECT_1 = new Color(20,120,240),
C_RECT_2 = new Color(0,64,128),
C_TEXTE = Color.WHITE;
/** Options pour améliorer l'affichage. */
private HashMap<RenderingHints.Key, Object> hm_hints = new HashMap<RenderingHints.Key, Object>();
private TestPaint()
{
hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
setBackground(C_BG);
}
@Override
protected void paintComponent(Graphics gg)
{
Graphics2D g = (Graphics2D) gg;
g.setRenderingHints(hm_hints);
// Selon les dimensions du composant
Dimension d = getSize();
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
g.setFont(F_TEXTE);
FontMetrics fm = g.getFontMetrics();
// Hauteurs des caractères (au dessus et en dessous de la ligne de base)
int ha = fm.getMaxAscent(), hb = fm.getMaxDescent();
// Texte à afficher :
String s = "fr.wikibooks.org";
// Largeur du texte, et calcul de la position centrée du texte
int ws = fm.stringWidth(s),
x = (d.width-ws)/2, y = (d.height+ha-hb)/2;
// Ensemble de glyphes du texte à partir de la police de caractères
GlyphVector gv = F_TEXTE.createGlyphVector(g.getFontRenderContext(), s);
Shape
sh_title = gv.getOutline(), // Contour des caractères
// Caractère en position 3 (w) :
sh_w_ext = gv.getGlyphLogicalBounds(3), // - Rectangle de toute la zone du caractère
sh_w = gv.getGlyphVisualBounds(3); // - Rectangle resserré sur le caractère
g.setColor(C_LIGNE);
g.fillRect(x-100, y-14, ws+200, 16);
AffineTransform at = g.getTransform();
g.translate(x, y);
g.setColor(C_RECT_1);
g.fill(sh_w_ext);
g.setColor(C_RECT_2);
g.fill(sh_w);
// Tracé en pointillé, épaisseur 3 pixels
Stroke sk = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 5.0f, new float[]{ 10f, 5f }, 5f);
Stroke st = g.getStroke();
g.setStroke(sk);
g.setColor(C_TEXTE);
g.draw(sh_title);
g.setStroke(st); // Restaurer
g.setTransform(at); // Restaurer
// Équivalent de g.drawString :
//g.drawGlyphVector(gv, x, y);
}
}
Créer une image
Une application peut avoir besoin de créer une image, soit pour réutiliser cette image et l'afficher plusieurs fois en évitant de tout redessiner à nouveau, soit pour créer un fichier externe utilisable dans une autre application.
La création d'une image peut se faire aussi simplement que le dessin d'un composant, en utilisant un contexte graphique.
Créer une image
modifierLa classe java.awt.image.BufferedImage
permet de créer une nouvelle image.
Le type d'image et sa taille en pixels doit être spécifié au constructeur.
Exemple :
// Image 200x100 pixels
// Couleurs RVB sur 8 bits regroupés en pixels de type entier (int) :
BufferedImage b_image = new BufferedImage(200,100, BufferedImage.TYPE_INT_RGB);
Dessiner le contenu
modifierLe contenu est ensuite dessiné en obtenant un contexte graphique, comme celui utilisé pour dessiner un composant :
Graphics2D g = b_image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0,0,200,100);
g.setColor(Color.BLUE);
g.drawRect(100,50,50,25);
g.dispose(); // Libérer le contexte après utilisation
Comme le contexte graphique est de la même classe que celui utilisé pour le dessin d'un composant, les mêmes méthodes s'appliquent, donc notamment la méthode addRenderingHints
pour améliorer la qualité de l'image.
Enregistrer l'image
modifierEnregistrer l'image se fait en utilisant la classe javax.imageio.ImageIO
, en appelant la méthode statique write
et en lui passant l'image, le nom du format d'image, et le chemin du fichier (de type java.io.File
):
ImageIO.write(b_image, "png", new File("D:\Temp\image.png"));
Capture d'écran
modifierLa classe java.awt.Robot
permet d'automatiser certaines actions de l'utilisateur (déplacer le curseur de souris, générer des touches, ...) et de réaliser des captures d'écrans.
Son but principal est de pouvoir tester des applications automatiquement.
La classe possède deux constructeurs :
Robot()
- Constructeur d'instance pour l'écran principal.
Robot(GraphicsDevice device)
- Constructeur d'instance pour l'écran spécifié.
Ensuite, il suffit d'appeler la méthode createScreenCapture(Rectangle bounds)
pour créer une image de capture de la zone spécifiée de l'écran.
L'image retournée est de type java.awt.image.BufferedImage
.
Le code suivant réalise une capture de l'écran principal :
Robot robot = new Robot();
MediaTracker mt = new MediaTracker(this); // Argument de type Component,
// this ok si le code est dans une classe de type composant ou fenêtre.
// Dimension(width, height) --> Rectangle(0, 0, width, height)
Rectangle tout_l_ecran = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
BufferedImage image_capture = robot.createScreenCapture(tout_l_ecran);
mt.addImage(image_capture, 0); // Ajouter l'image à attendre
mt.waitForAll(); // Attendre que l'image soit prête
La capture est réalisée de manière asynchrone, ce qui explique l'utilisation de la classe java.awt.MediaTracker
dans l'exemple précédent pour attendre que l'image soit prête à être utilisée.
GFDL | Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture. |