Interface Graphique d'utilisateur (GUI)
(référence: Chap. 10)
Préparation: Interface (Section 9.2)
Il faut bien distinguer la notion d'interface en Java et l'interface graphique. Ici, on parle de la notion qui est définie en Java. Une interface en java permet de définir un ensemble d'exigences. Une classe qui veut satisfaire ces exigences doit "implements" cette interface. La notion d'interface est nécessaire pour gérer une interface GUI.
Définition d'une interface
[mofificateur] interface <nom_interface> {
<définition des constantes>
<définition des méthodes abstraites>
}
Comme on peut voir dans cette squelette, une interface peut contenir deux types d'éléments: constante ou méthode abstraite. Une méthode abstraite est une méthode non-implantée (i.e. sans la partie { . }). Par exemple:
public abstract int m1(String s);
Cette méthode ne spécifie que les entrées nécessaires et le type de la sortie.
Dans une interface, comme toutes les méthodes sont abstraites, le modificateur "abstract" est automatiquement ajouté (s'il n'y est pas) à chaque méthode. La même chose pour "interface" qui est aussi automatiquement "abstract". Les méthodes abstraites dans une interface sont aussi "public" automatiquement.
Utilisation d'une interface
Une classe peut "implements" une ou plusieurs interfaces comme suit:
class C implements <interface1>, <interface2>, ... { ... }
Cette classe doit remplir toutes les exigences des interfaces implantées. Cela signifie que les méthodes abstraites déclarées dans chaque interface doit être implantée (i.e. avec un corps { . } ).
Hiérarchie des interfaces
Il existe une hiérarchie des interfaces comme pour des classes. Donc, on peut avoir une sous-interface d'une interface. La sous-interface hérite les définitions mises dans la super-interface (comme dans le cas d'héritage entre les classes). La définition d'une sous-interface est comme suit:
Interface <sous-interface> extends <super-interface> { ... }
Remarquez que l'héritage entre les interfaces reste simple (comme dans le cas des classes). Donc, une sous-interface ne peut avoir qu'une seule super-interface.
Programmation par événement
La programmation par événement signifie que le fonctionnement du programme est dirigé par les événements, produits souvent par les actions de l'utilisateur via une interface graphique.
Dans l'environnement d'interface graphique, chaque action de l'utilisateur sur l'interface produira un (ou plusieurs) événement(s). Les événements peuvent être captés par certains objets, sensibles à certains types d'événements. Les actions correspondantes peuvent ensuite être déclenchées.
Les aspects dans une interface graphique
On doit s'occuper des aspects suivants:
Organisation physique
Qui réagit à quoi et comment réagir
"Quoi" est un événement de certain type en provenance de certain objet.
"Qui" est l'objet qui peut capter ce type d'événement.
Dans Java, pour qu'un objet puisse capter un type d'événement, il faut qu'il "implements" certaine "interface".
(Note: "interface" ici est un concept en java qui est différente de l'interface graphique. Informellement, une interface pose un ensemble d'exigences sur le comportement d'un objet. Quand un objet "implements" une interface, il faut que ces exigences soient toutes remplies).
Chaque "interface" exige un ensemble de méthodes prédéfinies. Ce sont précisément les méthodes de réaction pour le type d'événement correspondant.
La syntaxe générale pour permettre à une classe d'objet de réagir à un type d'événement est comme suit:
class <class_name> implements <Event>Listener { <attributes / methods of the class> <implementation of all the required methods> }
Les méthodes exigées déterminent exactement comment le programme doit réagir à ce type d'événement (une méthode pour chaque événement de ce type). Certain type d'événement peut contenir plusieurs événements possibles. Par exemple, MouseEvent peut contenir mouseDragged, mousePressed, etc (voir fig 10.6, pp. 365). Il faut implanter toutes ces méthodes.
Par exemple, pour permettre à une classe d'objets de réagir aux changements sur un Scrollbar, un type d'objet qui peut produire des événements du type "AdjustmentEvent", on doit "implements" l'interface AdjustmentListener à cette classe comme suit:
class <class_name> implements AdjustmentListener { <attributes / methods of the class> public void adjustmentValueChanged(AdjustmentEvent e) { ... } }
où la méthode "adjustmentValueChanged" est la méthode exigée pour traiter les AdjustmentEvent.
Voir l'exemple concret de WindowBlind
Cette "implementation" donne le pouvoir à un objet de réagir à un type d'événement. Il faut ensuite créer le lien d'écoute entre les objets. Cela se réalise par:
<objet_événement>.add<Event>Listener(<objet_écoute>);
où <objet_événement> est l'objet qui produit l'événement; <Event> correspond à un type d'événement; et <objet_écoute> est l'objet qui doit capter l'événement.
Par exemple, dans WindowBlind.java, nous avons la ligne suivante:
slider.addAdjustmentListener(this);
ce qui ajoute l'objet courant (this) dans la liste d'objet qui écoute les événements du type "AdjustmentEvent" provenant de "slider".
Chaque type d'objet graphique peut produire certains types d'événements. Fig. 10.2 (pp. 359) donne certains types d'événements produits par certains objets.
On a mentionné que pour traiter un type d'événements, un ensemble de méthodes exigées doit être implanté. Les méthodes pour certains type d'événement sont résumées dans fig. 10.6 (pp. 365).
Un exemple simple d'interface graphique: WindowBlind.java (voir la page web mentionné plus tôt).
MVC (Modèle - Vue - Contrôle)
L'exemple WindowBlind.java utilise une seule classe pour décrire l'objet qui produit des événements (via slider - un composant dans l'interface), qui capte les événements (via "implements AdjustmentListener"), et l'objet qui traite l'événement (this). Quand une interface devient complexe, cela devient très confus. Ainsi, il est recommander de séparer les trois fonctions de l'interface en trois modules:
Les trois modules forment les relations suivantes:
Ici, on lance le programme à partir de la Vue. Durant la phase de lancement, on doit créer une instance de Modèle et de Contrôle. Ensuite, la Vue est affichée. Si l'utilisateur agit sur la Vue, un événement est généré. Si ce type d'événement est capté par le Contrôle, le Contrôle appelle une méthode du Modèle pour le traiter. Ce traitement peut éventuellement affecter la Vue (un changement dans la Vue). Donc, il peut appeler la Vue pour un ré-affichage. Et on recommence…
Plus schématiquement, on suggère le schéma général pour MVC comme suit:
Modèle:
Vue
("extends Applet" ou bien elle doit contenir main(…))
Contrôle
("implements <Event>Listener")
Remarque: Un Contrôle peut "implements" plusieurs "Listener"s. Cela veut dire qu'il peut réagir à plusieurs sortes d'événements. Il faut alors implanter toutes les méthodes exigées.
Un exemple: WindowBlindView.java
Dans cet exemple, les 3 modules sont séparés exactement comme décrits dans le schéma.
D'autres exemples: exemples MVC
Les objets graphiques
Une hiérarchie partielle des classes d'objets graphiques est donnée à pp. 662.
Ici, nous donnons les pages de références pour quelques classes.
Applet: pp. 673.
Les classes suivantes sont décrites dans pp. 373-382:
L'annexe à la fin du livre est aussi une très bonne référence. Une façon simple de savoir quels types d'événements un objet produit est de regarder quelles sont les méthodes définies dans la classe comme add<Event>Listener. Par exemple, pour List, il y a "addActionListener", "addItemListener". Elle peut donc réagir à ces deux types d'événements. N'oubliez pas d'examiner aussi les super-classes à partir desquelles les sous-classes peuvent hériter certaines de ces méthodes (par exemple, de TextComponent vers TextArea et TextField).
Layout
Pour une interface qui a besoin d'une organisation plus sophistiquée, on peut la diviser en plusieurs morceaux, et ensuite organiser ces morceaux (on peut les diviser encore à l'intérieur). Dans un morceau, on peut utiliser les modes d'organisation suivantes:
Un exemple de Layout