IHM - Introduction à Swing et AWT

Chapitres traités   

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

Choix du chapitre Java Foundation Classes

Swing fait partie d'une collection plus vaste de logiciels baptisée JFC (Java Foundation Classes). JFC contient les API suivantes :

  1. l'AWT (Abstract Window Toolkit), la boîte à outils d'origine de l'interface utilisateur ;
  2. Swing, la boîte à outil de l'interface utilisateur ;
  3. L'accessibilité, qui propose des outils permettant d'intégrer des périphériques d'entrée et sortie non standard à vos interfaces utilisateurs ;
  4. L'API 2D, ensembles de classes cohérentes permettant des dessins de bonnes qualité ;
  5. Drag and Drop, API supportant le "glisser-déplacer" ;
  6. L'API permettant la gestion du bureau issu du système d'exploitation hôte (IconTray, etc. ).
  7. ...

JFC est la partie la plus volumineuse et la plus compliquée de la plate-forme standard Java.
.


Choix du chapitre Abstract Window Toolkit

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.

 

Choix du chapitre L'interface utilisateur Swing

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.


Construction de l'interface utilisateur avec Swing

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.

Gestionnaire de disposition

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.

Choix du chapitre Les composants

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

Structure d'un composant

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.

La gestion des événements

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.
.

Tous les composants sont des conteneurs et doivent gérer les autres composants intégrés

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.

 

Choix du chapitreRedessiner le composant

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

Composant Swing en tant que conteneur

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

Méthodes proposées de la classe Graphics

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.

Exemples d'utilisation des méthodes de la classe Graphics

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.
.

 

Choix du chapitre Service minimum pour un composant graphique - JComponent

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.

Les conteneurs - add(), remove() et removeAll()

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.

  1. Un JFrame est une fenêtre de niveau supérieur de votre écran. JFrame hérite de JWindow, assez semblable mais sans bordure.
  2. Un JPanel est un élément de conteneur générique servant à regrouper des composants à l'intérieur de JFrame et d'autres JPanel.
  3. La classe JApplet est une sorte de conteneur de classe servant de base aux processus exécutés dans un navigateur Web. Comme tout JComponent, un JApplet peur contenir d'autres composants d'interface utilisateur.
  4. Nous pouvons également utiliser la classe JComponent directement, comme un JPanel, pour maintenir des composants dans un autre conteneur.

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 {
...
}
Retour sur les gestionnaires de placement - setLayout()

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 :

  1. Le gestionnaire de placement par défaut d'un JPanel est un FlowLayout ; il essaie de placer les objets sous leur taille préférée de gauche à droite et de haut en bas :

    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();  
       }
    }
  2. Le gestionnaire de placement par défaut de JFrame est un BorderLayout, qui place des objets à des emplacements particuliers désignés de la fenêtre, comme NORTH, SOUTH et CENTER :

    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();  
       }
  3. Il est tout à fait possible de changer de gestionnaire de placement par défaut et d'imposer celui qui vous convient. Dans l'exemple qui suit, nous choisissons le gestionnaire GridLayout en lieu et place du gestionnaire BorderLayout :

    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();  
       }
    }
  4. L'idéal est de combiner l'ensemble de ces gestionnaires au travers de panneaux intermédiaires (JPanel) afin d'obtenir l'apparence désirée :




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.

Activer et désactiver des composants - setEnabled() - isEnabled()

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.
.

Gestion de la couleur - la classe Color

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 :

Nom de constante Couleur correspondante
Color.black
Color.blue
Color.cyan
Color.darkGray
Color.gray
Color.green
Color.lightGray
Color.magenta
Color.orange
Color.pink
Color.red
Color.white
Color.yellow
noir
bleu
cyan (bleu clair)
gris foncé
gris
vert
gris clair
magenta
orange
rose
rouge
blanc
jaune

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

Autres méthodes de composant

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 :

Container getParent()
Renvoie le conteneur hébergeant ce composant.
String getName()
void setName(String nom)
Obtient ou effecte le nom String à ce composant. L'appellation d'un composant s'avère utile en cas de débogage. Le nom est renvoyé par la méthode toString().
void setVisible(boolean visible)
Rend le composant visible ou invisible à l'intérieur de ce conteneur. Lorsque vous changez la visibilité du composant, le gestionnaire de placement du conteneur dispose automatiquement ses composants visibles.
Font getFont()
void setFont(Font c)
Obtient ou définit la police de caractère d'un composant. Il est possible d'écrire un texte avec une police différente que celle prévue par défaut. Les polices de caractères, appelées aussi fontes, sont représentées par des objets de la classe java.awt.Font. Un objet Font est construit à partir d'un nom de police, un identificateur de style et d'une taille.

setFont(new Font("Verdana", Font.BOLD, 14));

Nous disposons de quatre identificateur de style, sous forme de constantes, qui permettent de spécifier,
... une police normale - Font.PLAIN,
... une police en gras - Font.BOLD,
... une police en italique - Font.ITALIC,
... une police en gras et en italique - Font.BOLD+Font.ITALIC.
Color getForeground()
void setForeground(Color c)
Color getBackground()
void setBackground(Color c)
Obtient ou définit les couleurs de premier plan et d'arrière plan de ce composant. La couleur de premier plan d'un composant est la couleur par défaut utilisée pour le dessin. Par exemple, la couleur utilisée pour le texte dans un champ de saisie, c'est également la couleur par défaut de l'objet Graphics passé en argument des méthodes paint() et paintComponent() du composant. La couleur d'arrière plan est utilisée dans le remplissage de la surface du composant lorsqu'elle est affacée par l'implémentation par défaut update().
boolean isOpaque()
void setOpaque(boolean visibilité)
Obtient ou définit une opacité, ou inversement une transparence du composant.
Dimension getSize()
void setSize(int largeur, int hauteur)
Obtient ou définit la taille actuelle du composant. Un gestionnaire de placement peut modifier la taille d'un composant même après que vous l'avez définie. Pour changer la taille qu'un composant veut avoir, utilisez alors la méthode setPreferedSize(). D'autres méthodes de JComponent permettent de définir son emplacement, mais cette tâche est normalement dévolue à un gestionnaire de placement.
Dimension getPreferedSize()
void setPreferedSize(Dimension taillePréférée)
Utilisez ces méthodes pour examiner ou définir la taille préférée d'un composant. Les gestionnaires de placement essaient d'installer les composants avec leurs tailles préférées. Si vous changez la taille préférée d'un composant, n'oubliez pas d'appeler la méthode revalidate() sur ce composant pour le placer de nouveau.
Point getLocation()
Point getLocationOnScreen()
void setLocation(Point p)
void setLocation(int x, int y)
Obtient ou définit la position actuelle du composant par rapport au composant parent. La méthode getLocationOnScreen() précise les coordonnées par rapport à l'écran.
int getX()
int getY()
int getWidth()
int getHeight()
Délivre respectivement la l'abscisse, l'ordonnée, la largeur et la hauteur actuelles du composant.
Rectangle getBounds()
void setBounds(Rectangle zonePréférée)
void setBounds(int x, int y, int largeur, int hauteur)
Obtient ou définit à la fois la position et la taille actuelle du composant. Lorsque le composant doit être automatiquement redimensionnné, cette dernière méthode est systématiquement appelée.
boolean contains(int x, int y)
boolean contains(Point p)
Permet d'indiquer si le point spécifier en argument est dans la zone interne du composant.
Graphics getGraphics()
Récupère le contexte graphique du composant. Il est alors possible de proposer des tracés supplémentaires sans avoir à créer un nouveau composant et à redéfinir la méthode paintComponent(). Attention toutefois, le tracé ne sera pas pris en compte dans le cas d'un réaffichage automatique ou d'un redimensionnement.
Cursor getCursor()
void setCursor(Cursor curseur)
Obtient ou définit le type de curseur (pointeur de souris) utilisé lorsque la souris se trouve au dessus de ce composant. Il existe des curseurs prédéfinis qu'il est possible de récupérer au moyen de la méthode getPredefinedCursor() en spécifiant la constante correspondante :
.... DEFAULT_CURSOR (normal),
.... CROSSHAIR_CURSOR (croix),
.... HAND_CURSOR (main),
.... MOVE_CURSEUR (déplacement),
.... TEXT_CURSOR (texte),
.... WAIT_CURSOR (sablier), etc.
Border getCursor()
void setBorder(Border bordure)
Obtient ou définit une bordure sur le pourtour du composant.
String getToolTipText()
void setToolTipText(String text)
Obtient ou définit une bulle d'aide associé au composant. Cette bulle d'aide apparaît automatiquement lorsque le curseur de la souris passe au dessus du composant pendant un certain temps.
Component[] getComponents()
Renvoie les composants du conteneur dans un tableau.
Component getComponentAt(int x, int y)
Indique quel composant se trouve aux coordonnées spécifiées dans le système de coordonnées du conteneur.
Bordure sur les composants - setBorder()

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 :

  1. createLoweredBevelBorder() : relief incrusté.

    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();  
       }
    }
  2. createRaisedBevelBorder() : relief en saillie.

  3. createBevelBorder(int type) : relief incrusté ou relief en saillie.

    Méthode commune correspondant aux deux méthodes précédentes,
    .... soit en incrustée à l'aide de la constante BevelBorder.LOWERED,
    .... soit en relief avec la constante BevelBorder.RAISED.
  4. createEtchedBorder() : gravé.

  5. createEtchedBorder(int type) : gravé ou en relief.

    A l'aide de l'agument entier, vous pouvez choisir votre type de bordure fine :
    .... EtchedBorder.LOWERED (gravé)
    ou EtchedBorder.RAISED (relief).


  6. createLineBorder() : trait uniforme.



    Il existe deux méthodes createLineBorder(). La première prend un argument correspondant à la couleur du trait. La deuxième propose en plus l'épaisseur du trait.
  7. createMatteBorder() : avec trait variable ou texture.
  8. createEmptyBorder(int haut, int gauche, int bas, int droite) : vide pour créer un espace autour du composant.
  9. createTitledBorder() : ajout d'un titre à la bordure en conjonction d'une autre bordure.



    Cette méthode offre plein de possibilités. Elle est surdéfinie de plusieurs des manières :
    void createTitledBorder(String leTitre); // titre avec une bordure discrète comme l'image ci-dessus
    void createTitledBorder(Border bordure); // une bordure sans titre (peu d'intérêt)
    void createTitledBorder(Border bordure, String leTitre); // choix du type de bordure
    void createTitledBorder(Border bordure, String leTitre, int justification, String position); // précise en plus l'emplacement du titre

    La justification se règle avec les constantes suivantes :
    ....... TitledBorder.LEFT
    ....... TitledBorder.CENTER
    ....... TitledBorder.RIGHT
    ....... TitledBorder.LEADING
    ....... TitledBorder.TRAILING
    ....... TitledBorder.DEFAULT_JUSTIFICATION (leading)


    La position se règle avec les constantes suivantes :
    ....... TitledBorder.ABOVE_TOP
    ....... TitledBorder.TOP
    ....... TitledBorder.BELOW_TOP
    ....... TitledBorder.ABOVE_BOTTOM
    ....... TitledBorder.BOTTOM
    ....... TitledBorder.BELOW_BOTTOM
    ....... TitledBorder.DEFAULT_POSITION (top)


    Voici par exemple de ce que nous pouvons réaliser :



    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
    void createTitledBorder(Border bordure, String leTitre, int justification, String position, Font police, Color couleur); // permet de choisir la couleur du titre
  10. createCompoundBorder() : combinaison de plusieurs bordures.

    Cette méthode accepte deux arguments de type Border. Voici, par exemple, ce que nous pouvons faire :



    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 :

BevelBorder
Cette bordure trace des coutours en relief ou en creux, donnant l'illusion de profondeur.
SoftBevelBorder
Cette bordure ressemble à BevelBorder, mais elle est plus fine.

exemple : bordure.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED));


EmptyBorder
N'effectue aucun tracé, mais occupe l'espace. Nous pouvons l'utiliser pour donner "un peu d'air" à un composant dans une interface utilisateur chargée.
EtchedBorder
Une bordure en creux donnant l'apprence d'un rectangle non ciselé dans la pierre. Une bordure en relief semble ressortir de l'écran.
LineBorder
Dessine un simple rectangle autour d'un composant. Nous pouvons préciser la couleur et l'épaisseur de la ligne dans le constructeur de LineBorder.
MatteBorder
Version gonflée de LineBorder. Nous pouvons créer un MatteBorder d'une certaine couleur et indiquer la taille de la bordure à gauche, en haut, à droite ou en bas du composant. MatteBorder permet également de transmettre une icône dans un Icon qui sera utilisé pour dessiner la bordure. Il peut s'agir d'une image ou de tout autre implémentation de l'interface Icon.
TitleBorder
Bordure ordinaire avec titre. TitleBorder ne trace pas vraiment une bordure ; il dessine seulement un titre en conjonction avec un autre objet de bordure. Nous pouvons indiquer les emplacements du titre, sa justification, ainsi que sa police. Ce type de bordure s'avère particulièrement utile lorsque nous regroupons plusieurs contrôles dans une interface complexe.
CompoundBorder
Une bordure contenant deux autres bordures. Particulièrement commode pour enfermer un composant dans un EmptyBorder, puis pour placer un décor autour, comme un EtchedBorder ou un MatteBorder.
Exemple de synthèse

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

 

Choix du chapitre Reévaluation des dispositions - revalidate()

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 :

  1. La modification de sa taille ;
  2. le redimensionnement ou le déplacement de l'un de ses composants enfants ;
  3. l'ajout, l'affichage, la suppression ou le masquage d'un composant enfant.

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.

Cas d'utilisation de la méthode revalidate()

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é :

  1. Cela peut se faire lorsque vous modifiez la taille souhaitée d'un composant (par opposition à sa taille réelle sur l'écran). Pour nettoyer l'affichage, appelez la méthode revalidate(). Par exemple, si vous avez un JPanel - disons un pavé numérique de quelques boutons - et que vous changez sa taille souhaitée en appelant sa méthode setPreferredSize(), vous devriez également appeler la méthode revalidate() pour le panneau ou pour son conteneur immédiat. Le gestionnaire de placement du panneau réarrange alors ses boutons pour qu'ils rentrent dans la nouvelle zone d'affichage.
  2. Si une taille est attribué au composant avant l'affichage du conteneur auquel il est rattaché, le gestionnaire de mise en forme en tiendra compte et l'affichage du conteneur sera correct. En revanche, si nous modifions cette taille par la suite, il faudra obliger le gestionnaire de mise en forme du conteneur à refaire ses calculs :
    1. soit en appelant la méthode validate() pour le conteneur (de la même façon que cet appel devait être efefctué en cas de modification du contenu d'un conteneur...),
    2. soit en appelant la méthode revalidate() pour le composant.
  3. Après avoir changer la taille d'un champ de texte (JTextField) à l'aide de la méthode setColumns(), vous devez effectivement appelez la méthode revalidate() du conteneur. Cette méthode recalcule donc la taille et la disposition de tous les composants dans un conteneur. Après son utilisation, le gestionnaire de mise en forme redimensionne le conteneur avec la taille du champ modifiée.
  4. Lorsque vous modifiez dynamiquement la liste prévue initialement dans une ComboBox.
  5. etc.

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

Exemple d'application

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

 

Choix du chapitre Les JavaBeans

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 :

  1. La classe représentant le JavaBean (le composant) doit impérativement être publique.
  2. La classe doit disposer d'un constructeur par défaut (c'est-à-dire sans argument) dans le cas où elle propose d'autres constructeurs.
  3. La classe doit proposer des propriétés qui vont permettre d'interagir avec le composant.
  4. La classe doit également gérer les événements pour compléter l'interaction.
  5. La classe est très souvent un élément graphique et doit donc, dans ce cas là, hérité de JComponent où d'un de ses enfants, mais ce n'est pas une obligation.

Exemples de JavaBeans

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.

Définition d'une propriété

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

Intérêt des beans

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.


Codage du bean ZoneImage

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 :

  1. Mise en place d'un constructeur par défaut qui choisie une taille du composant avec une bordure particulière. Notre image sera alors comme incrustée par rapport à l'environnement graphique périphérique.
  2. Lorsque nous changeons la propriété fichierImage, au travers donc de la méthode setFichierImage(), nous récupérons l'image correspondante et nous en profitons pour calculer le rapport qui existe entre sa largeur et sa hauteur. Nous demandons alors à redimensionner le composant en conséquence, à l'aide la méthode setSize(), et nous demandons à afficher la photo correspondante, à l'aide de la méthode repaint().
  3. Cette dernière méthode repaint() sollicite indirectement la méthode redéfinie paintComponent() qui affiche effectivement l'image dont les dimensions sont calquées sur la dimension du composant conteneur.
  4. Nous en profitons également pour redéfinir la méthode setBound() afin que lorsque nous changeons la taille du bean dans le mode Design, le ratio de l'image soit effectivement respecté.