Swing est la boîte à outils d'interface utilisateur de Java. Il a été développé durant l'existence de Java 1.1 et fait désormais partie des API centrales de Java 1.2 et supérieure.
Swing fournit des classes pour représenter des éléments d'interface comme les fenêtres, des boutons, des boîtes combo, des arborescences, des grilles et des menus - toute chose nécessaire à la construction d'une interface utilisateur dans une application Java. Pour cela, vous disposez du paquetage javax.swing (ainsi que ses nombreux sous-paquetages).
Swing fait partie d'une collection plus vaste de logiciels baptisée JFC (Java Foundation Classes). JFC contient les API suivantes :
JFC est la partie la plus volumineuse et la plus compliquée de la plate-forme standard Java.
.
Pour comprendre Swing, il est utile de comprendre son prédécesseur AWT (Abstract Window Toolkit) issu du paquetage java.awt. Comme son nom l'indique, AWT est une abstraction. Comme le reste de java, il a été conçu pour être portable ; ses fonctionnalités sont les mêmes pour toutes les implémentaitons Java. Bien que les gens s'attendent généralement à ce que leur applications aient un look-and-feel cohérent, il est souvent différent d'une plateforme à l'autre. C'est pourquoi, AWT a été conçu pour fonctionner de la même façon sur toutes les plate-formes, avec l'aspect du système natif.
Vous pouvez choisir d'écrire votre code sous Windows, puis l'exécuter sur un système Linux ou un Macintosh. Pour parvenir à une indépendance de plate-forme, AWT utilise des boîtes à outil interchangeables qui interagissent avec le système de fenêtrage de l'hôte pour afficher les composants de l'interface utilisateur.
Les détails de l'environnement d'exécution sont ainsi cachés à votre application.
.
Supposons que nous demandions à AWT de créer un bouton. Lorsque votre application ou une applet s'exécute, une boîte à outils adaptée à l'environnement hôte affiche le bouton : Sur Windows, nous obtenons un bouton ressemblant aux autres boutons de Windows, sur Linux, nous obtenons un bouton Linux, etc.
L'approche fondée sur AWT fonctionnait bien pour les applications simples, mais il est rapidement devenu évident qu'il était très difficile d'écrire une bibliothèque graphique portable de haute qualité qui dépendait des éléments d'une interface utilisateur native. L'interface utilisateur comme les menus, les barres de défilement et les champs de texte peuvent avoir des différences suptiles de comportement sur différentes plates-formes. Il était donc difficile d'offrir aux utilisateurs une expérience cohérente et prévisible. De plus, certain environnements graphiques (comme X11/Motif) ne dispose pas d'une large collection de composants d'interface utilisateur comme l'ont Windows ou Macintosh. Ceci restreint ensuite une bibliothèque portable fondée sur l'approche du plus petit dénominateur commun. En conséquence, les applications GUI élaborées avec AWT n'étaient pas aussi jolies que les applications Windows ou Macintosh natives et n'avaient pas non plus le type de fonctionnalité que les utilisateurs attendaient.
Les éléments d'interface utilisateur, que nous appelons également composants, sont de simples classes issues du paquetage java.awt. Nous trouvons ainsi la classe Frame correspondant au cadre de la fenêtre, la classe Button correspondant à un bouton, la classe TextField correspondant à une zone de saisie, etc.
Comme nous allons le voir, il sera préférable de choisir l'interface utilisateur Swing. Toutefois, nous utiliserons certains des éléments d'AWT comme la classe Color et la gestion des événements, ainsi que quelques autres.
Swing suit une approche fondamentalement différente. Au lieu d'utiliser des boîtes à outils natives pour fournir des éléments d'interface comme des boutons et des boîtes combo, les composants de Swing sont implémentés directement dans Java. Cela signifie que quelle que soit la plateforme utilisée, un bouton Swing a toujours la même apparence.
Quoiqu'il en soit, Swing fournit également une API puissante de look-and-feel modifiable, qui permet de remplacer l'apparence du système d'exploitation natif au niveau de Java.
Comme il ne travaille qu'en Java, Swing n'est plus sujet aux bogues de plate-forme comme l'était AWT. Cela signifie également que les composants Swing sont beaucoup plus souples et peuvent être complétés et modifiés dans vos applications. Par ailleurs, Swing propose un ensemble d'éléments d'interface plus étendu et pratique.
La plupart des classes de composant Swing commencent par la lettre J : JButton, JFrame, etc. Ce sont des classes comme Button et Frame, mais il s'agit dans ce dernier cas, comme nous l'avons vu, de composants AWT. Pour retrouver ces composants Swing, vous devez importer le paquetage javax.swing. En réalité JFrame hérite de Frame alors que les autres composants comme JButton, JLabel, JTextField, JMenu, etc. héritent tous de la classe de base JComponent qui fait parti de Swing, qui hérite lui-même indirectement de Component qui est un élément de AWT.
La manipulation des composants d'une interface utilisateur est aisée avec Swing. Lors de la construction d'une interface utilisateur pour votre application, vous travaillerez avec des éléments préfabriqués. Il est facile d'assembler une collection de composants d'interface utilisateur (bouton, zone de texte, etc.) et de les disposer dans des conteneurs afin de construire des placements complexes. Vous pouvez également utiliser des composants simples comme des briques, pour créer entièrement des nouveaux types de gadgets d'interface complètement portables et réutilisables.
Swing utilise des gestionnaires de placement (appelé aussi gestionnaire de disposition) pour disposer des composants à l'intérieur de conteneurs en contrôlant leur taille et leur position. Les gestionnaires de placement définissent une stratégie de disposition des composants au lieu de spécifier des positions absolues. Par exemple, vous pouvez définir une interface utilisateur avec plusieurs boutons et zones de texte et supposer raisonnablement qu'ils s'affichent toujours correctement, même si l'utilisateur redimensionne la fenêtre de l'application. Peu importe la plate-forme ou l'apparence de l'interface utilisateur utilisé ; le gestionnaire de placement les conditionnera toujours intelligemment les uns par rapport aux autres.
Plus loin dans cette étude, nous utilisons la méthode setLayout() pour choisir la disposition qui vous intéresse. Pour en savoir plus sur les gestionnaires de disposition, reportez-vous à l'étude correspondante.
Un composant est l'objet fondamental d'une interface utilisateur sous Java. Tout ce que nous voyons sur l'écran d'une application Java est un composant. Ceci comprend des fenêtres, des boutons, des cases à cocher, des barres de défilement, des listes, des menus, des champs de saisie, etc.
Pour être utilisé, un composant doit généralement être placé dans un conteneur. Les objets conteneurs regroupent des composants, les disposent pour les afficher dans un gestionnaire de placement et les associent à un périphérique d'affichage particulier. Tous les composants Swing, sauf pour les conteneurs fenêtre, dérivent de la classe abstraite javax.swing.JComponent. Par exemple, la classe JButton est une classe fille de la classe AbstractButton, elle-même classe fille de la classe JComponent.
JComponent est la racine de l'arborescence de composant de Swing, mais elle descend de la classe Container d'AWT. A ce niveau, Swing est basé sur AWT, nous rentrerons donc parfois dans les détails du paquetage AWT. La classe mère de Container est Component, la racine de tous les composants AWT, et la classe mère de Component est, pour terminer, Object.
JComponent héritant de Container, possède à la fois les capacités d'un composant et d'un conteneur.
.
AWT et Swing, dès lors, possèdent des hiérarchies parallèles. La racine de la hiérarchie de AWT est Component, alors que les composants de Swing sont basés sur JComponent. Vous trouverez des classes similaires dans les deux hiérarchies, par exemple, Button et JButton.. Mais Swing est beaucoup plus qu'un simple remplacement de AWT - il contient des composants sophistiqués, comme List et JList ainsi qu'une véritable implémentation du paradigme MVC (Modèle-Vue-Contrôleur).
La classe JComponent contient des méthodes et des attributs qui contrôlent l'apparence générale d'un objet. Cela comprend des attributs de base comme sa visibilité, sa taille et son emplacement courant, et certaines valeurs par défaut graphiques communes, comme la police et la couleur. La classe JComponent contient également des méthodes implémentées par des classes filles particulières pour produire les affichages spécifiques.
Lorsqu'un composant est affiché la première fois, il est associé à un périphérique de message particulier. La classe JComponent encapsule l'accès à cette zone d'affichage sur ce périphérique. Elle comporte des outils permettant à la fois d'accéder à des graphismes, de travailler avec des ressources et de recevoir des données utilisateur.
Par "comportement d'un composant", nous entendons sa façon de répondre aux événements controlés par l'utilisateur. Lorsque l'utilisateur effectue une action (comme presser le bouton de la souris) à l'intérieur de la zone d'affichage d'un composant, un thread Swing délivre un objet événement qui décrit "ce qui s'est passé". L'événement est délivré aux objets qui sont enregistrés en tant que "listener" (écouteur) de ce type d'événement à partir de ce composant.
Par exemple, lorsque l'utilisateur clique sur un panneau placé sur la surface utile de la fenêtre, celui-ci délivre un objet MouseEvent. Pour recevoir ces événements, un objet s'enregistre avec le bouton en tant que MouseListener. Des événements sont délivrés en invoquant les méthodes de gestionnaire d'événement désignées à l'intérieur de l'objet récepteur (listener). Un objet récepteur se prépare à recevoir des événements en implémentant des méthodes, comme mouseClicked(), pour les types d'événement qui l'intéressent.
Des types spécifiques d'événement concernent les différentes catégories d'interactions de composant utilisateur. Par exemple, MouseEvent décrit les activités de la souris dans une zone de composants alors que KeyEvent décrit des appuis de touches. Des événements de haut niveau, comme ActionEvent, indiquent la validation d'une saisie ou d'une sélection.
L'architecture d'événement de swing est très souple. Au lieu de demander à chaque composant de guetter et de gérer les événements pour sa propre part de l'interface utilisateur, une application peut enregistrer des objets gestionnaires d'événements afin de recevoir les événements pour un ou plusieurs composants et les coller à la logique de l'application. Un conteneur, doit par exemple, gérer les événements relatifs à ses composants enfants.
Pour tout connaître sur la gestion des événements, reportez-vous à l'étude suivante : Les événements.
.
Un conteneur est responsable du placement de ses composants. Un composant informe son conteneur lorsqu'il effectue une chose pouvant affecter d'autres composants du conteneur, par exemple, lorsqu'il change de taille ou de visiblité. Le conteneur indique alors à son gestionnaire de placement qu'il doit redisposer les composants enfants.
Comme vous l'avez vu, les composants Swing sont également des conteneurs. Les conteneurs peuvent gérer et disposer des objets JComponent sans les connaître et sans savoir ce qu'ils font. Des composants peuvent être permutés et remplacés par de nouvelles versions et combinés en objets composites d'interface utilisateur qui peuvent être traités comme des composants individuels. Tout cela se prète très bien à la construction d'éléments d'interface utilisateur plus vaste et réutilisable. C'est tout la richesse de la programmation objet avec notamment la notion de polymorphisme.
Dans un environnement de programmation événementielle tel que Swing, les composants peuvent être appelés à se redessiner à tout moment. Sous Java, les composants agissent d'une façon étroitement lié au comportement sous-jacent de l'environnement d'affichage. Par exemple, lorsque nous cachons un composant sous une autre fenêtre puis la remontrons, un thread Swing lui demande de se redessiner.
Swing demande à un composant de se redessiner en appelant sa méthode paint(). paint() peut être appelée n'importe quand, mais en pratique, elle l'est lorsque l'objet est rendu visible pour la première fois, lorsque son apparence change et à chaque fois qu'un événement dans le système d'affichage perturbe son espace. paint(), incapable de faire la moindre supposition sur ce qui se passe, doit redessiner entièrement l'objet. Le système peut limiter le tracé lorsque seule une partie du composant doit être redessinée, mais vous n'avez pas en vous en soucier.
Un composant n'appelle jamais directement sa méthode paint() : lorsqu'il a besoin d'être redessiné, il sollicite un appel à paint() en invoquant la méthode repaint(). La méthode repaint() demande à Swing de prévoir alors un nouveau tracé du composant.
Un appel à paint() se produira très rapidement. Swing peut gérer ces requêtes de la façon qui lui paraît la plus efficace. En cas de requêtes trop nombreuses ou de plusieurs requêtes pour un même composant, le thread peut alors reprogrammer plusieurs requêtes de dessin dans un seul appel à paint(). Nous ne pouvons donc pas prévoir l'instant exact où paint() sera appelée en réponse à une repaint(), mais seulement que cela se produira au moins une fois après la demande.
L'appel à repaint() est normalement une demande explicite de mise à jour dès que possible. Une autre forme de repaint() vous permet de spécifier un intervalle de temps, offrant ainsi plus de souplesse dans la planification de la requête. Le système essaye de repeindre le composant pendant l'intervalle de temps spécifié, et si vous faites plus d'une demande durant le même intervalle de temps, le système se contente de les regrouper afin de n'effectuer qu'une seule mise à jour. Une application contenant de l'animation pourrait utiliser cette méthode pour contrôler son taux de rafraîchissement (en spécifiant ainsi un intervalle de temps inverse de la fréquence souhaitée).
Les composants Swing peuvent agir comme des conteneurs, contenant d'autres composants. Comme ils se dessinent eux-mêmes, ils doivent indiquer aux composants contenus de se redessiner eux-mêmes également. Par chance, et par défaut, tout cela est assuré pour vous automatiquement par la méthode paint(). Toutefois, si vous redessinez cette méthode, prenez soin d'appeler l'implémentation de la classe mère, comme ceci :
public void paint(Graphics surface) { super.paint(surface); ... }
Il existe une autre méthode autour de cette question. En effet, tous les composants Swing possèdent une méthode paintComponent(). Tandis que paint() a la responsabilité de dessiner le composant et ceux qu'il contient, paintComponent() n'a que l'obligation de se redessiner lui-même. Si vous redessiner paintComponent() au lieu de paint(), vous n'avez pas à vous préoccuper de dessiner les composants contenus.
paint() et paintComponent() ne prennent chacun qu'un argument : un objet Graphics, qui représente le contexte graphique du composant. Il correspond à l'espace de l'écran sur lequel le comosant peur dessiner et fournit les méthodes de tracé de base et de manipulation d'images.
Ainsi, lorsque vous avez un affichage particulier à produire, il est judicieux de créer un nouveau composant graphique en héritant de JComponent tout en redéfinissant la méthode paintComponent(). Voici ce que nous pouvons faire, par exemple, pour créer une application qui affiche une image dans la zone active de la fenêtre :
package dessin; import java.awt.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; public class Fenêtre extends JFrame { public Fenêtre() throws IOException { this.setDefaultCloseOperation(this.EXIT_ON_CLOSE); this.setSize(358, 260); this.setTitle("Voir image"); this.getContentPane().setBackground(Color.ORANGE); this.add(new Zone()); } public static void main(String[] args) throws IOException { new Fenêtre().setVisible(true); } } class Zone extends JComponent { private BufferedImage image; public Zone() throws IOException { image = ImageIO.read(new File("chouette.jpg")); } public Zone(BufferedImage image) { this.image = image; } protected void paintComponent(Graphics surface) { surface.drawImage(image, 0, 0, null); } }
Graphics comporte quelques méthodes permettant de dessiner et remplir des formes courantes :
Méthode | Description |
---|---|
drawArc(int x, int y, int largeur, int hauteur, int angledébut, int anglefin) |
Dessine un arc de cercle (angle en degré) |
drawLine(int xdébut, int ydébut, int xfin, int yfin) |
Dessine une ligne |
drawOval(int x, int y, int largeur, int hauteur) | Dessine un ovale |
drawPolygon(int[] lesX, int[] lesY, int nombrePoint) | Dessine un polygone et le ferme en joignant les extrémités |
drawPolyline(int[] lesX, int[] lesY, int nombrePoint) | Dessine une ligne en reliant une série de points, sans la fermer |
drawRect(int x, int y, int largeur, int hauteur) | Dessine un rectangle |
drawRoundRect(int x, int y, int largeur, int hauteur, int largeurArc, int hauteurArc) | Dessine un rectangle à coins arrondis |
fillArc(int x, int y, int largeur, int hauteur, int angledébut, int anglefin) | Dessine un arc de cercle plein |
fillOval(int x, int y, int largeur, int hauteur) | Dessine un ovale plein |
fillPolygon(int[] lesX, int[] lesY, int nombrePoint) | Dessine un polygone plein |
fillRect(int x, int y, int largeur, int hauteur) | Dessine un rectangle plein |
fillRoundRect(int x, int y, int largeur, int hauteur, int largeurArc, int hauteurArc) | Dessine un rectangle plein à coins arrondis |
Pour chacune des méthodes fill() du tableau, il existe une méthode draw() correspondante créant la forme selon un dessin non plein. A l'exception de fillArc() et de fillPolygon(), chaque méthode prend une simple indication <x, y> correspondant au coin supérieur gauche de la forme, ainsi qu'une largeur width et une hauteur height.
La méthode la plus souple dessine un polygone, défini par deux tableaux de coordonnées des sommets, d'une part le tableau des x, et d'autre part le tableau des y suivi ensuite du nombre de point. Des méthodes de la classe Graphics lisent ces tableaux et dessinent le contour du polygone ou remplissent celui-ci.
La méthode fillArc() requiert six arguments entiers. Les quatre premiers définissent le rectangle englobant un ovale - comme la méthode fillOval(). Les deux derniers définissent la portion de l'ovale à dessiner, sous forme de position angulaire de départ et de déplacement (tous deux en degrés). La position zéro degré se trouve à trois heures ; l'angle croît dans le sens des aiguilles d'une montre.
Fenêtre.java |
---|
package dessin; import java.awt.*; import javax.swing.*; public class Fenêtre extends JFrame { public Fenêtre() { this.setDefaultCloseOperation(this.EXIT_ON_CLOSE); this.setSize(300, 250); this.setTitle("Test dessins"); this.getContentPane().setBackground(Color.ORANGE); this.getContentPane().add(new Zone()); } public static void main(String[] args) { new Fenêtre().setVisible(true); } } class Zone extends JComponent { protected void paintComponent(Graphics g) { g.drawArc(-40, 10, 100, 100, 0, 90); g.drawLine(10, 10, 10, 60); g.drawLine(10, 60, 60, 60); g.drawOval(10, 80, 120, 120); g.drawPolygon(new int[] {110, 170, 170}, new int[] {60, 60, 10}, 3); g.drawPolyline(new int[] {210, 270, 270}, new int[] {60, 60, 10}, 3); g.drawRect(10, 80, 120, 120); g.drawRoundRect(150, 80, 120, 120, 10, 10); } } |
Il est possible d'avoir des tracés très sophistiqués, de proposer également des motifs, des dégradés, etc. Reportez-vous pour cela sur l'étude du graphisme 2D.
.
Nous venons de voir que tous les composants de Swing, sauf pour les conteneurs fenêtres, héritent de JComponent. Ce dernier est prépondérant puisqu'il assure un certain nombre de services associés qui sont accessibles au travers de méthodes prévues à cet effet.
Un conteneur est une sorte de composant qui contient et gouverne d'autres composants. Des objets JComponent peuvent être des conteneurs, car la classe JComponent descend de la classe Container. Toutefois, vous ne devriez pas ajouter directement des composants à des composants spécialisés comme les boutons ou les listes.
Les trois types de conteneurs les plus utilisés sont : JFrame, JPanel et JApplet.
A l'exception de JFrame et de JWindow, tous les composants et conteneurs Swing sont légers (écrits en Java).
.
Un conteneur entretient la liste des composants enfants qu'il gère et possède des méthodes pour les traiter. Notons que cette relation familiale fait référence à une hiérarchie visuelle, non à une hiérarchie <classe mère / classe fille>. Par eux-mêmes, la plupart des composants ne sont pas très utiles tant qu'ils ne sont pas intégrés à un conteneur puis affichés. La méthode add() de la classe Container permet d'ajouter un composant au conteneur ; lequel composant peut ensuite être affiché dans la zone d'affichage du conteneur et positionné par son gestionnaire. La méthode remove() permet d'enlever un composant du conteneur alors que removeAll() supprime l'ensemble des composants déjà intégrés dans le conteneur.
package dessin; import java.awt.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; public class Fenêtre extends JFrame { public Fenêtre() throws IOException { this.setDefaultCloseOperation(this.EXIT_ON_CLOSE); this.setSize(358, 260); this.setTitle("Voir image"); this.getContentPane().setBackground(Color.ORANGE); this.add(new Zone()); <-- ajout du composant image } public static void main(String[] args) throws IOException { new Fenêtre().setVisible(true); } } class Zone extends JComponent { ... }
Un gestionnaire de placement est un objet contrôlant le placement et le dimensionnement des composants situés à l'intérieur de la zone d'affichage d'un conteneur. Il ressemble au gestionnaire de fenêtres d'un système d'affichage ; il gouverne l'emplacement et la taille des composants. Chaque conteneur possède un gestionnaire de placement par défaut, mais il est possible d'en installer un nouveau en appelant sa méthode setLayout().
Swing possède plusieurs gestionnaires de placement qui implémentent des modèles de disposition courants :
package gestionnaire; import javax.swing.*; public class Fenêtre extends JFrame { private JButton nord = new JButton("Nord"); private JButton ouest = new JButton("Ouest"); private JButton sud = new JButton("Sud"); private JButton centre = new JButton("Centre"); private JButton est = new JButton("Est"); private JPanel panneau = new JPanel(); public Fenêtre() { setTitle("Une fenêtre"); setSize(300, 250); setDefaultCloseOperation(EXIT_ON_CLOSE); panneau.add(nord); panneau.add(ouest); panneau.add(sud); panneau.add(centre); panneau.add(est); add(panneau); setVisible(true); } public static void main(String[] args) { new Fenêtre(); } }
package gestionnaire; import java.awt.BorderLayout; import javax.swing.*; public class Fenêtre extends JFrame { private JButton nord = new JButton("Nord"); private JButton ouest = new JButton("Ouest"); private JButton sud = new JButton("Sud"); private JButton centre = new JButton("Centre"); private JButton est = new JButton("Est"); public Fenêtre() { setTitle("Une fenêtre"); setSize(300, 250); setDefaultCloseOperation(EXIT_ON_CLOSE); add(nord, BorderLayout.NORTH); add(ouest, BorderLayout.WEST); add(sud, BorderLayout.SOUTH); add(centre, BorderLayout.CENTER); add(est, BorderLayout.EAST); setVisible(true); } public static void main(String[] args) { new Fenêtre(); }
package gestionnaire; import java.awt.*; import javax.swing.*; public class Fenêtre extends JFrame { private JButton nord = new JButton("Nord"); private JButton ouest = new JButton("Ouest"); private JButton sud = new JButton("Sud"); private JButton centre = new JButton("Centre"); private JButton est = new JButton("Est"); public Fenêtre() { setTitle("Une fenêtre"); setSize(300, 250); setDefaultCloseOperation(EXIT_ON_CLOSE); setLayout(new GridLayout(3, 2)); add(nord); add(ouest); add(sud); add(centre); add(est); setVisible(true); } public static void main(String[] args) { new Fenêtre(); } }
Vous remarquez que dans ces différents codes sources, pour ajouter un composant à un conteneur qui utilise un simple gestionnaire de placement, comme FlowLayout, nous utilisons la version add() qui prend Component en argument unique. Mais avec un gestionnaire de placement utilisant des restrictions, comme le BorderLayout, il est alors nécessaire d'indiquer des informations supplémentaires concernant l'emplacement du nouveau composant, en faisant appel à la version qui prend un objet restrictif en argument, comme BorderLayout.NORTH.
Nous pouvons activer et désactiver les composants Swing standard en appelant la méthode setEnabled(). Lorsqu'un composant tel que JButton et JTextfield est désactivé, il devient fantôme ou grisé et ne répond plus aux entrées de l'utilisateur.
Nous pouvons, à titre d'exemple, prévoir un programme qui comporte deux boutons où seul un des deux est actif. Lorsque nous cliquons sur un bouton le deuxième s'active alors que le premier est grisé et vise versa.
1 package gestionnaire; 2 3 import java.awt.*; 4 import java.awt.event.*; 5 import javax.swing.*; 6 7 public class Fenêtre extends JFrame implements ActionListener { 8 private JButton premier = new JButton("Premier"); 9 private JButton deuxième = new JButton("Deuxième"); 10 11 public Fenêtre() { 12 setTitle("Activation"); 13 setSize(250, 150); 14 setDefaultCloseOperation(EXIT_ON_CLOSE); 15 setLayout(new FlowLayout()); 16 premier.addActionListener(this); 17 add(premier); 18 deuxième.addActionListener(this); 19 deuxième.setEnabled(false); 20 add(deuxième); 21 setVisible(true); 22 } 23 public static void main(String[] args) { 24 new Fenêtre(); 25 } 26 27 public void actionPerformed(ActionEvent e) { 28 premier.setEnabled(!premier.isEnabled()); 29 deuxième.setEnabled(!deuxième.isEnabled()); 30 } 31 }
Lorsque nous cliquons sur un bouton, un événement de type ActionEvent est alors généré. L'objet récepteur de cet événement, ici la fenêtre elle-même puisqu'elle implémente l'interface ActionListener (ligne 7), lance alors l'action correspondante décrite dans la méthode redéfinie actionPerformed() - (lignes 27 à 30). Chaque bouton doit s'enregistrer en tant que source d'événement auprès de l'objet récepteur, au travers de la méthode addActionListener() - (lignes 16 et 18).
Remarquez au passage qu'il est toujours possible de connaître l'activité d'un composant au moyen de la méthode isEnabled().
.
Nous pouvons également désactiver un conteneur entier : par exemple, le fait de désactiver un JPanel désactive tous les composants qu'il contient.
.
JComponent utilise beaucoup de méthodes qui s'occupe de la couleur, directement ou indirectement. Les couleurs sont définies à l'aide de la classe Color. La classe java.awt.Color propose des constantes prédéfinies pour les treize couleurs standard :
|
---|
Vous pouvez spécifier une couleur personnalisée en créant un objet Color à l'aide de ses composantes rouge, vert et bleue. En utilisant une échelle de 0 à 255 (c'est-à-dire un octet) pour les proportions de rouge, de vert et de bleu, appelez le constructeur correspondant de la classe Color :
Color(int rouge, int vert, int bleue);
Ainsi pour choisir une nouvelle couleur pour le texte d'un label, par exemple un bleu-vert foncé, voici comment nous pouvons procéder :
JLabel unTexte = new JLabel("J'ai une nouvelle couleur");
unTexte.setForeground(new Color(0, 128, 128));
Les méthodes brighter() et darker() de la classe Color produisent des versions plus vives ou plus foncées de la couleur actuelle. La méthode brighter() permet de mettre un élément en surbrillance, mais avive en réalité à peine la couleur. Pour obtenir une couleur plus visible, appelez plusieurs fois la méthode.
couleur.brighter().brighter().brighter();
Nous avons même la possibilité d'intégrer de la transparence dans la couleur proposée. La transparence est décrite par une couche alpha. Chaque pixel possède, en plus de ses composantes de rouge, de vert et de bleu, une valeur alpha variant entre 0 - transparence parfaite - et 255 - opacité (dans le cas où les paramètres sont de type int). Pour cela, il suffit d'utiliser le constructeur de la classe Color qui intègre la couche alpha :
Color(int rouge, int vert, int bleu, int alpha); // valeur des paramètres varie de 0 à 255
En reprenant l'exemple précédent :
JLabel unTexte = new JLabel("J'ai une nouvelle couleur avec un peu de transparence");
unTexte.setForeground(new Color(0, 128, 128, 64));
Il existe un deuxième contructeur, dont le fonctionnement est plus intuitif, qui prend cette fois-ci des float en paramètres. Dans ce cas là, il faut spécifier la valeur de chacune des couches par un pourcentage :
Color(float rouge, float vert, float bleu, float alpha); // valeur des paramètres varie de 0F à 1F
En reprenant l'exemple précédent :
JLabel unTexte = new JLabel("J'ai une nouvelle couleur avec un peu de transparence");
unTexte.setForeground(new Color(0F, 0.5F, 0.5F, 0.25F));
La classe JComponent est très étendue ; elle doit fournir les fonctionnalités de bas niveau des divers types d'onjets GUI (Graphic Unit Interface) de Java. Elle hérite de nombreuses fonctionnalités de ses classes parentes Container et Composant. La place nous manque ici pour documenter chaque méthode de la classe JComponent, mais nous pouvons étoffer notre étude en décrivant certaines des plus importantes :
Tout composant Swing peut avoir une bordure décorative. JComponent inclut une méthode appelée setBorder() où vous spécifiez la classe correspondante à l'implémentation de l'interface Border.
Swing offre de nombreuses implémentations utiles de Border avec le paquetage javax.swing.border. Vous pouvez ainsi créer une instance de l'une de ces classes et la passer à la méthode setBorder() d'un composant, mais il existe une technique encore plus simple. La classe BorderFactory sait créer tout type de bordure en utilisant des méthodes "d'usine". Dès lors, la création et l'installation des bordures d'un composant sont extrêmement simples :
JLabel unTexte = new JLabel("J'ai une bordure gravée.");
unTexte.setBorder(BorderFactory.createEtchedBorder());
Les méthodes de la classe BorderFactory sont les suivantes :
package gestionnaire; import java.awt.FlowLayout; import javax.swing.*; public class Fenêtre extends JFrame { private JLabel bordure = new JLabel("J'ai une bordure en relief incrusté..."); public Fenêtre() { setLayout(new FlowLayout()); setTitle("Les bordures"); setSize(250, 70); setDefaultCloseOperation(EXIT_ON_CLOSE); bordure.setBorder(BorderFactory.createLoweredBevelBorder()); add(bordure); setVisible(true); } public static void main(String[] args) { new Fenêtre(); } }
Border gravée = BorderFactory.createEtchedBorder(); bordure.setBorder(BorderFactory.createTitledBorder(gravée, "Un titre", TitledBorder.TRAILING, TitledBorder.BOTTOM));void createTitledBorder(Border bordure, String leTitre, int justification, String position, Font police); // permet de choisir la police du titre
Border incrustée = BorderFactory.createLoweredBevelBorder(); Border espace = BorderFactory.createEmptyBorder(5, 5, 5, 5); Border saillie = BorderFactory.createRaisedBevelBorder(); bordure.setBorder(BorderFactory.createCompoundBorder(incrustée, BorderFactory.createCompoundBorder(espace, saillie)));
Chaque composant graphique possède ainsi une méthode setBorder(), des simples labels jusqu'aux composants texte puisqu'ils héritent tous de la classe JComponent.
Comme nous l'avons déjà évoqué, au lieu de prendre un BorderFactory, vous avez aussi la possibilité de créer un objet en passant par les classes correspondantes qui sont définies ci-dessous. Elles possèdent les mêmes arguments au niveau de la construction :
Voici un petit exemple qui permet de comprendre l'utilité de quelques unes de ces méthodes :
package gestionnaire; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; public class Fenêtre extends JFrame implements ActionListener { private JButton majuscule = new JButton("Majuscule"); private JTextField saisie = new JTextField("Votre texte à saisir"); private JLabel résultat = new JLabel(" "); private JPanel panneau = new JPanel(); public Fenêtre() { setTitle("Majuscule"); setSize(390, 100); setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().setBackground(Color.BLUE); panneau.setLayout(new BorderLayout()); majuscule.addActionListener(this); majuscule.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); majuscule.setPreferredSize(new Dimension(100, 26)); Border espace = new EmptyBorder(3, 5, 3, 5); saisie.setFont(new Font("Verdana", Font.BOLD, 12)); saisie.setBorder(new CompoundBorder(new BevelBorder(BevelBorder.LOWERED), espace)); résultat.setForeground(Color.WHITE); résultat.setBorder(new CompoundBorder(new EtchedBorder(EtchedBorder.LOWERED), espace)); panneau.add(saisie); panneau.add(majuscule, BorderLayout.EAST); add(panneau, BorderLayout.NORTH); add(résultat, BorderLayout.SOUTH); setVisible(true); } public static void main(String[] args) { new Fenêtre(); } public void actionPerformed(ActionEvent e) { résultat.setText(saisie.getText().toUpperCase()); } }
Un gestionnaire de placement ne dispose les composants d'un conteneur que lorsqu'il est sollicité. Plusieurs événements peuvent perturber un conteneur, après sa disposition initiale :
N'importe laquelle de ces actions fait que le conteneur est marqué invalide. Ceci signifie que ses composants enfants doivent être réajustés par son gestionnaire de placement. Dans la plupart des cas, Swing réajuste automatiquement l'affichage. Tous les composants, pas seulement les conteneurs, maintiennent une notion de validité ou d'invalidité. Si la taille, l'emplacement ou le placement interne d'un composant Swing change, la méthode revalidate() appelle d'abord la méthode validate() pour marquer le composant et tous ces composants comme invalide. La validation parcourt la hiérarchie, en commençant par le conteneur le plus proche de la racine de validation, et valide de manière récursive chacun des enfants. Valider un conteneur enfant signifie appeler sa méthode doLayout(), qui demande au gestionnaire de placement de faire son travail puis note que le Conteneur a été réorganisé en passant de nouveau son état à valide. Une racine de validation est un conteneur qui peut gérer des enfants de toute taille comme JScrollPane.
Il existe peu de cas où l'on demande à Swing de régler les choses manuellement au travers de cette méthode revalidate(). Dans certaines situations toutefois, lorsque vous proposez des changements d'apparence dynamique (en cours de programme), cette méthode nous sauve souvent la mise. Voici quelques exemples qui vous permettra de vous rendre compte de son utilité :
La méthode revalidate() appartient à la classe JComponent. Elle ne redimensionne pas immédiatement le composant, elle le signale simplement pour un redimensionnement ultérieur. Cette approche évite des calculs répétitifs lorsqu'il faut redimensionner plusieurs composants. Toutefois, si vous souhaitez que cette validité soit prise en compte, effectuez éventuellement un appel à la méthode repaint() du composant concerné. Pour finir, si vous désirez que toute la fenêtre soit remise à jour, afin de recalculer tous les composants dans un JFrame, vous devez appeler la méthode validate() (JFrame n'étend pas JComponent).
package calendrier; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; public class Calendrier extends JFrame { private static enum JourSemaine {Dim, Lun, Mar, Mer, Jeu, Ven, Sam}; private Semaine semaine = new Semaine(); private Mois jourMois = new Mois(); private Sélection sélection = new Sélection(); public Calendrier() { super("Calendrier"); add(semaine, BorderLayout.NORTH); add(jourMois); add(sélection, BorderLayout.SOUTH); pack(); setDefaultCloseOperation(EXIT_ON_CLOSE); setResizable(false); setVisible(true); } public static void main(String[] args) { new Calendrier(); } private class Semaine extends JPanel { Semaine() { setLayout(new GridLayout(1, 0)); setBackground(Color.PINK); for (JourSemaine jour : JourSemaine.values()) add(new Jour(jour)); } } private class Jour extends JLabel { Jour(JourSemaine jour) { super(jour.name()); init(); } Jour(int i) { super(""+i); init(); } private void init() { setHorizontalAlignment(CENTER); setBorder(BorderFactory.createEtchedBorder()); setPreferredSize(new Dimension(37, 20)); } } private class Mois extends JComponent { Calendar aujourdhui = Calendar.getInstance(); Calendar calendrier = Calendar.getInstance(); int jour = calendrier.get(Calendar.DAY_OF_MONTH); int mois; int année; int premierJourDuMois; int nombreJour; Mois() { init(); setLayout(new GridLayout(0, 7)); } private void init() { removeAll(); mois = calendrier.get(Calendar.MONTH); année = calendrier.get(Calendar.YEAR); Calendar calcul = calendrier; calcul.set(Calendar.DAY_OF_MONTH, 1); premierJourDuMois = calcul.get(Calendar.DAY_OF_WEEK); nombreJour = calendrier.getActualMaximum(Calendar.DAY_OF_MONTH); for (int i=1; i<premierJourDuMois; i++) add(new JLabel()); for (int i=1; i<=nombreJour; i++) { Jour jourDuMois = new Jour(i); if (i==jour && mois==aujourdhui.get(Calendar.MONTH) && année==aujourdhui.get(Calendar.YEAR)) jourDuMois.setForeground(Color.RED); add(jourDuMois); } revalidate(); repaint(); pack(); } void setMois(int mois) { calendrier.set(Calendar.MONTH, mois); init(); } void setAnnée(int année) { calendrier.set(Calendar.YEAR, année); init(); } } private class Sélection extends JComponent implements ActionListener, ChangeListener { private JComboBox mois = new JComboBox(new String[] {"Janvier", "Février", "Mars", "Avril", "Mai","Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"}); private JSpinner année = new JSpinner(new SpinnerNumberModel(jourMois.année, 0, 2100, 1)); Sélection() { setLayout(new BorderLayout()); mois.setSelectedIndex(jourMois.mois); mois.addActionListener(this); année.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 16)); année.addChangeListener(this); add(mois); add(année, BorderLayout.EAST); } public void actionPerformed(ActionEvent e) { jourMois.setMois(mois.getSelectedIndex()); } public void stateChanged(ChangeEvent e) { jourMois.setAnnée((Integer)année.getValue()); } } }
Dans mes propos, je parle souvent de composants. Dans le monde de Java, ces composants sont aussi appelés des JavaBeans ou, sous forme raccourci, des beans. Les Javabeans définissent un ensemble de règles ; les beans Java sont des objets Java ordinaires qui satisfont ces règles ; ils se conforment ainsi à l'API et aux modèles de conception de JavaBeans. Ces règles sont les suivantes :
Comme exemples de beans, il nous suffit d'étudier les paquetages javax.swing. Tous les composants habituels comme JButton, JTextArea, JScrollpane, etc. sont en réalité des beans.
Une propriété est composée de trois éléments : d'abord un attribut privé suivi de deux méthodes publiques associées. Chaque propriété doit donc être accessible au client par l'intermédiaire de deux méthodes spécialisées, appelées accesseurs : une méthode getXxx() (acquérir) pour lire la valeur de la propriété et une méthode setXxx() (placer) pour la modifier.
Le nom de chaque accesseur, appelés communément getter et setter est construit avec les préfixes get ou set suivi du nom de la propriété (attribut) avec, toutefois, la première lettre transformée en majuscule. Dans le cas des propriétés booléennes, nous disposons de deux getter ; nous utilisons indépendamment les formes isXxx( ) ou getXxx( ).
Ainsi en prenant comme exemple la propriété nom :
private String nom ;
public String getNom( ) { return nom; }
public void setNom(String nom) { this.nom = nom; ...(reste du code)... }
D'une façon générale, nous avons :
private type unePropriété ;
public type getUnePropriété( ) { return unePropriété; }
public boolean isUnePropriété( ) { return unePropriétéBooléenne; }
public void setNom(type unePropriété) { this.unePropriété = unePropriété; ...(reste du code)...
Les beans sont des objets destinés à être manipulés visuellement, à l'intérieur d'un constructeur d'application graphique. Ils sont généralement choisis dans une palette d'outils et manipulés graphiquement dans l'espace de travail du constructeur d'application, souvent appelé mode Design. L'intérêt des propriétés, c'est qu'elles sont directement accessibles, toujours graphiquement, dans la palette de propriétés. Il est donc facile de changer rapidement le libellé d'un bouton, de prévoir une nouvelle couleur de fond dans un JPanel, etc.
Par exemple, nous pouvons créer un nouveau composant (bean), qui se prénomme ZoneImage, qui est capable d'afficher une photo. La sélection de votre image se fait à partir de la propriété fichierImage. Cette propriété est de type File. Ainsi, lorsque vous demandez à changer la valeur de cette propriété, une boîte de dialogue correspondant à la gestion des fichiers est automatiquement affichée. Il suffit alors de choisir votre fichier en parcourant l'arborescence souhaitée. Il est également possible de préciser votre fichier image avec l'arborescence requise en écrivant tout simplement le texte correspond dans la zone de propriété prévue à cet effet. Une fois que l'image est choisie à partir de la propriété fichierImage, elle apparaît instantanément à l'intérieur du composant se qui démontre la grande force des JavaBeans.
En tenant compte de la définition d'une propriété, et en tenant compte qu'il s'agit d'un composant graphique, voici le codage de notre JavaBean ZoneImage :
1 package photo; 2 3 import java.awt.Graphics; 4 import java.awt.image.BufferedImage; 5 import java.io.*; 6 import javax.imageio.ImageIO; 7 import javax.swing.*; 8 9 public class ZoneImage extends JComponent { 10 private BufferedImage photo; 11 private double ratio; 12 private File fichierImage; 13 14 public ZoneImage() { 15 setSize(150, 100); 16 setBorder(BorderFactory.createLoweredBevelBorder()); 17 } 18 19 public File getFichierImage() { 20 return fichierImage; 21 } 22 23 public void setFichierImage(File fichierImage) { 24 try { 25 this.fichierImage = fichierImage; 26 photo = ImageIO.read(fichierImage); 27 ratio = (double)photo.getWidth()/photo.getHeight(); 28 setSize(getWidth(), (int)(getWidth()/ratio)); 29 repaint(); 30 } 31 catch (IOException ex) { } 32 } 33 34 @Override 35 public void setBounds(int x, int y, int width, int height) { 36 if (photo==null) super.setBounds(x, y, width, height); 37 else super.setBounds(x, y, width, (int)(width/ratio)); 38 } 39 40 @Override 41 protected void paintComponent(Graphics g) { 42 if (photo!=null) 43 g.drawImage(photo, 0, 0, getWidth(), (int)(getWidth()/ratio), null); 44 } 45 }
Sur le code ci-dessus, nous pouvons voir comment est maîtrisée l'apparence de notre composant :