Master 2 CCI 2012-2013
POO, langage Java
Henri Garreta 

7. Interfaces graphiques

        1. Compteurs
        2. Afficher l’heure
        3. Boîtes de dialogue prêtes-à-l’emploi
        4. La liste des courses
        5. L’inévitable bloc-notes
        6. Dessine-moi une spline...!
        7. Tracer une courbe
Le signe © renvoie à la correction

7.1. Compteurs ©

On vous demande de réaliser une application pour compter (des bactéries sous le microscope, des passagers embarquant dans un bateau, des étoiles dans le ciel, etc.). Cela se présente (voyez la figure 1) comme un panneau comportant un titre, un nombre entier et un bouton. Chaque fois que l’utilisateur appuie sur le bouton, le nombre augmente d’une unité.

A. Pour commencer, réalisez un programme très minimaliste : une classe Compteur avec la méthode main et deux variables statiques (la valeur du nombre et le JLabel chargé de son affichage).

Il vous faudra aussi une classe auxiliaire AuditeurBouton pour représenter l’objet qui réagit aux pressions sur le bouton: faites-en une classe interne à Compteur, ce qui lui permettra d’accéder aux variables valeur et affichage (puisqu’elle est accédée depuis main, cette classe devra être qualifiée static).

B. [Légère amélioration du code] Remplacez la classe interne AuditeurBouton par une classe anonyme.

C. [Légère amélioration de l’aspect] Faites en sorte que le bouton « ++ » ait sa largeur préférée (voyez la figure 2), au lieu d’occuper toute la largeur du cadre. Pour cela, intercalez un panneau entre le bouton et le cadre.

D. [Grosse amélioration du code] Faites les choses comme il faut les faire : réorganisez le code précédent afin de définir une classe Compteur, sous-classe de JPanel. Elle est munie d’un constructeur prenant le titre pour argument, et chacune de ses instances représente un panneau supportant un triplet (titre, nombre affiché, bouton).

Pour essayer cette classe, écrivez une classe de test avec une méthode main qui crée un cadre et y place un compteur.


Fig. 1


Fig. 2

E. Pour vous convaincre du bien fondé de cette manière d’organiser le code, modifiez la méthode main précédente afin qu’elle crée un cadre avec, par exemple, quatre compteurs indépendants :


Fig. 3

7.2. Afficher l’heure ©

L’objet de cet exercice est la réalisation d’un composant Horloge servant à afficher la date et l’heure courantes – rafraîchies toutes les secondes – dans une interface graphique.

Un objet Horloge doit pouvoir être utilisé à tout endroit où un objet JLabel peut être utilisé, et doit supporter les personnalisations (changement de taille, de couleur, de police, etc.) que peut supporter un JLabel (cette phrase devrait vous faire penser « héritage »).

Cette classe possédera un constructeur

public Horloge(String texte, String format)

texte est la chaîne qu’on souhaite voir affichée devant l’heure et format une chaîne spécifiant la présentation de l’heure (au besoin, revoyez l’exercice 1.5 ou bien la classe SimpleDateFormat). Par exemple, dans l’illustration ci-contre, texte est la chaîne "Il est" et format la chaîne "HH:mm:ss".

Dans cet exemple, un objet Horloge a été ajouté comme composant inférieur (« SOUTH ») d’un cadre ayant par ailleurs une icône comme composant central. L'horloge a été personnalisée pour avoir un premier plan rouge, un fond cyan et des caractères SansSerif de 18 points.

Pour obtenir le rafraîchissement de l’heure faites en sorte que la construction d’un objet Horloge produise la mise en route d’un Thread séparé, associé à un objet Runnable dont la méthode run peut être ainsi décrite :

N.B. 1. On crée une instance de la classe Thread en appelant un constructeur qui prend pour argument un objet Runnable ; on met en route le thread ainsi créé en appelant sa méthode start().

Un objet Runnable est tout simplement un objet qui possède une méthode nommée public void run(). Dans le présent exercice, l’objet Runnable peut être l’objet Horloge lui-même.

N.B. 2. Une icône (interface javax.swing.Icon) est une image, souvent petite, utilisée pour décorer un élément de l’interface graphique. Par exemple, un JLabel peut porter un texte, ou une icône, ou les deux.

Les objets « image icon » (classe javax.swing.ImageIcon) sont des Icon simples et pratiques qu’on crée à partir d’un fichier, d’une URL ou même d’un tableau d'octets.

7.3. Boîtes de dialogue prêtes–à–l’emploi ©

Un certain nombre de boîtes de dialogue simples sont utilisées très fréquemment dans toutes sortes d’applications : les boîtes d’information (figure 2), les « questions » à deux et trois boutons (figure 3), la saisie d’une chaîne, soit libre (figure 4), soit en la choisissant dans une liste de possibilités (figure 5).

(Bien entendu, pour des boîtes de dialogue plus complexes il n’y a pas de solution toute prête, et le programmeur doit construire sa propre boîte dialogue. Cela se fait en définissant une sous-classe de JDialog, et sera étudié dans un autre exercice.)

En Java les boîtes de dialogue prêtes–à–l’emploi s’obtiennent très facilement à l’aide des méthodes statiques showQuelqueChoseDialog de la classe JOptionPane. Dans cet exercice on vous demande d’écrire une application qui sert de « présentoir » des diverses sortes de boîtes de dialogue qu’on peut obtenir de cette manière. La figure 1 montre le cadre principal de l’application.


Fig. 1

Cela se présente comme un cadre contenant une colonne de boutons. La pression sur un bouton affiche la boîte de dialogue en question. Lorsque la chose est pertinente, l’information obtenue à travers la boîte de dialogue est affichée à l’aide d’une boîte d’information, pour contrôle.


Fig. 2

Fig. 3

Fig. 4

Fig. 5

Note. Observez la différence d’aspect des cadres et des boîtes de dialogue que vous obtenez selon que vous avez ou non mis, avant toute création d’un objet JFrame ou JDialog (les méthodes show...Dialog créent de tels objets) les deux lignes

    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);

7.4. La liste des courses ©

Comme plusieurs autres composants de Swing, les listes (classe JList) sont moins faciles à utiliser qu’on ne pourrait le penser (et que ne le sont les List, leurs homologues d’AWT) à cause de la séparation qui est maintenue entre la vue, c'est-à-dire le composant lui-même, et le modèle sous-jacent, qui gère les données que la vue montre. Cette séparation a de nombreux avantages, mais impose une machinerie avec laquelle il faut bien négocier.

Pour pratiquer cela nous allons réaliser une application qui construit et affiche – on se contentera de cela, mais on pourrait imaginer des fonctions ultérieures d’impression, de sauvegarde dans un fichier, etc. – une liste simple, par exemple la liste des courses au supermarché, voyez la figure ci-contre.

Pour ajouter un item à cette liste il faut le taper dans le champ de texte tout en bas de l’interface, puis actionner le bouton « Ajout ». Pour supprimer un item il faut le sélectionner dans la liste puis agir sur le bouton « Suppr ».

A. Pour commencer ne vous occupez que de l’interface (c.-à-d. ignorez le comportement associé aux composants). Écrivez une classe ListeSimple, sous-classe de JPanel, réduite à un constructeur qui dispose sur ce panneau les composants montrés sur la figure ci-contre.

 L’objet ListeSimple est géré par un BorderLayout, contenant :
  - comme composant central, un objet JScrollPane qui contient à son tour un objet JList initialisé comme indiqué plus loin,
  - comme composant inférieur, un objet JPanel géré par un GridLayout à une colonne (il en découlera 2 lignes), contenant
    - un JPanel géré par un GridLayout à une ligne (il en découle 2 colonnes), contenant deux boutons "Ajout" et "Suppr"
    - un JTextArea

Écrivez également une classe de test, avec une méthode main qui se limite à créer un cadre (classe JFrame) et lui donner comme panneau de contenu un objet ListeSimple.

B. Dans un deuxième temps intéressons-nous à la détection et au traitement des actions de l’utilisateur sur cette interface.

Pour l’utilisateur, les moyens d’action principaux sont les deux boutons Ajout et Suppr. Or, l’ajout n’a de sens que si un mot est tapé dans la zone de texte ; de même, la suppression n’est pertinente que si un item de la liste est sélectionné. On pourrait traiter ces « préconditions » par des tests placés à l’entrée des fonction qui traitent les actions sur les boutons. Mais on préfère généralement, et c’est ce que nous ferons ici, régler cela en amont de l’action de l’utilisateur, en désactivant (estompant) les boutons lorsque l’opération qu’ils expriment n’a pas de sens.

Pour une fois nous allons renoncer aux classes internes anonymes en déclarant que notre classe ListeSimple implémente les interfaces

A divers endroits du constructeur de ListeSimple nous aurons donc les instructions

laListe.addListSelectionListener(this);
zoneTexte.getDocument().addDocumentListener(this);    
boutonAjout.addActionListener(this); 
boutonSuppr.addActionListener(this);

Écrivez les méthodes mentionnées plus haut, qui donnent à notre interface le comportement voulu. Les méthodes valueChanged (de ListSelectionListener), insertUpdate et removeUpdate (de DocumentListener) se chargent d’« allumer » et d’« éteindre » les boutons, la méthode actionPerformed de ActionListener s’occupe de faire le travail d’ajout ou de suppression d’un élément.

7.5. L’inévitable bloc-notes ©

On vous demande d’écrire un éditeur de textes simple, analogue au bloc-notes de Windows :

Fondamentalement, le bloc notes est un cadre (classe JFrame) avec une barre de menus (classe JMenuBar) et un panneau de contenu dans lequel on aura mis un panneau de défilement (classe JScrollPane) contenant une zone de texte (classe JTextArea).

On veillera à ce que toute commande produisant la perte du texte édité soit précédée d’une confirmation :

Le menu Fichier est formé des commandes les plus classiques

Le menu Aide se réduit à la boîte à propos :

Note 1. Pour déterminer s’il y a lieu de demander une confirmation avant de détruire le texte définissez une variable d’instance texteModifié que certaines opérations mettent à false, et qui est mise à true chaque fois qu’un caractère est tapé au clavier en visant la zone de texte (événement Key).

Note 2. La sauvegarde du texte dans un fichier ne pose pas de problème : il suffit d’écrire tout le contenu de la zone de texte dans un FileWriter ouvert à partir d’un objet File obtenu à l’aide d’un dialogue FileChooser :

La lecture du texte est un peu plus alambiquée, car les objets FileReader ne peuvent lire que dans un tampon de caractères, qu’il faudra allouer à partir de la taille donnée par une expression de la forme unFichier.length() (où unFichier est un objet File obtenu à l’aide d’un objet FileChooser).

7.6. Dessine-moi une spline...! ©

Vous voulez épater votre petit neveu, qui vous tient pour un héros, en lui montrant une section de spline cubique, c’est-à-dire un segment de courbe définie par un polynôme de degré 3 dont les extrémités sont données et les tangentes aux extrémités aussi (voyez la figure 2). Cela tombe bien, de telles courbes sont prêtes à l’emploi, disponibles dans la bibliothèque Java2D.

Le plus dur à faire sera de mettre en place un panneau portant quatre points (les deux extrémités de la courbe et les deux extrémités restantes des tangentes) qu’on pourra déplacer avec la souris pour observer les changements de la courbe.

A. Définissez la classe Cubique, sous-classe de JPanel, qui représente un panneau montrant quatre points, extrémités de deux segments, voyez la figure 1. A la création ces points ont des positions convenues, ensuite l’utilisateur peut les attraper avec la souris et les déplacer comme il veut.

Indications. Définissez une classe interne pour représenter les points, chacun ayant sa propre couleur. Un Point est fait de trois variables d’instance, deux (x et y) de type int et une de type Color. Votre classe Cubique contiendra comme variable d’instance un tableau de quatre de tels objets.

Cubique 1
Fig. 1

La méthode void paint(Graphics g) de votre classe Cubique consistera essentiellement à tracer les deux segments (méthode drawLine) puis à dessiner les quatre points sous forme de petits carrés (méthode fillRect).

Pour le déplacement des points à la souris déclarez que votre classe implémente les interfaces MouseListener et MouseMotionListener. Cela vous oblige à écrire sept méthodes, dont seules trois vous intéressent :

B. Pour montrer la spline cubique définie par les quatre points (les noirs jouent le rôle d’extrémités de la courbe, les rouges définissent les tangentes) il faut déclarer une variabe d’instance de type CubicCurve2D.Double, créée lors de la construction du panneau et, surtout, positionnée à chaque appel de la méthode paint par un appel de la méthode setCurve. Cette méthode requiert quatre objets de type Point2D.Double (des points aux coordonnées double) qu’il faudra construire à partir des coordonnées (entières) des quatre points dessinés.

Cubique 1
Fig. 2

7.7. Tracer une courbe ©

A. L’objet de l’exercice est l’écriture d’une classe simple effectuant la représentation graphique d’une fonction réelle d’une variable réelle. Par exemple, dans le cas de la fonction y = sin x :


Fig. 1

La fonction est traceé de manière à remplir au mieux le panneau de dessin. Ainsi, les modifications de la taille ou de la forme du cadre entraînent un nouveau tracé, voyez la figure 2.

Écrivez une classe Traceur, sous-classe de JPanel. Elle est munie d’un constructeur

public Traceur(Fonction fonc);
Fonction est une interface ainsi définie :
public interface Fonction { 
    double tion(double x); 
    double xMin(); 
    double xMax(); 
}

double tion(double x) représente la fonction en question, tandis que xMin() et xMax() sont deux méthodes qui renvoient toujours les mêmes valeurs (c.-à-d. des constantes). Ces valeurs déterminent l’intervalle de définition de la fonction [il est supposé que xMin() < xMax()].

Par exemple, le tracé montré sur les figures 1 et 2 est obtenu en construisant un panneau Traceur de la manière suivante :

Traceur panneau = new Traceur(new Fonction() { 
    public double tion(double x) { 
        return Math.sin(x);
    } 
    public double xMin() { 
        return -10; 
    } 
    public double xMax() { 
        return 10;
    } 
});

Fig. 2

A partir des valeurs renvoyées par xMin() et xMax() le constructeur calcule les valeurs de yMin et yMax, ces quatre valeurs sont conservées dans des variables d’instance privée.

Le tracé lui-même est pris en charge par la rédéfinition de la méthode canonique paint(Graphics g), qui commence par obtenir la taille du panneau (méthode getSize()) et en déduire les quatre coefficients Ax, Bx, Ay et By permettant de convertir les coordonnées « utilisateur » (xu, yu) en des coordonnées « écran » (xe, ye) :

xe = Ax × xu + Bx
ye = Ay × yu + By

Notez que xu et yu sont des double, alors que xe et ye sont des int.

Si le cœur vous en dit, pour pouvez compléter ce programme par le tracé d’un quadrillage ou l’affichage de repères numériques..

B. Ajoutez au programme précédent la possibilité de « zoomer » sur une partie du tracé : lorsque l’utilisateur définit un rectangle avec la souris (par les gestes habituels : presser le bouton, déplacer la souris, relâcher le bouton) les coefficients Ax, Bx, Ay et By sont recalculés afin que la partie du tracé délimitée par ce rectangle remplisse tout le panneau.

On doit pouvoir revenir en arrière. Par exemple, si l’utilisateur fait un « Ctrl-clic » n’importe où dans le panneau, le tracé doit reprendre ses proportions précédentes.