Fenêtre et cadre de fenêtre

Chapitres traités   

Dans cette étude, nous nous concentrons uniquement sur la mise en oeuvre de nouvelles fenêtres avec ou sans cadres. Nous en profiterons pour connaître leurs différentes constitutions, mais aussi comment interragir avec le bureau et la barre de tâche.

 

 

Choix du chapitre Fenêtres et cadres

Les fenêtres et les cadres sont les conteneurs de plus niveau des composants Java. JWindow n'est rien d'autre qu'un plein écran graphique d'affichage dans le système de fenêtrage. Les fenêtres ne comportent pas fioritures : elles conviennent surtout pour faire surgir des écrans et des fenêtres popup. De son côté, JFrame est une classe fille de JWindow, pourvue d'une bordure et pouvant contenir une barre de menu. Il est également possible de déplacer un cadre sur l'écran et de le redimensionner, à l'aide des contrôles habituels de l'environnement de fenêtrage.

L'exemple ci-dessous présente un cadre JFrame à gauche et une fenêtre JWindow à droite :



package fenêtres;

import javax.swing.*;

public class Fenêtres {
    public static void main(String[] args) {
        JFrame cadre = new JFrame("Cadre de fenêtre");
        cadre.setSize(300, 250);
        cadre.setLocation(150, 100);
        
        JWindow fenêtre = new JWindow();
        fenêtre.setSize(300, 250);
        fenêtre.setLocation(500, 100);
        
        cadre.setVisible(true);
        fenêtre.setVisible(true);
    }
}

Décrivons le fonctionnement de ce petit programme :

  1. Le constructeur de JFrame peut prendre un argument String indiquant le titre affiché dans la barre de titre. Il est également possible de créer un JFrame sans titre, puis d'appeler ensuite la méthode setTitle() pour en fournir un ultérieurement.
  2. La taille et la position de JFrame et de JWindow sur votre bureau sont déterminées par les méthodes que nous connaissons déjà, c'est-à-dire setSize() et setLocation().
  3. JWindow ne possédant pas de barre de titre, le constructeur de JWindow ne comporte donc pas d'arguments.
  4. Une fois que vous avez définis votre JFrame et votre JWindow, nous appelons setVisible(true) pour les afficher à l'écran. La méthode setVisible() revient immédiatement, sans blocage. Par bonheur, notre application ne se termine pas, même après avoir atteint la fin de la méthode main(), puique les fenêtres sont encore visibles. Vous pouvez fermer le JFrame en cliquant sur l'icône de fermeture de la barre de titre. Attention, par défaut, JFrame se cache lorsque nous cliquons dessus, en appelant la méthode setVisible(false), mais ne quitte toujours pas l'application. Heureusement, nous verrons que nous pouvons modifier ce comportement en appelant la méthode adéquate setDefaultCloseOperation().
  5. Il est toutefois impossible de fermer un JWindow dans l'application. Pour mettre fin à votre à l'exécution de l'application Fenêtres, vous devez saisir Crtl-C ou la touche destinée à tuer un processus sur votre machine. Du coup, JWindow sera plutôt utilisé par un autre JFrame et sera très rarement la fenêtre principale d'une application.

En Java, une fenêtre de haut niveau, c'est-à-dire une fenêtre qui n'est pas contenue dans une autre fenêtre, est appelée frame (cadre ou fenêtre d'encadrement). Pour représenter ce niveau supérieur la bibliothèque AWT possède une classe nommée Frame. La version Swing de cette classe est baptisée JFrame, qui étend la classe Frame et désigne l'un des rares composants Swing qui ne soient pas dessiné sur un canevas (grille). Du coup, les éléments de décoration de la fenêtre (boutons, barre de titre, icônes, etc.) ne sont pas dessinés par Swing, mais par le système de fenêtrage du système d'exploitation de l'utilisateur.


Les cadres sont des exemples de conteneurs. Cela signifie qu'ils peuvent contenir d'autres composants d'interface tels que les boutons et des champs de texte. De toute façon, tous les autres composants et conteneurs doivent, à un certain niveau, se trouver dans un JWindow ou un JFrame. Les applets JApplet sont une sorte de Container. Même les applets doivent être hébergées dans un dans cadre ou une fenêtre, bien qu'en général, nous ne voyons pas son cadre parent car il fait partie du navigateur qui affiche l'applet.

 

Choix du chapitre Mise en place d'un cadre

Je reviendrais plutard sur la fenêtre JWindow. Nous allons maintenant étudier les méthodes employées le plus fréquemment lorsque nous travaillons avec un composant JFrame. Nous allons procéder par étapes, de l'ossature d'un cadre de fenêtre minimal jusqu'à que ayons une fenêtre relativement sophistiquée.

Création d'un cadre visible où nous pouvons quitter directement l'application

Dans l'exemple précédent, nous avons simplement créé un objet de type JFrame et nous avons utilisé les fonctionnalités présentes dans cette classe. Mais pour que notre programme présente un intérêt, il va de soi qu'il faut lui associer des fonctionnalités ou des attributs supplémentaires ; de fait, la fenêtre devra pouvoir réagir à certains événements. Pour cela, il nous faudra généralement définir notre propre classe dérivée de JFrame et créer un objet de ce nouveau type. Par ailleurs, il ne sera plus nécessaire de préciser à chaque fois le nom de l'objet de type JFrame pour faire appel à la méthode requise.

Voici donc un exemple de codage minimum requis, qui permet de constituer une fenêtre principale d'application placée à l'endroit voulu, avec la possibilité de quitter l'application quand l'utilisateur le désire.

package cadre;

import javax.swing.JFrame;

public class Fenêtre extends JFrame {
   public Fenêtre(String titre) {
      super(titre);
      setSize(300, 200);
      setLocation(100, 100);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
    
   public static void main(String[] args) {
      new Fenêtre("Première fenêtre")  ;
   }
}


ou -----------------------------------------------------


public class Fenêtre extends JFrame {
   public Fenêtre() {
      setTitle("Première fenêtre");
      setBounds(100, 100, 300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setVisible(true);
   }
    
   public static void main(String[] args) {
      new Fenêtre();
   }
}

Bien que nous n'ayons rien prévu de particulier, l'utilisateur peut manipuler cette fenêtre comme n'importe qu'elle fenêtre graphique d'un logiciel du commerce, et en particulier : la retailler, la redimmensionner, la réduire à une icône dans la barre de tâche. Ces fonctionnalités, communes à toutes les fenêtres, sont prises en charge par la classe JFrame elle-même. Vous n'avez donc pas à vous soucier de la gestion des événements correspondants tels que le clic sur l'une des cases systèmes, le glissé (drag) d'une bordure ...

Après ce commentaire, analysons notre code source et voyons la liste des méthodes utiles cette classe JFrame :

  1. super() : Nous faisons ici explicitement appel au constructeur de la classe JFrame qui accepte un paramètre de type String qui précise tout simplement le titre de la fenêtre.
  2. setTitle() : Au lieu, de faire un appel explicite au constructeur de la classe de base, il est tout à fait possible d'utiliser cette méthode après coup.
  3. setSize() : Par défaut, un cadre a une taille assez peu utile de 0 x 0 pixels. Il faut donc utiliser cette méthode pour spécifier la taille requise.
  4. setLocation() : cette méthode de la classe Component peut être utilisée pour un JFrame ou un JWindow afin de définir sa position à l'écran. Les coordonnées x et y sont relatives à l'origine de l'écran (le coin supérieur gauche). Attention, l'axe des abscisses est orientée vers la droite, celui des ordonnées vers le bas (nous n'avons à pas affaire à un système orthonormé usuel).
  5. setBounds() : nous pouvons aussi utiliser une méthode qui positionne et propose les dimensions voulues en une seule fois.
  6. setDefaultCloseOperation() : Nous indiquons ensuite ce qui doit se passer lorsque l'utilisateur ferme ce cadre. Dans ce cas précis, le programme doit sortir. Dans d'autres programmes comprenant éventuellement plusieurs cadres, vous ne souhaiterez pas la sortie du programme si l'utilisateur ferme seulement l'un des cadres. Par défaut, un cadre est masqué - setVisible(false) - lorsque l'utilisateur le ferme, mais le programme ne se termine pas pour autant. Nous pouvons imposer un autre comportement lors de la fermeture de la fenêtre, en appelant cette méthode avec l'un des arguments suivants :
  7. setVisible() : Le simple fait de construire un cadre ne l'affiche pas automatiquement. Les cadres sont au départ invisibles. Cela permet au programmeur d'y ajouter des composants avant l'affichage. Pour afficher le cadre, vous devez impérativement appeler cette méthode.

Positionnement et réglages du cadre de la fenêtre

La classe JFrame ne fournit que peu de méthodes capables de modifier l'aspect d'un cadre. Cependant, grâce à l'héritage, les diverses superclasses de JFrame proposent la plupart des méthodes permettant d'agir sur la taille et la position d'un cadre ainsi que quelques petits ajouts intéressants.

C'est généralement dans la classe Component (ancêtre de tous les objets d'interface utilisateur graphique) ou dans la classe Window (superclasse du parent de la classe Frame) que l'on recherche les méthodes permettant de modifier la taille et la position des cadres.

Les plus importantes sont les suivantes :

java.awt.Component
boolean isVisible()
Renvoie true si le composant est visible. Les composants sont initialement visibles par défaut, à l'exception des composants de haut niveau tels que JFrame.
void setVisible(boolean visible)
Affiche ou cache le composant, selon la valeur booléenne proposée (respectivement true ou false).
boolean isShowing()
Vérifie que le composant s'affiche à l'écran. Pour cela, le composant doit être visible et son éventuel conteneur doit être également affiché.
boolean isEnabled()
void setEnabled(boolean activation)
Obtient ou définit l'activité du composant. Un composant activé peut recevoir le focus du clavier. Les composants sont initialement activés.
boolean isOpaque()
void setOpaque(boolean visibilité)
Obtient ou définit une opacité, ou inversement une transparence du composant.
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.
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 Component permettent de définir son emplacement, mais cette tâche est normalement dévolue à un gestionnaire de placement.
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.
java.awt.Window
void toFront()
Affiche cette fenêtre par-dessus toutes les autres.
void toBack()
Place cette fenêtre au-dessous de la pile des fenêtres du bureau et réorganise les autres fenêtres visibles.
boolean isLocationByPlatform()
void setLocationByPlatform(boolean valider)
Récupère ou défini la propriété locationByPlatform. Lorsque la propriété est définie avant que la fenêtre ne soit affichée, la plate-forme choisit un emplacement adéquat, légèrement décallée de la dernière qui s'est affichée.
void setLocationRelativeTo(Component référence)
Cette méthode permet de positionner la fenêtre par rapport à un autre composant. Si ce composant n'existe pas ou si vous placez la valeur null, la fenêtre est alors automatiquement centrée.
java.awt.Frame
void setResizable(boolean visible)
Détermine si l'utilisateur peut modifier la taille du cadre.
void setTitle(String titre)
Affiche le texte de la chaîne passée en argument à la barre de titre du cadre.
void setIconImage(Image icône)
Spécifie l'icône de l'application qui est alors visible sur bord supérieur gauche de la fenêtre ou dans la barre de tâche lorsque la fenêtre est réduite sous forme d'icône.
boolean isUndecorated()
void setUndecorated(boolean décoration)
Indique si les décorations du cadre sont présentes ou les supprime si le paramètre passé en argument est true.
int getExtendedState()
void setExtendedState(int état)
Récupère ou définit l'état de la fenêtre. Voici les différents états possibles :
-- normal : Frame.NORMAL
-- icônifié : Frame.ICONIFIED
-- maximum en horizontal : Frame.MAXIMIZED_HORIZ
-- maximum en vertical : Frame.MAXIMIZED_VERT
-- plein écran : Frame.MAXIMIZED_BOTH
java.awt.Toolkit
static Toolkit getDefaultToolkit()
Renvoie la boîte à outils par défaut.
Dimension getScreenSize()
Renvoie la taille de l'écran.
Image getImage(String fichierImage)
Image getImage(URL url)
Charge une image à partir du nom du fichier spécifié en argument ou à partir de son URL.

Au vue de ces différentes méthodes, nous pouvons rajouter quelques commentaires supplémentaires :

  1. Pour spécifier un placement de fenêtre classique, nous connaissons déjà toutes les méthodes issues de Component comme setSize(), setLocation() et setBounds().
  2. Il est possible de démarrer votre application pour que cette dernière prennent systématiquement tout l'écran. Il est alors judicieux d'utiliser la méthode setExtendedState(Frame.MAXIMIZED_BOTH).
  3. Nous pouvons préférer imposer une taille de fenêtre qui ne bouge plus durant toute sa durée de vie, prenez alors la méthode setResizable(false).
  4. Une méthode particulière est certainement setUndecorated(true) qui enlève tout le pourtour du cadre de la fenêtre. Nous nous retrouvons alors exactement comme dans le cas d'un JWindow.
  5. Nous pouvons aussi donner le contrôle du placement au système de fenêtres. Si nous appelons la setLocationByPlatform(true) avant d'afficher le cadre, le système de fenêtres choisit l'emplacement (mais non la taille), généralement avec un léger décalage par rapport à la dernière fenêtre. Dans la plupart des cas, cette méthode est plus intéressante que la simple setLocation().
  6. Toujours dans le positionnement d'une fenêtre, il est également possible d'utiliser la méthode setLocationRelativeTo() qui par nature positionne le cadre par rapport à un autre composant. L'intérêt ici est de faire en sorte que la fenêtre s'affiche automatiquement au centre de l'écran en proposant tout simplement la valeur null à cette méthode.

Pour vous donner une idée de ce que nous pouvons faire avec une fenêtre, nous terminerons ce chapitre en proposant un programme qui positionne le cadre au centre de l'écran.

Pour connaître la taille de l'écran, nous devons utiliser la classe Toolkit. La classe Toolkit est un dépotoir pour diverses méthodes qui interfacent le système de fenêtrage natif. La méthode qui nous intéresse ici est la méthode getScreenSize(), qui renvoie la taille de l'écran sous forme d'un objet Dimension (un objet de ce type stocke simultanément une largeur et une hauteur dans des attributs publics appelés respectivement width et height.

Nous fournissons également une icône à notre cadre. Comme la récupération des images dépend aussi du système, nous employons également la boîte à outils pour charger une image, à l'aide de la méthode getImage(). Ensuite, nous affectons l'image à l'icône du cadre au travers de la méthode setIconImage().

Les méthodes getScreenSize() et getImage() de la classe Toolkit sont des méthodes non statiques. Vous devez donc créer au préalable un objet représentant la boîte à outil en utilisant la méthode statique getDefaultToolkit().

package cadre;

import java.awt.*;
import javax.swing.JFrame;

public class Fenêtre extends JFrame {
   public Fenêtre() {
      setTitle("Cadre centré à l'écran");
      Toolkit kit = Toolkit.getDefaultToolkit();
      Dimension dimensionEcran = kit.getScreenSize();
      int largeur = dimensionEcran.width;
      int hauteur = dimensionEcran.height;
      setBounds((largeur-300)/2, (hauteur-200)/2, 300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setIconImage(kit.getImage("icone.gif"));
      setResizable(false);
      setVisible(true);
   }
    
   public static void main(String[] args) {
      new Fenêtre()  ;
   }
}
Comme nous l'avons vu plus haut, de façon plus simple, pour positionner votre cadre au centre de l'écran, il est préférable d'utiliser la méthode setLocationRelativeTo(null).

Codage correspondant
package cadre;

import java.awt.*;
import javax.swing.JFrame;

public class Fenêtre extends JFrame {
   public Fenêtre() {
      setTitle("Cadre centré à l'écran");
      setLocationRelativeTo(null);
      setSize(300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setIconImage(new ImageIcon("icone.gif").getImage("icone.gif"));
      setResizable(false);
      setVisible(true);
   }
    
   public static void main(String[] args) {
      new Fenêtre()  ;
   }
} 

 

Choix du chapitre Utilisation de ressources

Restons quelques instants sur ce programme. Nous remarquons que nous avons un problème de déploiement pour l'icône. En effet, cette dernière doit être utilisée à côté du fichier d'archive, ce qui est assez gênant. L'idéal serait qu'elle soit directement intégrée dans l'archive elle-même. Pour cela, vous devez alors mettre en oeuvre le mécanisme des ressources. Ainsi, pour obtenir un seul fichier d'archive afin que le déploiement en soit facilité, il est nécessaire que l'icône soit plutôt considérée comme une ressource.

Utilité des ressources

Il est assez fréquent, à terme, d'avoir besoin de changer le titre d'une fenêtre, de changer un ensemble de messages ou tout simplement de préférer une autre image de fond de celle prévue initialement. Nous pouvons avoir deux approches pour réaliser ces changements. Soit nous changeons dans le code source les références à ces différents éléments ce qui nécessite, bien entendu, de tout recompiler. Ou alors nous plaçons ces éléments dans des fichiers séparés afin de proposer les changements à l'extérieur du programme suivant le désir de l'utilisateur à l'aide d'un tout petit éditeur de texte. L'application se charge ensuite de lire le contenu de ces fichiers afin de configurer correctement les objets requis. Lorsque vous placez des valeurs à l'extérieur de votre programme, ces valeurs sont considérées comme des ressources.

En reprenant l'exemple de notre application, nous pourrions avoir comme type de ressources, le titre de la fenêtre ainsi que l'icône de l'application :

Quand utiliser une ressource ?

Les classes employées à la fois dans les applets et les applications utilisent généralement des fichiers de données ou de configuration associés tels que :

  1. des fichiers d'images et de son ;
  2. des fichiers de texte contenant les messages et les libellés des boutons ;
  3. des fichiers de données binaires, par exemple pour indiquer la disposition d'une carte ;
  4. des fichiers contenant l'ensemble des items des menus afin de prendre en compte la langue du pays.

En Java, un fichier associé de ce type est appelée une ressource.
.

Récupérer les valeurs des ressources

Où devons nous placer ces fichiers ressources ? Bien sûr, il serait pratique de les placer au même endroit que les autres programmes, par exemple dans un fichier d'archive JAR.

Le chargeur de classe sait comment parcourir chacun des emplacements possibles jusqu'à retrouver le fichier de classes. Toutefois, dans notre cas, nous devons répéter le processus de recherche manuellement pour localiser les fichiers de ressources associés. La fonctionnalité de chargement de ressource automatise cette tâche.

Nous suivrons donc les étapes suivantes pour charger une ressource :

  1. Charger l'objet Class pour la classe possédant une ressource, par exemple Fenêtre.class ou, si nous somme à l'intérieur de l'objet représentant la dite classe : this.getClass().
  2. Appeler la méthode getRessource() ou la méthode getRessourceAsStream() suivant que vous désirez obtenir le fichier ressource en tant qu'URL ou directement les valeurs de ce fichier au travers d'un flux adapté. Cette deuxième méthode est souvent préférable.
  3. Si la ressource est un fichier image, nous pouvons de nouveau utiliser la méthode getImage() de la boîte à outil Toolkit.

Le chargeur de classes mémorise l'emplacement où il charge la classe ; il peut alors retrouver dans cet emplacement la ressource associée.
.

Par exemple, vous pouvez utiliser les instructions suivantes pour créer l'icône de l'application à partir du fichier image "icône.gif" en suivant cette procédure :

URL url = Fenêtre.class.getRessource("icone.gif");
setIconImage(kit.getImage(url));

Cela revient à rechercher le fichier "icone.gif" au même endroit que celui où vous avez trouvé Fenêtre.class.

Pour lire un fichier texte "titre.txt" représentant le titre de votre application, vous pouvez, par exemple, utiliser les instructions suivantes :

InputStream flux = Fenêtre.class.getRessourceAsStream("titre.txt");

Pour lire ensuite à partir de ce flux, vous devez prendre ensuite un flux de plus niveau afin d'adapter le contenu au type requis. Ici, nous devons récupérer un texte, il faudra donc prendre un flux capable de retrouver ce texte. Pour une entrée, nous avons besoin de la classe Scanner.

Scanner titre = new Scanner(flux);
this.setTitle(titre.nextLine());

Pour en savoir plus sur les flux, consulter l'étude correspondante : Les flux et les fichiers.
.

Structuration de vos ressources

Il est possible de placer vos fichiers ressources dans un répertoire particulier afin d'éviter de les mélanger avec les fichiers de classes. Vous pouvez même hiérarchiser les noms des ressources. Par exemple, nous pouvons placer toutes nos ressources dans un répertoire appelé justement <ressources>. La localisation se fera alors de la façon suivante :

../ressources/titre.txt
et
../resources/icone.gif

Ce nom de ressource relatif est interprété à partir du package de la classe chargeant la ressource. Remarquez l'utilisation obligatoire du séparateur /, quel que soit le séparateur de répertoire du système d'exploitation sur lequel se trouvent finalement les fichiers de ressources. Ainsi, sous Windows, le chargeur de ressources convertit automatiquement / en séparateur \.

Le dispositif de chargement de ressources se limite à l'automatisation du chargement des fichiers. Il n'existe pas de méthodes standard pour interpréter le contenu d'un fichier de ressources. Chaque programme doit interpréter à sa façon le contenu de ses fichiers de ressources.

Codage de l'application en tenant compte de ces ressources
package cadre;

import java.awt.*;
import java.util.Scanner;
import javax.swing.JFrame;

public class Fenêtre extends JFrame {
   public Fenêtre() {
      Toolkit kit = Toolkit.getDefaultToolkit();
      Dimension dimensionEcran = kit.getScreenSize();
      int largeur = dimensionEcran.width;
      int hauteur = dimensionEcran.height;
      setBounds((largeur-300)/2, (hauteur-200)/2, 300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      setIconImage(kit.getImage(getClass().getResource("../ressources/icone.gif")));
      Scanner titre = new Scanner(getClass().getResourceAsStream("../ressources/titre.txt"));
      setTitle(titre.nextLine());
      setResizable(false);
      setVisible(true);
   }
    
   public static void main(String[] args) {
      new Fenêtre()  ;
   }
}

Nous pouvons proposer une autre approche qui consiste à créer directement des fichiers de configuration. Si le sujet vous intéresse, repportez-vous à l'étude correspondante : Fichier de configuration.

 

Choix du chapitreStructure d'un cadre de fenêtre

La structure de JFrame est étonnement complexe et possède quatre couches superposées comme vous pouvez le constater en observant la figure ci-dessous.

JRootPane

Ce composant est un conteneur qui gère une hiérarchie fixe d'enfants, comprenant toutes les autres couches nécessaires à la fenêtre. Il est possible d'accéder à ces différents couches en faisant appel aux méthodes correspondantes comme : getLayeredPane(), getContentPane(), getGlassPane(). Pour placer un menu, on peut employer la méthode setJMenuBar(). JRootPane possède également un gestionnaire de disposition spécifique qui organise automatiquement la disposition des éléments graphiques placés sur la fenêtre.

JLayeredPane

Ce composant fournit les fonctionnalités d'empilement que requiert Swing pour implémenter les dialogues modaux, les palettes flottantes, les menus contextuels, les bulles d'aide et les effets graphiques de glisser-déplacer.

ContentPane

La couche de contenu est la partie principale de la fenêtre. C'est sur cette couche que vous allez placer tous les différents composants graphiques nécessaire à votre application.

GlassPane

Le panneau de verre ( ou la vitre ) doit être soit caché soit transparent ; faute de quoi, il obscurcit tous les autres composants du JRootPane. Il peut être utilisé pour intercepter les événements souris destinés aux autres composants du JRootPane, et pour l'affichage de graphismes temporaires au dessus de ces composants.

 

 

 

La plupart du temps, nous n'avons pas à nous préoccuper de la racine (JRootPane), de la couche supperposée (JLayeredPane) et de la vitre (GlassPane) ; elles sont nécessaires pour l'organisation de la barre de menus et du contenu, ainsi que pour implémenter l'aspect (look and feel) du cadre. La partie qui intéresse les programmeurs Swing est plus généralement la couche contenue (ContentPane).

Par exemple, si vous désirez proposer une couleur de fond à votre zone de contenu, la première chose qui vient à l'esprit, c'est de faire appel à la méthode setBackground() du cadre de la fenêtre. Vous remarquerez que rien ne se passe. Ce qui est normal, puisque c'est uniquement la zone de contenu qui doit avoir cette couleur là. Il faut donc récupérer d'abord l'objet représentant cette zone particulière au moyen de la méthode getContentPane().

package cadre;

import java.awt.*;
import java.util.Scanner;
import javax.swing.JFrame;

public class Fenêtre extends JFrame {
   public Fenêtre() {
      setTitle("Structure d'un cadre");
      setBounds(100, 100, 300, 200);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      getContentPane().setBackground(Color.GREEN);
      setVisible(true);
   }
    
   public static void main(String[] args) {
      new Fenêtre()  ;
   }
}

 

Choix du chapitre Aspect des fenêtres (look and feel)

Nous avons vu en introduction que 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. Swing peut donc facilement changer d'aspect. Toutefois, les divers types de composants possèdent malgré tout des apparences ressemblantes sur certains points. Ils utilisent notamment la même police et la même grille de couleurs de base. L'ensemble des apparences de composants graphiques s'appellent look-and-feel.

Par défaut, les programmes Swing utilisent l'aspect et le comportement (look and feel) Metal qui est celui proposé par Java et qui offre donc la même apparence quelque soit le système d'exploitation utilisé. Voici quelques uns des aspects possibles :

  1. Metal : javax.swing.plaf.metal.MetalLookAndFeel.
  2. CDE/Motif : com.sun.java.swing.plaf.motif.MotifLookAndFeel.
  3. Windows : com.sun.java.swing.plaf.windows.WindowsLookAndFeel.
  4. Windows Classic : com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel.

Notez que le look and feel Metal est situé dans la paquetage javax.swing. Les autres look and feel se trouvent dans le paquetage com.sun.java et ne doivent pas nécessairement être présents dans chaque implémentation Java. Actuellement, pour des raisons de copyright, les look and feel Windows et Mac sont uniquement fournis avec les versions Windows ou Mac de l'environnement d'exécution de Java.

Il existe deux moyens de choisir le look and feel :

  1. Le premier consiste à placer, dans les répertoires jre/lib, un fichier swing.properties qui donne à la propriété swing.defaultlaf de la classe le look and feel que vous désirez employer.

    swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel

    Attention, vous devez relancer votre programme pour modifier le look and feel de cette manière. Un programme Swing ne lit qu'une seule fois le fichier swing.properties, au démarrage.
  2. La deuxième façon consiste à modifier dynamiquement le look and feel. Appelez la méthode statique UIManager.setLookAndFeel() et passer-lui le nom du look and feel souhaité. Appelez ensuite la méthode statique SwingUtilities.updateComponentTreeUI() pour actualiser l'ensemble des composants. Vous n'avez besoin de fournir qu'un seul composant à cette méthode ; elle trouvera automatiquement les autres.
Pour énumérer toutes les implémentations de look and feel installées, appelez :

UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
String nom = infos[i].getName();
String classe = infos[i].getClassName();

 

Exemple de codage qui permet de choisir son look and feel et de permettre ainsi la transparence des boutons



package cadre;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Fenêtre extends JFrame implements ActionListener {
   private JLabel bienvenue = new JLabel("Bienvenue...");
   private JToggleButton changement = new JToggleButton("Dimensions image normale");
   private PanneauImage panneauImage = new PanneauImage();
   private JPanel panneauSud = new JPanel();
   private JPanel panneauCentre = new JPanel();
   
   private String metal = "javax.swing.plaf.metal.MetalLookAndFeel";
   private String motif = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
   private String windows = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
   private String windowsClassic = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
   
   public Fenêtre() throws Exception {
      setTitle("Transparence");
      setBounds(100, 100, 400, 300);
      setDefaultCloseOperation(EXIT_ON_CLOSE);
      
      UIManager.setLookAndFeel(motif);
      
      bienvenue.setFont(new Font("Arial", Font.ITALIC+Font.BOLD, 54));
      bienvenue.setForeground(new Color(0, 255, 0, 96));
      
      panneauCentre.setOpaque(false);
      panneauCentre.add(bienvenue);
      
      changement.addActionListener(this);
      changement.setOpaque(false);
      changement.setFocusPainted(false);
      changement.setForeground(Color.YELLOW);
      
      panneauSud.setOpaque(false);
      panneauSud.add(changement);
      
      panneauImage.setLayout(new BorderLayout());
      panneauImage.add(panneauCentre);
      panneauImage.add(panneauSud, BorderLayout.SOUTH);
      
      add(panneauImage);

      setVisible(true);
   }
    
   public static void main(String[] args) throws Exception {
      new Fenêtre()  ;
   }

   public void actionPerformed(ActionEvent e) {
      if (changement.isSelected()) {
        panneauImage.dimensionAutomatique = false;
        changement.setText("Taille adaptée à la fenêtre");
      }
      else {
        panneauImage.dimensionAutomatique = true;
        changement.setText("Dimensions image normale");          
      }
      panneauImage.repaint();
   }
}

class PanneauImage extends JComponent {
    boolean dimensionAutomatique = true;
    private Image imageFond = new ImageIcon("Cabane dans un champ.jpg").getImage();
    
   @Override
    public void paintComponent(Graphics fond) {
        if (dimensionAutomatique)
           fond.drawImage(imageFond, 0, 0, getWidth(), getHeight(), null);
        else
           fond.drawImage(imageFond, 0, 0, imageFond.getWidth(null), imageFond.getHeight(null), null);
    }
}

Affichage d'image

Pour récupérer un fichier image et ensuite pouvoir l'afficher, nous avons utiliser la classe ImageIcon. En réalité, cette classe construit une icône dont l'image est stockée dans un fichier. Vous spécifiez le nom de votre fichier en paramètre du constructeur. L'image est alors chargée automatiquement.

Attention, le programme s'interrompt pendant tout le temps de chargement.
.

La classe ImageIcon encapsule une référence à un objet de type Image. C'est cette référence qui nous est utile pour faire du traitement et notamment pour l'afficher dans la zone correspondante. Cette référence à Image s'obtient à partir de la méthode getImage() de la classe ImageIcon.

Vous avez ci-dessous quelques unes des méthodes intéressantes de cette classe ImageIcon :

javax.swing.ImageIcon
ImageIcon()
ImageIcon(byte[] octets)
ImageIcon(Image image)
ImageIcon(String nomFichierImage)
ImageIcon(URL localisationImage)
Construit une icône respectivement : vierge, à partir d'un tableau d'octets, à partir d'un image déjà existante, à partir d'un fichier ou à partir d'une localisation quelconque.
int getIconHeight()
Renvoie la hauteur de l'image.
int getIconWidth()
Renvoie la largeur de l'image.
Image getImage()
Récupère l'image proprement dite.
void paintIcon(Component élément, Graphics surface, int x, int y)
Propose des tracés supplémentaires à l'endroit spécifié.
void setImage(Image image)
Propose une nouvelle image à l'icône.

Cette classe est intéressante. Par contre, comme je l'ai déjà évoquée, elle est bloquante au moment du chargement du fichier image. Vous avez aussi également la possibilité d'utiliser la classe ImageIO qui possède une méthode statique read(). Ici, il sera nécessaire de travailler plutôt avec la classe BufferedImage qui est concrète et qui hérite de la classe abstraite Image. Pour en savoir plus, reportez-vous à la rubrique suivante : Traitement d'images.

 

Choix du chapitre Fenêtre sans cadre

Vous n'êtes pas obligé de prendre systématiquement un cadre de fenêtre. Utilisez, dans ce cas là, la classe JWindow en lieu et place de la classe JFrame. Toutefois, pensez bien qu'il faut prévoir une sortie à votre programme. Voici, en reprenant l'exemple précédent ce que nous pouvons obtenir :

package cadre;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.*;

public class Fenêtre extends JWindow implements ActionListener {
   private JLabel bienvenue = new JLabel("Bienvenue...");
   private JToggleButton changement = new JToggleButton("Dimensions image normale");
   private PanneauImage panneauImage = new PanneauImage();
   private JPanel panneauSud = new JPanel();
   private JPanel panneauCentre = new JPanel();
   private JButton quitter = new JButton("Quitter");
   
   private String metal = "javax.swing.plaf.metal.MetalLookAndFeel";
   private String motif = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
   private String windows = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
   private String classic = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
   
   public Fenêtre() throws Exception {
      Toolkit kit = Toolkit.getDefaultToolkit();
      Dimension d = kit.getScreenSize();
      setBounds((d.width-400)/2, (d.height-300)/2, 400, 300);     
      UIManager.setLookAndFeel(motif);
      bienvenue.setFont(new Font("Arial", Font.ITALIC+Font.BOLD, 54));
      bienvenue.setForeground(new Color(0, 255, 0, 96));     
      panneauCentre.setOpaque(false);
      panneauCentre.add(bienvenue);
      changement.addActionListener(this);
      changement.setOpaque(false);
      changement.setFocusPainted(false);
      changement.setForeground(Color.YELLOW);
      quitter.addActionListener(this);
      quitter.setOpaque(false);
      quitter.setForeground(Color.YELLOW);
      panneauSud.setOpaque(false);
      panneauSud.add(changement);  
      panneauSud.add(quitter);
      panneauImage.setLayout(new BorderLayout());
      panneauImage.add(panneauCentre);
      panneauImage.add(panneauSud, BorderLayout.SOUTH);
      add(panneauImage);
      setVisible(true);
   }
    
   public static void main(String[] args) throws Exception {
      new Fenêtre()  ;
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource()==quitter) System.exit(0);
      if (changement.isSelected()) {
        panneauImage.dimensionAutomatique = false;
        changement.setText("Taille adaptée à la fenêtre");
      }
      else {
        panneauImage.dimensionAutomatique = true;
        changement.setText("Dimensions image normale");          
      }
      panneauImage.repaint();
   }
}

class PanneauImage extends JComponent {
    boolean dimensionAutomatique = true;
    private BufferedImage imageFond; 
    
    public PanneauImage() throws IOException {
       imageFond = ImageIO.read(new File("Cabane dans un champ.jpg"));
       Border incrustée = BorderFactory.createLoweredBevelBorder();
       Border cadre = BorderFactory.createLineBorder(new Color(0, 255, 0, 96), 5);
       setBorder(BorderFactory.createCompoundBorder(cadre, incrustée)); 
    }
    
   @Override
    public void paintComponent(Graphics fond) {
        if (dimensionAutomatique)
           fond.drawImage(imageFond, 0, 0, getWidth(), getHeight(), null);
        else
           fond.drawImage(imageFond, 0, 0, imageFond.getWidth(null), imageFond.getHeight(null), null);
    }
}

Les méthodes setTitle() et setDefaultCloseOperation() ne sont plus nécessaires. Par contre, vous êtes obligé de prévoir un bouton supplémentaire pour permettre la sortie du programme. Pour le reste, nous retrouvons exactement le codage normalement prévu pour un JFrame.

 

Choix du chapitre Accéder à la barre des tâches

Une application Java peut faire acte de présence dans la barre des tâches du système hôte comme le font certaines applications natives. La classe java.awt.SystemTray s'occupe de gérer la question. Le terme SystemTray provient de la terminologie KDE et trouve sa correspondance avec la zone de notification de Gnome et la zone d'état de la barre des tâches de Windows. Tous ces bureaux sont donc supportés par Java. Ensuite, chaque icône représentant le programme à exécuter est gérée par la classe java.awt.TrayIcon.

La classe SystemTray est un singleton créé au démarrage de la JVM et représente la barre des tâches. Si la barre des tâches n'est pas disponible, la méthode isSupported() doit vous en informer. Questionner cette méthode est incontournable. Si la méthode renvoie true, vous pouvez alors obtenir l'instance de la barre des tâches par la méthode getSystemTray(), le point d'accès statique du singleton. SystemTray est un conteneur d'icônes. Ces dernières sont donc encapsulées par TrayIcon. Il est tout à fait possible d'insérer plusieurs icônes à l'aide de la méthode SystemTray.add() et de les retirer avec la méthode SystemTray.remove().

Une icône se construit, à partir de la classe TrayIcon, avec au moins une image. A cela, nous pouvons ajouter une bulle d'aide et un menu surgissant au moment de la construction. Nous pouvons aussi introduire ces deux critères supplémentaites plutard, respectivement, au moyen des méthodes setToolTip() et setPopupMenu(). Il est souhaitable que la dimension de l'icône soit adaptée à la barre des tâches, utilisez pour cela la méthode setImageAutoSize(true). Ensuite, le programme s'exécute normalement lorsque nous double-cliquons sur l'icône. Vous devez donc gérer cet événement en prévoyant un ActionListener. Il est bien sûr tout à fait possible d'intégrer d'autres types d'événement (le survol de la souris est déjà géré par défaut puisqu'une bulle d'aide apparaît). Précisons pour terminer, que nous pouvons utiliser la méthode displayMessage() qui permet d'afficher un message sous la forme d'une bulle d'aide avec un titre et un bouton de fermeture supplémentaires.

Vous avez ci-dessous l'ensemble des méthodes utiles de ces deux classes, avec leurs paramètres respectifs :

java.awt.SystemTray
void add(TrayIcon icône)
Ajoute une nouvelle icône à la barre des tâches.
SystemTray getSystemTray()
Récupère l'instance qui représente la barre des tâches.
TrayIcon[] getTrayIcons()
Récupère, sous forme de tableau, l'ensemble des icônes déjà présentes dans la barre des tâches.
Dimension getTrayIconSize()
Donne la dimension prévue pour chaque icône dans la barre des tâches.
boolean isSupported()
Indique la présence d'une barre de tâches.
void remove(TrayIcon icône)
Enlève une icône à la barre des tâches.
java.awt.TrayIcon
TrayIcon(Image image)
TrayIcon(Image image, String aide)
TrayIcon(Image image, String aide, PopupMenu menuSurgissant)
Construit une icône en précisant son image avec éventuellement une aide contextuelle et un menu surgissant.
void addActionListener(ActionListener écouteur)
Ajoute un écouteur d'événement qui gère l'action double-clic sur l'icône.
void addMouseListener(MouseListener écouteur)
Ajoute un écouteur d'événement qui gère le passage de la souris sur l'icône.
void addMouseMotionListener(MouseMotionListener écouteur)
Ajoute un écouteur d'événement qui gère le déplacement de la souris sur l'icône.
void displayMessage(String titre, String intitulé, TrayIcon.MessageType icône)
Propose une bulle d'aide avec un titre et une icônographie adaptée. Voici d'ailleurs le type de message que vous pouvez mettre en oeuvre :
--- ERROR : le message correspond à une erreur de manipulation.
--- INFO : message d'information.
--- NONE : Aucune icône particulière.
--- WARNING : message d'alerte.
String getActionCommand()
Renvoie la chaîne de caractères correspondant à la commande lié à l'événement de type ActionEvent.
boolean isImageAutoSize()
Indique si la retaille automatique de l'icône est valide ou pas.
void removeActionListener(ActionListener écouteur)
void removeMouseListener(MouseListener écouteur)
void removeMouseMotionListener(MouseMotionListener écouteur)
Enlève les écouteurs correspondants.
void setActionCommand(String commande)
Spécifie le texte de la commande à exécuter lors du double-clic de la souris sur l'icône.
void setImage(Image image)
Change l'image de l'icône.
void setImageAutoSize(boolean ajusté)
Impose une retaille automatique de l'icône.
void setPopupMenu(PopupMenu popup)
Place un nouveau menu contextuel.
void setToolTip(String message)
Met en place une nouvelle bulle d'aide.

Mise en oeuvre de ces différents composants

Nous allons utiliser nos nouvelles connaissances afin de mettre en oeuvre un programme de conversion entre les €uros et les francs. Cette application sera accessible depuis une icône sur la notification de la barre des tâches. Voici quelques illustrations qui montrent les différentes phases d'utilisation.

Lorsque vous mettez en oeuvre une icône en barre de tâche, il faut bien comprendre que votre programme doit fonctionner en permanence afin qu'il soit constamment à l'écoute d'un événement éventuel venant de l'utilisateur. Au minimum, cet événement correspond au double-click sur l'icône elle-même. Lorsque cet événement a lieu, l'action demandée est simplement d'afficher la fenêtre qui, par ailleurs, est déjà créée.


Attention, pour une fois, lorsque vous cliquez sur le bouton de fermeture de la fenêtre, celle-ci doit juste disparaître sans clôturer le programme, ce qui est justement, nous l'avons vu, le comportement par défaut d'un JFrame. Du coup, vous n'avez donc plus à utiliser la méthode setDefaultCloseOpération().

Vous avez également la possibilité de prévoir un menu surgissant. De cette manière, il sera possible de quitter définitivement le programme. Comme tout menu surgissant, vous le faites apparaître avec le clic bouton droit de la souris. Vous devez donc prévoir la gestion des événements des différentes éléments du menu.


Le fait de quitter le programme enlève automatiquement l'icône dans la barre des tâches. Il peut alors être judicieux de prévoir un message d'avertissement en conséquence :


 

Codage correspond de cette application
 1 package conversion;
 2 
 3 import java.awt.*;
 4 import java.awt.event.*;
 5 import javax.swing.*;
 6 
 7 public class Conversion extends JFrame implements ActionListener {
 8    private JTextField saisie = new JTextField("0");
 9    private JButton conversion = new JButton("Conversion");
10    private JLabel résultat = new JLabel("0 Franc");
11    private JPanel panneau = new JPanel();
12    private Image icône;
13    private static Conversion convertisseur;
14    private TrayIcon tray;
15    
16    public Conversion() {
17       setTitle("Conversion €uros -> Francs");
18       Toolkit kit = Toolkit.getDefaultToolkit();
19       Dimension dimension = kit.getScreenSize();
20       setBounds(dimension.width-290, dimension.height-140, 280, 80);
21       icône = kit.getImage(getClass().getResource("icone.gif"));
22       setIconImage(icône);
23       panneau.setLayout(new BorderLayout());
24       saisie.setHorizontalAlignment(JTextField.RIGHT);
25       panneau.add(saisie);
26       conversion.addActionListener(this);
27       panneau.add(conversion, BorderLayout.EAST);
28       add(panneau, BorderLayout.NORTH);
29       résultat.setHorizontalAlignment(JLabel.RIGHT);  
30       résultat.setBorder(BorderFactory.createEtchedBorder());
31       add(résultat, BorderLayout.SOUTH);
32       getContentPane().setBackground(Color.GREEN);
33       setResizable(false);
34       iconTray();
35    }
36    
37    public static void main(String[] args) {
38       convertisseur = new Conversion();  
39    }
40 
41    public void actionPerformed(ActionEvent e) {
42        final double TAUX = 6.55957;
43        double €uro = Double.parseDouble(saisie.getText());
44        double franc = €uro * TAUX;
45        résultat.setText(franc+" Francs");
46    }
47    
48    private void iconTray() {
49       if (SystemTray.isSupported()) {
50          // construction du menu et gestion des événements
51          PopupMenu popup = new PopupMenu();
52          MenuItem démarrer = new MenuItem("Afficher");
53          MenuItem quitter = new MenuItem("Quitter");
54          ActionListener afficher = new ActionListener() {
55             public void actionPerformed(ActionEvent e) {
56                convertisseur.setVisible(true);
57             }
58          };         
59          ActionListener arrêter = new ActionListener() {
60             public void actionPerformed(ActionEvent e) {
61                try {
62                   tray.displayMessage("Arrêt de la conversion", "A bientôt...", TrayIcon.MessageType.INFO);
63                   Thread.sleep(4000);
64                } 
65                catch (InterruptedException ex) { }
66                finally { System.exit(0);}
67             }
68          };  
69          démarrer.addActionListener(afficher);
70          quitter.addActionListener(arrêter);
71          popup.add(démarrer);
72          popup.add(quitter);
73          // création de l'icône
74          tray = new TrayIcon(icône, "Conversion entre les €uros et les francs", popup);
75          tray.setImageAutoSize(true);
76          tray.addActionListener(afficher);     
77          // placement de l'icône dans la barre de tâche
78          try {    
79             SystemTray.getSystemTray().add(tray);
80          } 
81          catch (AWTException ex) {}
82       }
83    }
84 }

Vous remarquez que la mise en oeuvre est très facile. Très peu de méthodes sont utilisées. En réalité, nous passons beaucoup de temps à fabriquer le menu surgissant avec la gestion d'événement adaptée.