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.
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 :
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.
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.
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 :
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 :
Au vue de ces différentes méthodes, nous pouvons rajouter quelques commentaires supplémentaires :
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() ; } }
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() ; } }
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.
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 :
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 :
En Java, un fichier associé de ce type est appelée une ressource.
.
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 :
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.
.
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.
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.
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.
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.
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.
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.
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() ; } }
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 :
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 :
UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
String nom = infos[i].getName();
String classe = infos[i].getClassName();
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); } }
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 :
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.
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.
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 :
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 :
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.