Au travers de cette étude, nous allons nous consacrer aux composants qui s'occupent plus particulièrement des aspects ergonomiques qui offrent ainsi des renseignements supplémentaires, comme par exemple les bulles d'aide ou les barres de progression.
Nous nous intéresserons également à d'autres composants, qui font suite à l'étude précédente et qui offrent des choix ou des orientations d'évolution dans l'application, qui sont généralement plutôt des conteneurs, comme par exemple les barres d'outil ou les panneaux à onglets.
Les étiquettes (ou libellés) sont des composants qui contiennent du texte. Ils ne possèdent pas d'ornements, telle une bordure, et ne réagissent pas aux entrées de l'utilisateur. Vous pouvez employer une étiquette pour identifier des composants comme les champs de saisie, qui, contrairement aux boutons, n'ont pas de libellé.
Pour associer une étiquette à un composant, procédez ainsi :
Le constructeur d'un objet JLabel permet de spécifier le texte initial ou l'icône et, en option, l'alignement du contenu. Vous pouvez utilisez l'interface du contenu SwingConstants pour spécifier l'alignement grâce aux constantes suivantes qu'elle définit : LEFT, RIGHT, CENTER, TOP et BOTTOM. La classe JLabel est l'une des classes qui implémentent cette interface. Donc pour spécifier l'une de ces constantes, vous pouvez tout aussi bien écrire SwingConstants.RIGHT ou JLabel.RIGHT.
A tout moment, il est possible de changer les valeurs initiales proposées durant la phase de création. Ainsi, vous avez le loisir :
- de modifier le libellé au moyen de la méthode setText().
- de proposer une autre icône au moyen de la méthode setIcon().
- de changer d'alignement au moyen de la méthode setHorizontalAlignment().
Notez que la prise en compte d'un changement ne nécessite aucun appel supplémentaire tel que repaint(), validate() ou invalidate().
.
Contrairement à la plupart des autres composants, une étiquette n'a ni bordure, ni couleur de fond (le fond est transparent). Ainsi, la méthode setBackground() peut toujours lui être appliquée, mais elle est sans effet. En revanche, nous pouvons toujours agir sur la couleur du texte à l'aide de la méthode setForeground(). Malgré les apparences, nous pouvons toutefois proposer un affichage relativement sophistiqué.
Commençons par découvrir le comportement normal d'une étiquette en proposant toutefois un libellé avec son icône. Par ailleurs, nous avons la possibilité de régler l'espacement qui existe entre l'icône et le texte du libellé au moyen de la méthode setIconTextGap(int). J'en profite donc pour prévoir un écartement de 10 pixels entre l'icône à gauche et le libellé à droite :
import java.awt.*; import javax.swing.*; public class Etiquettes extends JFrame { private JLabel bienvenue = new JLabel("Bienvenue", new ImageIcon("icone.gif"), JLabel.CENTER); public Etiquettes() { super("Etiquette"); bienvenue.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 28)); bienvenue.setForeground(Color.BLUE); bienvenue.setIconTextGap(10); add(bienvenue); getContentPane().setBackground(Color.WHITE); setSize(250, 150); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Etiquettes(); } }
Nous pouvons modifier le comportement par défaut et ainsi proposer une apparence plus personnalisée comme nous l'avons fait pour l'écartement. Ainsi, comme la méthode setHorizontalAlignment() permet de positionner le libellé dans le sens horizontal, il est également possible de gérer la position verticale du libellé au moyen de la méthode setVerticalAlignment(). Dans ce genre d'idée, nous pouvons changer le placement par défaut du texte par rapport à l'icône au travers, respectivement des méthodes setHorizontalTextPosition() et setVerticalTextPosition() :
... public Etiquettes() { super("Etiquette"); bienvenue.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 28)); bienvenue.setForeground(Color.BLUE); bienvenue.setVerticalAlignment(JLabel.BOTTOM); bienvenue.setHorizontalTextPosition(JLabel.CENTER); bienvenue.setVerticalTextPosition(JLabel.BOTTOM); add(bienvenue); ...
Nous pouvons nous servir du composant JLabel comme support d'image. Attention toutefois, il est nécessaire que la taille de l'image soit préalablement adaptée à la surface que nous désirons prendre en compte. Il faut donc que l'image soit retaillée en dehors de l'application ou alors que le programme lui-même la retaille avant de la proposée à l'étiquette :
import java.awt.*; import javax.swing.*; public class Etiquettes extends JFrame { private JLabel bienvenue = new JLabel(new ImageIcon("rouge-gorge.png")); public Etiquettes() { super("Etiquette"); add(bienvenue); getContentPane().setBackground(Color.BLACK); setSize(308, 270); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Etiquettes(); } }
Il est possible de rendre le libellé inactif. Le texte devient alors grisé quelque soit sa couleur initiale. Nous pouvons encore accentuer cette apparence de non activité en proposant une icône adaptée au travers de la méthode setDisabledIcon().
Un libellé sert souvent pour des champs de texte qui servent à saisir les valeurs désirées par l'utilisateur. Il est possible de faire en sorte que la saisie prennent le focus à partir d'un raccourci clavier. Le problème, c'est que dans la zone de saisie, rien n'apparaît pour indiquer quel raccourci nous avons choisi. La solution consiste à faire apparaitre ce choix au niveau du libellé associé à cette zone de saisie au moyen des méthodes setDisplayedMnemonic(int) et setDisplayedMnemonic(char).
Dans la première méthode, vous spécifiez le code de la touche clavier. Cela s'avère utile dans le cas où vous désirez prendre en compte les touches annexes <Ctrl>, <Alt> et <Shift> alors que dans la seconde méthode, vous spécifiez uniquement le caractère qui vous intéresse.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Etiquettes extends JFrame implements ActionListener { private JLabel bienvenue = new JLabel("Inactif ", new ImageIcon("icone.gif"), JLabel.RIGHT); private Timer timer = new Timer(500, this); public Etiquettes() { super("Etiquette"); bienvenue.setEnabled(false); bienvenue.setFont(new Font("Arial", Font.BOLD+Font.ITALIC, 28)); bienvenue.setForeground(Color.RED); bienvenue.setDisabledIcon(new ImageIcon("inactif.gif")); bienvenue.setDisplayedMnemonic('a'); add(bienvenue); getContentPane().setBackground(Color.WHITE); timer.start(); setSize(250, 200); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Etiquettes(); } public void actionPerformed(ActionEvent e) { bienvenue.setText(bienvenue.isEnabled() ? "Inactif " : "Actif "); bienvenue.setEnabled(!bienvenue.isEnabled()); } }
Si la propriété text commence par <html>, l'étiquette est formatée en texte HTML et peut contenir ainsi de multiples fontes sur plusieurs lignes, avec même la possibilité d'incorporer plusieurs images, des tableaux, etc.
Voici ci-dessous un exemple qui permet d'afficher une image avec les dimensions souhaitées, placée avec un encadrement et un libellé au dessous, tout ceci réalisé par un simple tableau HTML.
import javax.swing.*; public class Etiquettes extends JFrame { public Etiquettes() { super("Etiquette"); String html = "<html><table bgcolor=#FF0000>" +"<tr><td><img src=\"file:///C:/Photos/oiseau.jpg\" width=225 height=180 /></td></tr>" +"<tr><th>Oiseau.jpg</th></tr>" +"</table></html>"; add(new JLabel(html, JLabel.CENTER)); setSize(308, 270); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Etiquettes(); } }
Pour conclure sur ce sujet, je vous propose de donner la liste des photos présentes dans un répertoire donné sous forme de vignettes. Cette fois-ci, la démarche suivie consiste à retailler systématiquement chacune des photos pour construire des vignettes sous forme d'icônes :
import java.awt.geom.AffineTransform; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; public class Etiquettes extends JFrame { private File[] fichiers; private JPanel panneau = new JPanel(); private final int largeur = 170; public Etiquettes() throws IOException { super("Etiquette"); fichiers = new File("C:/Photos/").listFiles(); for (File fichier : fichiers) { JLabel étiquette = new JLabel(fichier.getName(), vignette(fichier), JLabel.LEFT); étiquette.setHorizontalTextPosition(JLabel.CENTER); étiquette.setVerticalTextPosition(JLabel.BOTTOM); panneau.add(étiquette); } add(panneau); setSize(550, 190); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private Icon vignette(File fichier) throws IOException { BufferedImage source = ImageIO.read(fichier); BufferedImage image = new BufferedImage(largeur, largeur*3/4, source.getType()); double ratio = largeur / (double)source.getWidth(); AffineTransform retailler = AffineTransform.getScaleInstance(ratio, ratio); int interpolation = AffineTransformOp.TYPE_BICUBIC; AffineTransformOp retaillerImage = new AffineTransformOp(retailler, interpolation); retaillerImage.filter(source, image); return new ImageIcon(image); } public static void main(String[] args) throws IOException { new Etiquettes(); } }
Il est possible, lorsque la souris passe au dessus d'un composant, de faire apparaître un petit message sous le curseur et qui précise le rôle du composant, ce que nous appelons communément, une bulle d'aide. Cette bulle d'aide est assurée par la classe JToolTip. Généralement, toutefois, vous n'avez pas besoin d'implémenter directement cette classe. En effet, tous les composants graphiques, qui héritent donc de JComponent, peuvent accéder à cette fonctionnalité au travers de la méthode setToolTipText(String). Il suffit juste de préciser le texte désiré, et la bulle d'aide s'affichera alors automatiquement à chaque passage du curseur de la souris au dessus de ce composant.
A titre d'exemple, je vous propose de revoir l'application précédente, et de faire en sorte que lorsque l'utilisateur passe le curseur de la souris au dessus d'une étiquette, nous voyons une bulle d'aide qui apparaît en indiquant les dimensions de l'image originale.
import java.awt.geom.AffineTransform; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; public class Etiquettes extends JFrame { private File[] fichiers; private JPanel panneau = new JPanel(); private final int largeur = 170; public Etiquettes() throws IOException { super("Etiquette"); fichiers = new File("C:/Photos/").listFiles(); for (File fichier : fichiers) { JLabel étiquette = new JLabel(fichier.getName(), vignette(fichier), JLabel.LEFT); étiquette.setHorizontalTextPosition(JLabel.CENTER); étiquette.setVerticalTextPosition(JLabel.BOTTOM); étiquette.setToolTipText(aide(fichier)); panneau.add(étiquette); } add(panneau); setSize(550, 190); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private Icon vignette(File fichier) throws IOException { BufferedImage source = ImageIO.read(fichier); BufferedImage image = new BufferedImage(largeur, largeur*3/4, source.getType()); double ratio = largeur / (double)source.getWidth(); AffineTransform retailler = AffineTransform.getScaleInstance(ratio, ratio); int interpolation = AffineTransformOp.TYPE_BICUBIC; AffineTransformOp retaillerImage = new AffineTransformOp(retailler, interpolation); retaillerImage.filter(source, image); return new ImageIcon(image); } private String aide(File fichier) { Icon icône = new ImageIcon(fichier.getName()); return icône.getIconWidth()+" x "+icône.getIconHeight(); } public static void main(String[] args) throws IOException { new Etiquettes(); } }
Dans les études précédentes, nous avons très souvent utilisé le panneau avec barres de défilement. Nous profitons de ce chapitre pour le connaître un peu mieux. JScrollPane est un conteneur pouvant héberger un composant. Autrement dit, un JScrollPane enveloppe un autre composant. Par défaut, si le composant enveloppé est plus grand que le JScrollPane, celui-ci fournit des barres de défilement. Par contre, si le composant interne devient plus petit, ces barres disparaissent instantanément. Ainsi, tout se fait automatiquement suivant la capacité actuelle du composant enveloppé. JScrollPane gère les événements en provenance des barres de défilement et affiche la partie appropriée du composant enveloppé.
Techniquement, JScrollPane est un Container, mais un drôle de conteneur. Il possède en effet son propre gestionnaire de placement, lequel ne peut pas être remplacé. Il ne peut satisfaire qu'un seul composant à la fois. Cela semble très limitatif, mais à tort. Pour placer de nombreux objets dans un JScrollPane, il suffit de placer ces composants dans un JPanel intermédiaire, quel que soit le gestionnaire de placement souhaité, et de placer ensuite ce panneau dans le JScrollPane.
Généralement, lorsque vous désirez placer des barres de défilement sur un objet quelconque, il suffit de faire appel au constructeur avec un seul paramètre qui précise quel doit être le composant enveloppé. Dans ce cas là, nous obtenons, suivant le besoin, les barres de défilement horizontale et verticale. Il existe toutefois des situations où nous souhaitons avoir qu'une seule barre active. Nous pouvons alors choisir d'autres constructeurs qui gèrent les différentes situations spécifiques.
Par défaut, les politiques sont HORIZONTAL_SCROLLBAR_AS_NEEDED et VERTICAL_SCROLLBAR_AS_NEEDED.
.
Cette classe est donc un conteneur permettant de faire défiler un composant enfant horizontalement et verticalement. Le composant à faire défiler n'est pas un enfant direct du JScrollPane et ne doit donc pas être ajouté directement avec la méthode add(). Il s'agit en fait d'un JViewport (composant qui affiche une partie du composant enfant qu'il contient que nous allons décrire plus loin) contenu à l'intérieur du JScrollPane. Nous spécifions le composant à défiler en le passant au constructeur JScrollPane() ou à la méthode setViewportView(Component).
Tout type de composant peut être utilisé, mais les composants qui implémentent l'interface Scrollable fonctionnent mieux.
.
Voici quelques méthodes utiles qui permettent de gérer l'apparence de ce panneau particulier et de façon plus personnalisé :
Le panneau avec barre de défilement comporte un objet interne représenté par la classe JViewport. Ce composant affiche une partie du composant enfant qu'il contient. Il définit des méthodes servant à faire défiler efficacement le composant enfant à l'intérieur de la zone de visualisation.
Si vous devez gérer les événements associés aux barres de défilement pour avoir un comportement plus personnalisé, vous devez systématiquement passer par cet objet. Ce composant est en effet capable de prendre en compte les événements de type ChangeEvent. Vous devez donc implémenter un écouteur de type ChangeListener et redéfinir la méthode stateChanged(ChangeEvent) prévue par cette interface. La prise en compte de ces événements se fait au travers de la méthode addChangeListener().
Cet objet est également très intéressant pour récupérer les coordonnées ou la zone rectangulaire associées à la partie visible, ou bien pour imposer une nouvelle vue par programme. Voici quelques méthodes qui me paraissent intéressante à connaître :
Dans le chapitre suivant, nous implémenterons une application qui prend en compte les événements associés aux barres de défilement du panneau. Nous en profiterons pour utiliser queques unes des méthodes spécifiques à cette classe JViewport.
A titre d'exercice, je vous propose de reprendre l'application précédente en proposant un ascenceur uniquement dans le sens vertical. Grâce à des JLabel(s), les vignettes possèderont en plus des informations supplémentaires sur chacune des photos stockées sur le disque dur. Ainsi, nous précisons le nom du fichier, la dimension de l'image originale, le poids en octets ainsi que la date de stockage.
import java.awt.geom.AffineTransform; import java.awt.image.*; import java.io.*; import java.text.MessageFormat; import javax.imageio.*; import javax.swing.*; public class Etiquettes extends JFrame { private File[] fichiers; private Box panneau = Box.createVerticalBox(); private int largeur, hauteur; private JScrollPane ascenceur = new JScrollPane(panneau, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); private String html = "<html><table>" +"<tr><td>{0}</td></tr>" +"<tr><td>{1} x {2}</td></tr>" +"<tr><td>{3} octets</td></tr>" +"<tr><td>{4, date, full}</td></tr>" +"</table></html>"; public Etiquettes() throws IOException { super("Etiquette"); fichiers = new File("C:/Photos/").listFiles(); for (File fichier : fichiers) { Icon icône = vignette(fichier); String intitulé = MessageFormat.format(html, fichier.getName(), largeur, hauteur, fichier.length(), fichier.lastModified()); JLabel étiquette = new JLabel(intitulé, icône, JLabel.LEFT); // étiquette.setVerticalTextPosition(JLabel.TOP); panneau.add(Box.createVerticalStrut(5)); panneau.add(étiquette); } JTextField entête = new JTextField("Nombre de photos : "+fichiers.length); entête.setHorizontalAlignment(JTextField.CENTER); entête.setEditable(false); ascenceur.setColumnHeaderView(entête); ascenceur.setBorder(BorderFactory.createRaisedBevelBorder()); add(ascenceur); setSize(400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private Icon vignette(File fichier) throws IOException { BufferedImage source = ImageIO.read(fichier); largeur = source.getWidth(); hauteur = source.getHeight(); BufferedImage image = new BufferedImage(200, 150, source.getType()); double ratio = 200 / (double)largeur; AffineTransform retailler = AffineTransform.getScaleInstance(ratio, ratio); int interpolation = AffineTransformOp.TYPE_BICUBIC; AffineTransformOp retaillerImage = new AffineTransformOp(retailler, interpolation); retaillerImage.filter(source, image); return new ImageIcon(image); } public static void main(String[] args) throws IOException { new Etiquettes(); } }
Un panneau divisé est un conteneur spécial gérant deux composants, chacun dans son propre sous-panneau. Une barre de division ajuste la taille de chaque sous-panneau. Dans un afficheur de document, nous pourrions utiliser un panneau divisé pour afficher la table des matières à côté du document complet.
Un panneau divisé est représenté par la classe JSplitPane. Cette classe est donc un conteneur qui se divise horizontalement ou verticalement pour afficher deux enfants. L'orientation du pavé est spécifié par la propriété orientation, qui doit être l'une des deux constantes HORIZONTAL_SPLIT ou VERTICAL_SPLIT.
Les deux enfants sont spécifiés par une paire de propriétés qui dépendent de l'orientation du JPlitPane :
La position du diviseur entre les deux pavés d'un JSplitPane peut être donnée par la méthode setDividerLocation(). L'argument peut alors être un entier spécifiant la position en pixels ou un double entre 0.0 et 1.0 qui spécifie un pourcentage de la taille du JSplitPane.
Le composant JSplitPane permet à l'utilisateur d'ajuster les tailles relatives des deux enfants en faisant glisser le diviseur apparaissant entre les deux enfants. L'ajustement est cependant contraint afin qu'un enfant ne soit jamais plus petit que sa taille minimale. Si nous spécifions à la méthode setContinuousLayout() la valeur true, les enfants sont retaillés pendant que l'utilisateur entraîne le diviseur. Au contraire, si nous spécifions la valeur false à cette méthode, les composants de l'enfant ne change pas de taille jusqu'à ce que l'utilisateur ait finit le déplacement.
Il est possible de régler la taille du diviseur au moyen de la méthode setDividerSize().
.
Nous pouvons spécifier tous les comportement que nous venons de décrire dès la phase de création. Ainsi, nous pouvons choisir l'orientation de la division, incorporer les enfants qui feront partis du panneau divisé, et spécifier enfin si les enfant devront être automatiquement retaillés durant le déplacement du diviseur.
Voici la liste des constructeurs mis à votre disposition :
Pour valider toutes les fonctionnalités d'un panneau divisé ainsi que la gestion des événements associées à un panneau avec barres de défilement, je vous propose de mettre en oeuvre une application qui permet de visualiser une photo à la fois sous forme de vignette et aussi en grand format.
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class Etiquettes extends JFrame implements ChangeListener { private ImageIcon icône = new ImageIcon("oiseau.jpg"); private double ratio = icône.getIconWidth() / (double)icône.getIconHeight(); private Image photo = icône.getImage(); private JLabel grandFormat = new JLabel(icône); private JScrollPane ascenceur = new JScrollPane(grandFormat); private JViewport vue = ascenceur.getViewport(); private Zone zone = new Zone(); private JSplitPane diviseur = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, zone, ascenceur); public Etiquettes(){ super("Etiquette"); diviseur.setDividerLocation(170); vue.addChangeListener(this); add(diviseur); setSize(500, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private class Zone extends JComponent { Rectangle rect; double proportion; public Zone() { addMouseListener(new Souris()); } @Override protected void paintComponent(Graphics g) { g.drawImage(photo, 0, 0, getWidth(),(int) (getWidth() / ratio), null); g.setColor(Color.RED); g.drawRect(rect.x, rect.y, rect.width, rect.height); } @Override public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); proportion = (double)icône.getIconWidth()/getWidth(); } void traceRectangle(Rectangle r) { int x = (int) (r.x / proportion); int y = (int) (r.y / proportion); int largeur = (int) (r.width / proportion); int hauteur = (int) (r.height / proportion); rect = new Rectangle(x , y, largeur, hauteur); repaint(); } private class Souris extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { int x = (int) ((e.getX() - rect.width / 2) * proportion); int y = (int) ((e.getY() - rect.height / 2) * proportion); vue.setViewPosition(new Point(x, y)); } } } public static void main(String[] args) { new Etiquettes(); } public void stateChanged(ChangeEvent e) { zone.traceRectangle(vue.getViewRect()); } }
Il arrive souvent que nous ayons besoin de présenter un ensemble de documents dans une même application. Pour avoir une vue de chacun de ces documents, deux solutions peuvent se présenter. La première consiste à avoir autant de fenêtre fille que de document. Cette approche à connu un grand succès à une certaine époque. Le problème c'est que l'interface visuelle est vite surchargée. Nous préférons maintenant avoir plus de sobriété en plaçant chaque document dans un panneau dédié qui est alors accessible aux travers d'onglets. Cette deuxième approche utilise une seule fenêtre et chaque document peut ainsi être vite consulté.
Un panneau à onglets est représenté par la classe JTabbedPane. Ce composant est un conteneur acceptant un nombre quelconque d'enfants. Il n'affiche qu'un enfant à la fois, mais affiche un onglet pour chacun. L'utilisateur peut alors cliquer sur un onglet pour afficher l'enfant correspondant.
La mise en oeuvre d'un panneau, avec un certain nombres d'onglets associés à un ensemble de documents quelconques, se fait en deux étapes. Dans un premier temps, vous créez votre panneau vierge de tout onglet, en spécifiant juste éventuellement comment les onglets devront être présentés. Ensuite, une fois que le panneau existe, vous placerez successivement l'ensemble des documents représentés par des onglets libellés à l'aide de la méthode addTab().
Voici donc comment construire un panneau à onglets :
Une fois que le panneau est en place, il faut ensuite créer les différents onglets. Bien que nous puissions ajouter un enfant à un JTabbedPane avec les méthodes add() standard, cela n'offre pas beaucoup de souplesse pour spécifier le contenu de son onglet. JTabbedPane offre en fait plusieurs méthodes addTab() qui permettent de spécifier l'enfant avec son libellé, son icône et sa bulle d'aide associée.
L'intérêt des onglets c'est que l'utilisateur dispose d'un moyen simple et ergonomique pour choisir l'élément qu'il désire voir appraître. Il est toutefois possible par programme de demander à visualiser un onglet spécifique. Pour cela, nous employons soit la méthode setSelectedComponent(), soit la méthode setSelectedIndex().
Même si le JTabbedPane ne présente qu'un ensemble de composants à la fois, tous les composants de toutes les pages se trouvent en mémoire en même temps. Si certains de vos composants accaparent le temps CPU ou la mémoire, placez-les dans un état dormant lorsqu'ils sont invisibles. Justement, la méthode setEnabledAt() permet d'activer ou de désactiver un onglet d'après sa position dans la liste.
Par ailleurs, il est possible à tout moment de supprimer les onglets qui ne nous intéressent plus au moyen des méthodes remove() ou removeAll().
.
Par défaut, les onglets apparaissent en haut du JTabbedPane. Nous pouvons cependant surcharger cette valeur par défaut au moyen de la méthode setTabPlacement(). L'argument de cette méthode doit être une constante TOP, BOTTOM, LEFT ou RIGHT.
Par défaut, lorsqu'il y a trop d'onglets sur une même ligne, JTabbedPane les répartit automatiquement sur plusieurs lignes. Ce comportement, donnant l'apparence d'un tiroir à dossiers suspendus, est assez cohérent avec la notion d'onglet, mais il nécessite aussi que lorsque vous sélectionnez un onglet de la rangée du fond, les autres soient ré-agencés afin que celui-ci apparaisse au premier plan. Il est également possible de configurer un panneau à onglet, à l'aide de la méthode setTabLayoutPolicy(), pour qu'il n'utilise qu'une seule rangée d'onglets, avec la règle d'affichage JTabbedPane.SCROLL_TAB_LAYOUT.
Il est possible de savoir à tout moment si l'utilisateur change d'onglet et de connaître ainsi quel est celui qui a été sélectionné afin de proposer une action sépcifique ou de récupérer tous les renseignements nécessaires.
Comme beaucoup, JTabbedPane est capable de prendre en compte les événements de type ChangeEvent. Vous devez donc implémenter un écouteur de type ChangeListener et redéfinir la méthode stateChanged(ChangeEvent) prévue par cette interface. La prise en compte de ces événements se fait au travers de la méthode addChangeListener().
Je vous propose maintenant de mettre en oeuvre une application qui permet de visualiser l'ensemble des photos présentes dans un répertoire particulier. Chaque image disposera de son propre onglet. Ainsi il sera facile de visualiser celle qui nous intéresse. A titre d'exemple et afin de bien comprendre les fonctionnalités des onglets, le premier sera rendu inactif par programme alors que le dernier sera automatiquement sélectionné par défaut. Enfin, les onglets dont les images auront déjà été sélectionnées seront paint en rouge.
import java.awt.*; import java.io.*; import javax.swing.*; import javax.swing.event.*; public class Etiquettes extends JFrame implements ChangeListener { private String répertoire = "C:/Photos/"; private JTabbedPane onglets = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); private JLabel état = new JLabel(" "); public Etiquettes(){ super("Photos"); for (File fichier : new File(répertoire).listFiles()) { String nom = fichier.getName(); Icon icône = new ImageIcon(répertoire+nom); String aide = icône.getIconWidth()+" x "+icône.getIconHeight(); onglets.addTab(nom, new ImageIcon("image.gif"), new JScrollPane(new JLabel(icône)), aide); } onglets.setSelectedIndex(onglets.getTabCount()-1); onglets.setEnabledAt(0, false); onglets.setBackground(Color.YELLOW); onglets.addChangeListener(this); add(onglets); add(état, BorderLayout.SOUTH); setSize(500, 400); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Etiquettes(); } public void stateChanged(ChangeEvent e) { int index = onglets.getSelectedIndex(); onglets.setBackgroundAt(index, Color.RED); String titre = onglets.getTitleAt(index); Icon icône = onglets.getIconAt(index); String aide = onglets.getToolTipTextAt(index); état.setText(titre+" : "+aide); état.setIcon(icône); } }
La classe JProgressBar implémente une barre de progression : un composant qui affiche graphiquement une valeur entière non ajustable. Elle est généralement utilisée pour afficher la progression d'un programme pendant une tâche consomatrice en temps, mais peut également simuler l'affichage d'un équaliseur graphique.
ProgressMonitor est une classe utile qui affiche une JProgressBar dans une boîte de dialogue. Comme JScrollBar et JSlider, JProgressBar utilise un BoundedRangeModel pour maintenir son état.
Lorsqu'un utilisateur émet une commande pour laquelle le traitement est long, vous devez impérativement mettre en place un thread séparé pour qu'il effectue le travail afin que l'IHM ne soit pas bloqué. Il existe une classe spécifique SwingWorker qui permet de mettre en place un thread en tâche de fond, qui de temps en temps, permet de s'occuper les composants Swing afin de permettre ainsi de réaliser le suivi d'une progression. Grâce à cette classe, l'utilisateur garde toujours la main sur son IHM.
Pour en savoir plus sur cette classe SwingWorker et sur les threads en général, reportez-vous sur l'étude correspondante.
§
Afin de bien maîtriser le concept de thread avec les composants Swing, je vous propose de mettre en oeuvre un système client-serveur qui permet de transférer des gros fichiers sur le réseau et de contrôler ainsi la progression du transfert.
Deux programmes doivent donc être mise en place, d'une part le service qui stocke (uniquement, c'est un cas d'école) le fichier désiré dans un répertoire prédéfini, et d'autre part le client qui permet de choisir le fichier et de l'envoyer ensuite au service, sur le poste distant. Voici, la représentation de la partie cliente.
Vous pourrez rajouter par la suite des commandes supplémentaires, à la fois sur le client et le serveur, afin de pouvoir récupérer les fichiers stockés, de supprimer les fichiers déjà enregistrés, de lister les fichiers déjà présents, etc.
package réseau; import java.awt.*; import java.awt.event.ActionEvent; import java.io.*; import java.net.Socket; import java.util.List; import java.util.concurrent.ExecutionException; import javax.swing.*; public class Client extends JFrame { private JFileChooser sélection = new JFileChooser(); private JToolBar boutons = new JToolBar(); private JTextField résultat = new JTextField( ); private JProgressBar progression = new JProgressBar(); private final int BUFFER = 4096; public Client() { super( ); add(boutons, BorderLayout.NORTH); boutons.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { if (sélection.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) new TransfertFichier(sélection.getSelectedFile()).execute(); } }); boutons.addSeparator(); boutons.add(progression); boutons.addSeparator(); progression.setStringPainted(true); progression.setVisible(false); résultat.setEditable(false); résultat.setMargin(new Insets(3, 3, 3, 3)); add(résultat, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Client(); } private class TransfertFichier extends SwingWorker<String, Integer> { private File fichier; private int octetsLus; public TransfertFichier(File fichier) { this.fichier = fichier; } @Override protected String doInBackground() throws Exception { try { résultat.setText( +fichier.getName()+ ); progression.setVisible(true); progression.setValue(0); BufferedInputStream lecture = new BufferedInputStream(new FileInputStream(fichier)); progression.setMaximum(lecture.available()); Socket service = new Socket( , 5588); ObjectOutputStream envoi = new ObjectOutputStream(new BufferedOutputStream(service.getOutputStream())); envoi.writeObject(fichier.getName()); envoi.writeInt(lecture.available()); envoi.writeInt(BUFFER); byte[] octets = new byte[BUFFER]; while (lecture.available() > 0) { if (lecture.available() < BUFFER) octets = new byte[lecture.available()]; lecture.read(octets); envoi.write(octets); envoi.flush(); publish(octetsLus += BUFFER); } lecture.close(); service.close(); return +fichier.getName()+ ; } catch (FileNotFoundException ex) { return ; } catch (IOException ex) { return ; } } @Override protected void process(List<Integer> nombres) { progression.setValue(nombres.get(nombres.size()-1)); } @Override protected void done() { try { résultat.setText(get()); progression.setVisible(false); } catch (Exception ex) { } } } }
package réseau; import java.io.*; import java.net.*; public class Serveur { private static String stockage = ; public static void main(String[] args) throws IOException, ClassNotFoundException { ServerSocket service = new ServerSocket(5588); while (true) { Socket client = service.accept(); try { ObjectInputStream réception = new ObjectInputStream(new BufferedInputStream(client.getInputStream())); String nom = (String) réception.readObject(); int taille = réception.readInt(); int buffer = réception.readInt(); int nombre = taille / buffer; byte[] octets = new byte[buffer]; byte[] reste = new byte[taille % buffer]; BufferedOutputStream fichier = new BufferedOutputStream(new FileOutputStream(stockage + nom)); for (int i = 0; i < nombre; i++) { réception.readFully(octets); fichier.write(octets); } réception.readFully(reste); fichier.write(reste); fichier.close(); client.close(); } catch (Exception ex) {} } } }
Une barre d'outil est une barre contenant des boutons qui permettent d'accéder rapidement aux commandes les plus fréquemment utilisées dans un programme. La particularité des barres d'outils est de pouvoir être déplacées n'importe où. Vous pouvez ainsi la faire glisser vers l'un des quatre bords du cadre. Lorsque vous relâchez le bouton de la souris, la barre d'outils est placée au nouvel emplacement.
La barre d'outil peut même être complètement détachée du cadre. Elle est alors contenue dans son propre cadre. Lorsque vous fermez le cadre contenant une barre d'outils détachée, celle-ci revient dans le cardre d'origine.
Les barres d'outils sont très facile à construire. Par défaut, elles sont positionnées horizontalement, mais il est possible de choisir la position verticale. Vous pouvez aussi spécifier un titre au moment de la création, il apparaîtra lorsque la barre d'outils sera détachée.
Les boutons sont les composants les plus courants des barres d'outils. Il n'y a toutefois aucune restriction en ce qui concerne les composants pouvant être ajoutés. Vous pouvez, par exemple, ajouter une zone de liste déroulante à une barre d'outils. Pour ajouter un composant à une barre d'outil, il suffit de faire appel à la méthode add() classique.
Une barre d'outils peut contenir des composants de séparation spéciaux qui servent à regrouper les outils liés fonctionnellement entre eux et à les séparer des autres. Nous pouvons ajouter un séparateur à un JToolBar avec la méthode addSeparator().
Pour ajouter votre barre d'outils à l'application, pensez, comme d'habitude, a prévoir les contraintes de placement dans la méthode add() de la fenêtre de la façon suivante :
add(barre, BorderLayout.NORTH);
Nous pouvons interdire à une barre d'outils de flotter en utilisant ainsi la méthode setFloatable(false). La barre reste alors dans la position où nous l'avons placée. Nous pouvons également régler les marges internes au moyen de la méthode setMargin(Insets).
Nous allons mettre en oeuvre une barre d'outils qui comporte une zone de saisie, une boîte combo et un bouton. Cette barre d'outils s'intègre dans une application qui permet de faire une conversion entre les €uros et les francs. Dans la partie centrale de cette application, nous avons un historique de tous les calculs qui sont réalisés au travers de la barre d'outils.
import java.awt.*; import java.awt.event.*; import java.text.*; import javax.swing.*; public class Conversion extends JFrame implements ActionListener { private JToolBar outils = new JToolBar("Conversions"); private JTextArea historique = new JTextArea(); private JFormattedTextField saisie = new JFormattedTextField(0.0); private JComboBox choix = new JComboBox(new String[] {"€uro => Franc", "Franc => €uro"}); private JButton conversion = new JButton("Conversion"); private MessageFormat ligne = new MessageFormat("({0}) {1, number, currency} = {2, number, #,##0.00 F}"); public Conversion(){ super("Conversion €uros <--> Francs"); saisie.setColumns(10); saisie.setBackground(Color.YELLOW); conversion.addActionListener(this); historique.setMargin(new Insets(5, 3, 5, 3)); historique.setBackground(Color.ORANGE); outils.add(saisie); outils.add(choix); outils.addSeparator(); outils.add(conversion); add(outils, BorderLayout.NORTH); add(new JScrollPane(historique)); setSize(400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Conversion(); } public void actionPerformed(ActionEvent e) { final double TAUX = 6.55957; double €uro, franc; if (choix.getSelectedIndex()==0) { €uro = (Double)saisie.getValue(); franc = €uro * TAUX; } else { franc = (Double)saisie.getValue(); €uro = franc / TAUX; } historique.append(ligne.format(new Object[] {choix.getSelectedItem(), €uro, franc})+'\n'); } }