2.2. Java à la fenêtre



Plan de ce chapitre:   ...........retour au plan général

1. Le package AWT

    1.1 Les classes conteneurs
    1.2 Les classes de composants déclenchant des actions
    1.3 Les classes de composants d'affichage ou de saisie


2. Une application fenêtrée pas à pas
 
 

3. Les événements

    3.1 Principes de la programmation par événements
    3.2 Modèle de délégation de l'événement en Java
    3.3 Terminer une application par un click de bouton
    3.4 Evénements et objets



Java, comme tout langage moderne, permet de créer des applications qui ressemblent à l'interface du système d'exploitation. Cette assurance d'ergonomie et d'interactivité avec l'utilisateur est le minimum qu'un utilisateur demande à une application. Les interfaces homme-machine (dénommées IHM) font intervenir de nos jours des éléments que l'on retrouve dans la majorité des systèmes d'exploitation : les fenêtres, les menus déroulants, les boutons, etc...

Ce chapitre traite en résumé,mais en posant toutes les bases, de l'aptitude de Java  à élaborer une IHM. Nous regroupons sous le vocable d'IHM Java, les applications disposant d'une interface graphique et les applets que nous verrons plus loin.

1. Le package AWT

C'est pour construire de telles IHM que le package AWT (Abstract Window Toolkit) est inclu dans toutes les versions de Java. Ce package est la base des extensions ultérieures comme Swing, mais est le seul qui fonctionne sur toutes les générations de navigateurs.

Les classes contenues dans AWT  dérivent (héritent) toutes de la classe Component, nous allons étudier quelques classes minimales pour construire une IHM standard.
 
 

1.1 Les classes Conteneurs

Ces classes sont essentielles pour la construction d'IHM Java elles dérivent de la classe java.awt.Container, elles permettent d'intégrer d'autres objets visuels et de les organiser à l'écran.

Hiérarchie de la classe Container :

java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
 

Voici la liste extraite du JDK des sous-classes de la classe Container autres que Swing :
 Panel, ScrollPane, Window.

Les principales classes conteneurs :
Classe Fonction
+--java.awt.Container
              |
              +--java.awt.Window
Crée des rectangles simples sans cadre, sans menu,sans titre, mais ne permet pas de créer directement une fenêtre Windows classique.
+--java.awt.Container
              |
              +--java.awt.Panel
Crée une surface sans bordure, capable de contenir d'autres éléments : boutons, panel etc...
+--java.awt.Container
              |
              +--java.awt.ScrollPane
Crée une barre de défilement horizontale et/ou une barre de défilement verticale.

Les classes héritées des classes conteneurs :
Classe Fonction
java.awt.Window
  |
  +--java.awt.Frame
Crée des fenêtres  avec bordure, pouvant intégrer des menus, avec un titre, etc...comme toute fenêtre Windows classique.
C'est le conteneur de base de toute application graphique.
java.awt.Window
  |
  +--java.awt.Dialog
Crée une fenêtre de dialogue avec l'utilisateur, avec une bordure, un titre et un bouton-icône de fermeture.

Une première fenêtre construite à partir d'un objet de classe Frame; une fenêtre est donc un objet, on pourra donc créer autant de fenêtres que nécessaire, il suffira à chaque fois d'instancier un objet de la classe Frame.
 

Quelques méthodes de la classe Frame, utiles au départ :
Méthodes Fonction
public void setSize(int width, int height) retaille la largeur (width) et la hauteur (height) de la fenêtre.
public void setBounds(int x, int y, int width, int height) retaille la largeur (width) et la hauteur (height) de la fenêtre et la positionne en x,y sur l'écran.
public Frame(String title) 
public Frame( )
Les deux constructeurs d'objets Frame, celui qui possède un paramètre String écrit la chaîne dans la barre de titre de la fenêtre.
public void show( ) Change l'état de la fenêtre en mode visible.
public void hide( ) Change l'état de la fenêtre en mode invisible.
Différentes surcharges de la méthode add :
public Component add(Component comp)
etc...
Permettent d'ajouter un composant à l'intérieur de la fenêtre.

Une Frame lorsque son constructeur la crée est en mode invisible, il faut donc la rendre visible, c'est le rôle de la méthode show( ) que vous devez appeler afin d'afficher la fenêtre sur l'écran :
 
Programme Java
import java.awt.*;
class AppliWindow
{
  public static void main(String [ ] arg) {
    Frame fen = new Frame ("Bonjour" );
    fen.show( );
   }
}

 Ci-dessous la fenêtre affichée par le programme précédent :

 

Cette fenêtre est trop petite, retaillons-la avec la méthode setBounds :
 
Programme Java
import java.awt.*;
class AppliWindow
{
  public static void main(String [ ] arg) {
    Frame fen = new Frame ("Bonjour" );
    fen.setBounds(100,100,250,150);
    fen.show( );
   }
}

 Ci-dessous la fenêtre affichée par le programme précédent : 


Pour l'instant nos fenêtres sont repositionnables, retaillables, mais elles ne contiennent rien, comme ce sont des objets conteneurs, il est possible en particulier, d'y inclure des composants.

Il est possible d'afficher des fenêtres dites de dialogue de la classe Dialog, dépendant d'une Frame. Elles sont très semblables aux Frame (barre de titre, cadre,...) mais ne disposent que d'un bouton icône de fermeture dans leur titre :

une fenêtre de classe Dialog :

De telles fenêtres doivent être obligatoirement rattachées lors de la construction à un parent qui sera une Frame ou une autre boîte de classe Dialog, le constructeur de la classe Dialog contient plusieurs surcharges dont la suivante :

public Dialog(Frame owner, String title)
où owner est la Frame qui va appeler la boîte de dialogue, title est la string contenant le titre de la boîte de dialogue. Il faudra donc appeler le constructeur Dialog avec une Frame instanciée dans le programme.

Exemple d'affichage d'une boîte informations à partir de notre fenêtre "Bonjour" :
 
Programme Java
import java.awt.*;
class AppliWindow
{
  public static void main(String [ ] arg) {
    Frame fen = new Frame ("Bonjour" );
    fen.setBounds(100,100,250,150);
    Dialog fenetreDial = new Dialog (fen,"Informations");
    fenetreDial.setSize(150,70);
    fenetreDial.show( );
    fen.show( );
   }
}

 Ci-dessous les fenêtres Bonjour et la boîte Informations affichées par le programme précédent :


 
 
 

1.2 Les classes de composants déclenchant des actions

Ce sont essentiellement des classes directement héritées de la classe java.awt.Container. Les menus dérivent de la classe java.awt.MenuComponent. Nous ne détaillons pas tous les composants possibles, mais certains les plus utiles à créer une interface Windows-like.

Composants permettant le déclenchement d'actions :
Les classes composants Fonction
java.lang.Object
  |
  +--java.awt.MenuComponent
        |
        +--java.awt.MenuBar
Création d'une barre des menus dans la fenêtre.
java.lang.Object
  |
  +--java.awt.MenuComponent
        |
        +--java.awt.MenuItem
Création des zones de sous-menus d'un menu principal de la barre des menus classiques.
java.lang.Object
  |
  +--java.awt.MenuComponent
        |
        +--java.awt.MenuItem
              |
              +--java.awt.Menu
Création d'un  menu principal classique dans la barre des menus de la fenêtre.
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Button
Création d'un bouton poussoir classique (cliquable par la souris)
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Checkbox
Création d'un radio bouton, regroupable éventuellement avec d'autres radio boutons.

Enrichissons notre fenêtre précédente d'un bouton poussoir et de deux radio boutons :
 
Programme Java
import java.awt.*;
class AppliWindow
{
  public static void main(String [ ] arg) {
    Frame fen = new Frame ("Bonjour" );
    fen.setBounds(100,100,250,150);
    fen.setLayout(new FlowLayout( ));
    Button entree = new Button("Entrez");
    Checkbox bout1 = new Checkbox("Marié");
    Checkbox bout2 = new Checkbox("Célibataire");
    fen.add(entree);
    fen.add(bout1);
    fen.add(bout2);
    fen.show( );
   }
}

 Ci-dessous la fenêtre affichée par le programme précédent :

Remarques sur le programme précédent :

1) Les instructions

servent à créer un bouton poussoir (classe Button) et deux boutons radio (classe CheckBox), chacun avec un libellé.

2) Les instructions


servent à ajouter les objets créés au conteneur (la fenêtre fen de classe Frame).

3) L'instruction

sert à positionner les objets visuellement dans la fenêtre les uns à côté des autres, nous en dirons un peu plus sur l'agencement visuel des composants dans une fenêtre.
 

Terminons la personnalisation de notre fenêtre avec l'introduction d'une barre des menus contenant deux menus : "fichier" et "édition" :
 
Programme Java
import java.awt.*;
class AppliWindow
{
  public static void main(String [ ] arg) {
    Frame fen = newFrame ("Bonjour" );
    fen.setBounds(100,100,250,150);
    fen.setLayout(new FlowLayout( ));
    Button entree = new Button("Entrez");
    Checkbox bout1 = new Checkbox("Marié");
    Checkbox bout2 = new Checkbox("Célibataire");
    fen.add(entree);
    fen.add(bout1);
    fen.add(bout2);
   // les menus :
    MenuBar mbar = new MenuBar( );
    Menu meprinc1 = new Menu("Fichier");
    Menu meprinc2 = new Menu("Edition");
    MenuItem item1 = new MenuItem("choix n°1");
    MenuItem item2 = new MenuItem("choix n° 2");
    fen.setMenuBar(mbar);
    meprinc1.add(item1);
    meprinc1.add(item2);
    mbar.add(meprinc1);
    mbar.add(meprinc2);
    fen.show( );
   }
}

 Ci-dessous la fenêtre affichée par le programme précédent :

La fenêtre après que l'utilisateur clique sur le menu Fichier

Remarques sur le programme précédent :

1) Les instructions


servent à créer une barre de menus nommée mbar, deux menus principaux meprinc1 et meprinc2, et enfin deux sous-menus item1 et item2 A cet instant du programme tous ces objets existent mais ne sont pas attachés entre eux, ont peut les considérer comme des objets "flottants" .

2) Dans l'instruction


la méthode
setMenuBar de la classe Frame sert à attacher (inclure) à la fenêtre fen de classe Frame, l'objet barre des menus mbar déjà créé comme objet "flottant".

3) Les instructions


servent grâce à la méthode add de la classe Menu, à attacher les deux objets flottants de sous-menu nommés
item1 et item2 au menu principal meprinc1.

4) Les instructions


servent grâce à la méthode add de la classe MenuBar, à attacher les deux objets flottants de catégorie menu principal nommés
meprinc1 et meprinc2, à la barre des menus mbar.
 
Notons enfin ici une application pratique du polymorphisme dynamique (redéfinition) de la méthode add, elle même surchargée plusieurs fois dans une même classe.

 
 

1.3 Les classes de composants d'affichage ou de saisie

Composants permettant l'affichage ou la saisie :
Les classes composants Fonction
java.awt.Component
        |
        +--java.awt.Label
Création d'une étiquette permettant l'affichage d'un texte.
java.awt.Component
        |
        +--java.awt.Canvas
Création d'une zone rectangulaire vide dans laquelle l'application peut dessiner.
java.awt.Component
        |
        +--java.awt.List
Création d'une  liste de chaînes dont chaque élément est sélectionnable.
java.awt.Component
        |
        +--java.awt.TextComponent
              |
              +--java.awt.TextField
Création d'un éditeur mono ligne.
java.awt.Component
        |
        +--java.awt.TextComponent
              |
              +--java.awt.TextArea
Création d'un éditeur multi ligne.

Ces composants s'ajoutent à une fenêtre après leurs créations, afin d'être visible sur l'écran comme les composants de Button, de CheckBox, etc...
.
Ces composants sont à rapprocher quant à leurs fonctionnalités aux classes Delphi de composant standards, nous en donnons la correspondance dans le tableau ci-dessous :
 
Les classes  Java Les classes  Delphi
java.awt.Label TLabel
java.awt.Canvas TCanvas
java.awt.List TListBox
java.awt.TextField TEdit
java.awt.TextArea TMemo
java.awt.CheckBox TCheckBox
java.awt.Button TButton

Exemple récapitulatif :

Soit à afficher une fenêtre principale contenant le texte "Fenêtre principale" et deux fenêtres de dialogue, l'une vide directement instancié à partir de la classe Dialog, l'autre contenant un texte et un bouton, instanciée à partir d'une classe de boîte de dialogue personnalisée. L'exécution du programme produira le résultat suivant :

Nous allons construire un programme contenant deux classes, la première servant à définir le genre de boîte personnalisée que nous voulons, la seconde servira à créer une boîte vide et une boîte personnalisée et donc à lancer l'application.

Première classe :
 
La classe de dialoque personnalisée
import java.awt.*;
class  UnDialog extends Dialog
{
  public UnDialog(Frame mere)
  {
   super(mere,"Informations");
   Label etiq = new Label("Merci de me lire !");
   Button bout1 = new Button("Ok");
   setSize(200,100);
   setLayout(new FlowLayout( ));
   add(etiq);
   add(bout1);
   show( );
  }
}

Explications pas à pas des instructions :

Cette classe UnDialog ne contient que le constructeur permettant d'instancier des objets de cette classe, elle dérive (hérite) de la classe Dialog < class  UnDialog extends Dialog >
On appelle immédiatement le constructeur de la classe mère (Dialog) par l'instruction < super(mere,"Informations"); > on lui fournit comme paramètres : la Frame propriétaire de la boîte de dialogue et le titre de la future boîte.
On crée une Label <Label etiq = new Label("Merci de me lire !");>,
On crée un Button <Button bout1 = new Button("Ok");>.
On définit la taille de la boîte à instancier <setSize(200,100);>
On indique le mode d'affichage des composants qui y seront déposés <setLayout(new FlowLayout( ));>
On ajoute la Label <add(etiq);> à la future boîte,
On ajoute le Button <add(bout1);>
La future boîte devra s'afficher à la fin de sa création <show( );>

Seconde classe :
Une classe principale servant à lancer l'application et contenant la méthode main :
 
La classe principale contenant main
class  AppliDialogue
{
  public static void main(String [] arg) {
   Frame win = new Frame("Bonjour");
   UnDialog dial = new UnDialog(win);
   Dialog dlg = new Dialog(win,"Information vide");
   dlg.setSize(150,70);
   dlg.show( );
   win.setBounds(100,100,250,150);
   win.setLayout(new FlowLayout( ));
   win.add(new Label("Fenêtre principale."));
   win.show( );
  }
}

Toutes les instructions de la méthode main mise à part l'instruction <UnDialog dial = new UnDialog(win);>,  correspondent à ce que nous avons écrit plus haut en vue de la création d'une fenêtre win de classe Frame dans laquelle nous ajoutons une Label et qui lance une boîte de dialogue dlg.

L'instruction <UnDialog dial = new UnDialog(win);> sert à instancier un objet dial de notre classe personnalisée, cet objet étant rattaché à la fenêtre win


L'instruction <Dialog dlg = new Dialog(win,"Information vide");> sert à instancier un objet dlg de classe générale Dialog, cet objet est aussi rattaché à la fenêtre win :


 
Le programme Java avec les 2 classes
import java.awt.*;
class  UnDialog extends Dialog
{
  public UnDialog(Frame mere)
  {
   super(mere,"Informations");
         ........// instructions
   show( );
  }
}
class  AppliDialogue
{
  public static void main(String [] arg) {
   Frame win = new Frame("Bonjour");
      .......// instructions
   win.show( );
  }
}

Comment gérer la position d'un composant dans un conteneur de classe Container : Le Layout Manager

En reprenant la fenêtre de dialogue précédente, observons l'effet visuel produit par la présence ou non d'un Layout Manager :
 
La classe de dialogue sans Layout Manager
import java.awt.*;
class  AppliUnDialog2 extends Dialog
{
    public AppliUnDialog2(Frame mere)
    {
         super(mere,"Informations");
         Label etiq = new Label("Merci de me lire !");
         Button bout1 = new Button("Ok");
         setSize(200,100);
         //setLayout(new FlowLayout( ));
         add(etiq);
         add(bout1);
         show( );
     }
   public static void main(String[] args) {
      Frame fen = new Frame("Bonjour");
      AppliUnDialog2 dlg = new AppliUnDialog2(fen);
   }
}

En fait lorsqu'aucun Layout manager n'est spécifié, c'est par défaut la classe du Layout  <BorderLayout> qui est utilisée par Java. Cette classe n'affiche qu'un seul élément en une position fixée. Voici ce que donne l'exécution de ce programme Java. Nous remarquons que le bouton masque l'étiquette en prenant toute la place :

Si dans les instructions d'ajout des composants dans le constructeur public AppliUnDialog2(Frame mere) :
add(etiq);
add(bout1);
show( );

Nous intervertissons l'ordre d'ajout du bouton et de l'étiquette, toujours en laissant Java utiliser le <BorderLayout> par défaut :
add(bout1);
add(etiq);
show( );
voici l'effet visuel obtenu :

Cette fois c'est l'étiquette (ajoutée en dernier) qui masque le bouton !

Définissons un autre Layout puisque celui-ci ne nous plaît pas, utilisons la classe <FlowLayout> qui place les composants les uns à la suite des autres de la gauche vers la droite, l'affichage visuel continuant à la ligne suivante dès que la place est insuffisante. L'instruction <setLayout(new FlowLayout( ));>, assure l'utilisation du FlowLayout pour notre fenêtre de dialogue.
 
La classe de dialogue avec FlowLayout
import java.awt.*;
class  AppliUnDialog2 extends Dialog
{
    public AppliUnDialog2(Frame mere)
    {
         super(mere,"Informations");
         Label etiq = new Label("Merci de me lire !");
         Button bout1 = new Button("Ok");
         setSize(200,100);
         setLayout(new FlowLayout( ));
         add(etiq);
         add(bout1);
         show( );
     }
   public static void main(String[ ] args) {
      Frame fen = new Frame("Bonjour");
      AppliUnDialog2 dlg = new AppliUnDialog2(fen);
   }
}

voici l'effet visuel obtenu :

Si comme précédemment l'on échange l'ordre des instructions d'ajout du bouton et de l'étiquette :


on obtient l'affichage inversé :

D'une manière générale, utilisez la méthode < public void setLayout(LayoutManager mgr) > pour indiquer quel genre de positionnement automatique (cf. aide du JDK pour toutes possibilités) vous conférez au Container (ici la fenêtre) votre façon de gérer le positionnement des composants de la fenêtre. Voici à titre d'information tirées du JDK, les différentes façons de positionner un composant dans un container.

héritant de LayoutManager :
GridLayout, FlowLayout, ViewportLayout, ScrollPaneLayout, BasicOptionPaneUI.ButtonAreaLayout, BasicTabbedPaneUI.TabbedPaneLayout, BasicSplitPaneDivider.DividerLayout, BasicInternalFrameTitlePane.TitlePaneLayout, BasicScrollBarUI, BasicComboBoxUI.ComboBoxLayoutManager, BasicInternalFrameUI.InternalFrameLayout.

héritant de LayoutManager2 :
CardLayout, GridBagLayout, BorderLayout, BoxLayout, JRootPane.RootLayout, OverlayLayout, BasicSplitPaneUI.BasicHorizontalLayoutManager.

Vous notez qu'il est impossible d'être exhaustif sans devenir assommant, à chacun d'utiliser les Layout en observant leurs effets visuels.

Il est enfin possible, si aucun des Layout ne vous convient de gérer personnellement au pixel près la position d'un composant. Il faut tout d'abord indiquer que vous ne voulez aucun Layoutmanager, puis ensuite préciser les coordonnées et la taille de votre composant.

Indiquer qu'aucun Layout n'est utilisé :
setLayout(null); //on passe la référence null comme paramètre à la méthode de définition du Layout

Préciser les coordonnées et la taille du composant avec sa méthode setBounds :
public void setBounds(int x,  int y, int width,  int height)

Exemple, les paramètres de setBounds pour un Button :

 
 

Si nous voulons positionner nous mêmes un composant Component comp dans la fenêtre, nous utiliserons la méthode add indiquant le genre de façon de ranger ce composant (LayoutManager)

public void add(Component comp,Object constraints)
add(checkbox1, new FlowLayout( ));
   ou bien
add(checkbox1, null);
 
 
 

2. Une application fenêtrée pas à pas

Nous nous proposons ici de construire une IHM de saisie de renseignements concernant un(e) étudiant(e) :

import Cadre1; // utilisation de la classe Cadre1
class AppliIHM { //  classe principale
  //Méthode principale
  public static void main(String[] args) { // lance le programme
     Cadre1 fenetre = new Cadre1( );// création d'un objet de classe Cadre1
     fenetre.setVisible(true);// cet objet de classe Cadre1 est rendu visible sur l'écran
  }
}

import java.awt.*; // utilisation des classes du package awt
class Cadre1 extends Frame { // la classe Cadre1 hérite de la classe des fenêtres Frame
  Button button1 = new Button( );// création d'un objet de classe Button
  Label label1 = new Label( );// création d'un objet de classe Label
  CheckboxGroup checkboxGroup1 = new CheckboxGroup( );// création d'un objet groupe de checkbox
  Checkbox checkbox1 = new Checkbox( );// création d'un objet de classe Checkbox
  Checkbox checkbox2 = new Checkbox( );// création d'un objet de classe Checkbox
  Checkbox checkbox3 = new Checkbox( );// création d'un objet de classe Checkbox
  TextField textField1 = new TextField( );// création d'un objet de classe TextField

  //Constructeur de la fenêtre
  public Cadre1( ) //Constructeur sans paramètre
   Initialiser( );// Appel à une méthode privée de la classe
  }

  //Initialiser la fenêtre :
  private void Initialiser( ) //Création et positionnement de tous les composants
    this.setResizable(false); // la fenêtre ne peut pas être retaillée par l'utilisateur
    this.setLayout(null); // pas de Layout, nous positionnons les composants nous-mêmes
    this.setBackground(Color.yellow); // couleur du fond de la fenêtre
    this.setSize(348, 253); // widht et height de la fenêtre
    this.setTitle("Bonjour - Filière C.C.Informatique"); // titre de la fenêtre
    this.setForeground(Color.black); // couleur de premier plan de la fenêtre
    button1.setBounds(70, 200, 200, 30); // positionnement du bouton
    button1.setLabel("Validez votre entrée !"); // titre du bouton
    label1.setBounds(24, 115, 50, 23); // positionnement de l'étiquette
    label1.setText("Entrez :"); // titre de l'étiquette
    checkbox1.setBounds(20, 25, 88, 23); // positionnement du CheckBox
    checkbox1.setCheckboxGroup(checkboxGroup1); // ce CheckBox est mis dans le groupe checkboxGroup1
    checkbox1.setLabel("Madame");// titre du CheckBox
    checkbox2.setBounds(20, 55, 108, 23);// positionnement du CheckBox
    checkbox2.setCheckboxGroup(checkboxGroup1);// ce CheckBox est mis dans le groupe checkboxGroup1
    checkbox2.setLabel("Mademoiselle");// titre du CheckBox
    checkbox3.setBounds(20, 85, 88, 23);// positionnement du CheckBox
    checkbox3.setCheckboxGroup(checkboxGroup1);// ce CheckBox est mis dans le groupe checkboxGroup1
    checkbox3.setLabel("Monsieur");// titre du CheckBox
    checkboxGroup1.setSelectedCheckbox(checkbox1);// le CheckBox1 du groupe est coché au départ
    textField1.setBackground(Color.white);// couleur du fond de l'éditeur mono ligne
    textField1.setBounds(82, 115, 198, 23);// positionnement de l'éditeur mono ligne
    textField1.setText("Votre nom ?");// texte de départ de l'éditeur mono ligne
    this.add(checkbox1, null);// ajout dans la fenêtre du CheckBox
    this.add(checkbox2, null);// ajout dans la fenêtre du CheckBox
    this.add(checkbox3, null);// ajout dans la fenêtre du CheckBox
    this.add(button1, null);// ajout dans la fenêtre du bouton
    this.add(textField1, null);// ajout dans la fenêtre de l'éditeur mono ligne
    this.add(label1, null);// ajout dans la fenêtre de l'étiquette
  }
}

Maintenant que nous avons construit la partie affichage de l'IHM, il serait bon qu'elle interagisse avec l'utilisateur, par exemple à travers des messages comme les événements de souris ou bien d'appui de touches de claviers. Nous allons voir comment Java règle la gestion des échanges de messages entre le système et votre application.
 
 
 

3. Les événements

Rappelons ce que nous connaissons de la programmation par événements (cf.package chap.5.4)

3.1 Principes de la programmation par événeme

Logique selon laquelle un programme est construit avec des objets et leurs propriétés et d’après laquelle seules les interventions de l’utilisateur sur les objets du programme déclenchent l’exécution des routines associées.

Avec des systèmes multi-tâches préemptifs sur micro-ordinateur , le système d’exploitation passe l’essentiel de son " temps " à attendre une action de l’utilisateur (événement). Cette action déclenche un message que le système traite et envoie éventuellement à une application donnée.

Nous pourrons construire un logiciel qui réagira sur les interventions de l’utilisateur si nous arrivons récupérer dans notre application les messages que le système envoie. Nous avons déjà utilisé l’environnement Delphi de Borland, et Visual Basic de Microsoft, Java autorise aussi la consultation de tels messages.

 
  • L’approche événementielle intervient principalement dans l’interface entre le logiciel et l’utilisateur, mais aussi dans la liaison dynamique du logiciel avec le système, et enfin dans la sécurité.
  • L’approche visuelle nous aide et simplifie notre tâche dans la construction du dialogue homme-machine.
  • La combinaison de ces deux approches produit un logiciel habillé et adapté au système d’exploitation.


Il est possible de relier certains objets entre eux par des relations événementielles. Nous les représenterons par un graphe (structure classique utilisée pour représenter des relations).
 
 

3.2 Modèle de délégation de l'événement en Java

En Java, le traitement et le transport des messages associés aux événements sont assurés par deux objets dans le cadre d'un modèle de communication dénommé le modèle de traitement des événements par délégation (Delegation Event Model) :

Le message est envoyé par une source ou déclencheur de l'événement qui sera un composant Java, à un récepteur ou écouteur de l'événement qui est chargé de gérer l'événement, ce sera un objet de la classe des écouteurs instancié et ajouté au composant.


La méthode de programmation de l'interception des événements est nettement plus lourde en Java qu'en Delphi et en Visual Basic. Ce sont des classes abstraites dont le nom généralement se termine par Listener. Chacune de ces classes étend la classe abstraite d'interface EventListener. Toutes ces classes d'écouteurs d'événements sont situées dans le package java.awt.event, elles se chargent de fournir les méthodes adéquates aux traitements d'événements envoyés par un déclencheur.

Voici la liste des interfaces d'écouteurs d'événements tirées du JDK 1.4.2
Action, ActionListener, AdjustmentListener, AncestorListener, AWTEventListener, BeanContextMembershipListener, BeanContextServiceRevokedListener, BeanContextServices, BeanContextServicesListener, CaretListener, CellEditorListener, ChangeListener, ComponentListener, ContainerListener, DocumentListener, DragGestureListener, DragSourceListener, DropTargetListener, FocusListener, HyperlinkListener, InputMethodListener, InternalFrameListener, ItemListener, KeyListener, ListDataListener, ListSelectionListener, MenuDragMouseListener, MenuKeyListener, MenuListener, MouseInputListener, MouseListener, MouseMotionListener, PopupMenuListener, PropertyChangeListener, TableColumnModelListener, TableModelListener, TextListener, TreeExpansionListener, TreeModelListener, TreeSelectionListener, TreeWillExpandListener, UndoableEditListener, VetoableChangeListener, WindowListener.

Les événements possibles dans Java sont des objets (un événement est un message contenant plusieurs informations sur les états des touches de clavier, des paramètres,...) dont les classes sont dans le package java.awt.event.

Voici quelques classes générales d'événements possibles tirée du JDK 1.2.2:
ActionEvent, AdjustmentEvent, AncestorEvent, ComponentEvent, InputMethodEvent, InternalFrameEvent, InvocationEvent, ItemEvent, TextEvent.
A titre d'exemple examinons comment intercepter
un click de souris sur un bouton

Supposons avoir défini le bouton : Button bouton = new Button("Entrez");

Il nous faut choisir une classe d'écouteur afin de traiter l'événement click de souris. Pour intercepter un click de souris nous disposons de plusieurs moyens, c'est ce qui risque de dérouter le débutant. Nous pouvons en fait l'intercepter à deux niveaux.
 

Interception de bas niveau :

Les classes précédentes se dérivent en de nombreuses autres sous-classes. Par exemple, la classe MouseEvent qui encapsule tous les événements de souris de bas niveau, dérive de la classe ComponentEvent :
 

+--java.awt.event.ComponentEvent
                    |
                    +--java.awt.event.InputEvent
                          |
                          +--java.awt.event.MouseEvent
 

Nous pourrons par exemple, choisir la classe abstraite MouseListener (donc non instanciable, mais héritable) dont la fonction et d'intercepter (écouter) les événements de souris (press, release, click, enter, et exit).

Il existe une classe héritée de la classe des MouseListener qui permet d'instancier des écouteurs de souris, c'est la classe des MouseAdapter.

Dans ce cas il suffit de redéfinir la méthode qui est chargée d'intercepter et de traiter  l'événement qui nous intéresse.
 
Méthode à redéfinir Action déclenchant l'événement
void mouseClicked(MouseEvent e) invoquée lorsqu'il y a eu un click de souris sur le composant.
void mouseEntered(MouseEvent e) invoquée lorsque la souris entre dans le rectangle visuel du composant.
void mouseExited(MouseEvent e) invoquée lorsque la souris sort du rectangle visuel du composant.
void mousePressed(MouseEvent e) invoquée lorsqu'un des boutons de la souris a été appuyé sur le composant.
void mouseReleased(MouseEvent e) invoquée lorsqu'un des boutons de la souris a été relâché sur le composant.

L'événement est passé en paramètre de la méthode : mouseClicked(MouseEvent e)

1°) Nous construisons une classe InterceptClick héritant de la classe abstraite MouseAdapter et nous redéfinissons la méthode mouseClicked :
class InterceptClick extends MouseAdapter
{
     public void mouseClicked(MouseEvent e)
     { //.... actions à exécuter.
     }
}

2°) ensuite nous devons instancier un objet écouteur de cette classe InterceptClick :
InterceptClick clickdeSouris = new InterceptClick( );

3°) enfin nous devons ajouter cet écouteur à l'objet bouton :
bouton.addMouseListener(clickdeSouris);

Les étapes 2° et 3° peuvent être recombinées en une seule étape:
bouton.addMouseListener( new InterceptClick(  ) );

Remarque :
Afin de simplifier encore plus l'écriture du code, Java permet d'utiliser ici  une classe anonyme (classe locale sans nom) comme paramètre effectif de la méthode addMouseListener. On ne déclare pas de nouvelle classe implémentant la classe abstraite MouseAdapter, mais on la définie anonymement à l'intérieur de l'appel au constructeur.

Les étapes 1°, 2° et 3° peuvent être alors recombinées en une seule :
Classe dérivée de MouseAdapter
Classe anonyme
class InterceptClick extends MouseAdapter
{
     public void mouseClicked(MouseEvent e)
     { //.... actions à exécuter.
     }
}
Méthode xxx :
InterceptClick clickdeSouris = new InterceptClick( );
bouton.addMouseListener(clickdeSouris);
Méthode xxx :

bouton.addMouseListener ( new MouseAdapter( )   {
         public void mouseClicked(MouseEvent e)
        {//.... actions à exécuter.
         }
 } );
l
a référence à l'objet d'écouteur n'est pas accessible.

La classe anonyme est recommandée lorsque la référence à l'objet d'écouteur n'est pas utile. On se trouve dans le cas semblabe à Delphi où l'écouteur est l'objet de bouton lui-même.

Toutefois, il est possible de considérer qu'un click de souris qui est une action particulière, n'est qu'un cas d'action parmi d'autres possibles. Il existe une classe d'événement générique qui décrit tous les autres événements dont le cas particulier du click de souris sur un bouton, c'est la classe java.awt.event.ActionEvent. Un événement est donc un objet instancié de la classe ActionEvent, cet événement générique est passé à des écouteurs génériques de la classe des ActionListener, à travers l'ajout de l'écouteur au composant par la méthode addActionListener.

Nous allons donc reprendre la programmation de notre objet bouton de la classe des Button avec un écouteur de plus haut niveau de la classe des ActionListener.
 

Interception de haut niveau ou sémantique :

Sun a divisé d'une façon très artificielle les événements en deux catégories : les événements de bas niveau et les événements sémantiques : Les événement de bas niveau représentent des événements système de gestion de fenêtre de périphérique, souris, clavier et les entrées de bas niveau, tout le reste est événement sémantique.

Toutefois, Java considère qu'un click de souris sur un bouton qui est une action particulière de bas niveau, est aussi une action sémantique du bouton.

Il existe une classe d'événement générique qui décrit tous les autres événements dont le cas particulier du click de souris sur un bouton, c'est la classe java.awt.event.ActionEvent. Un événement est donc un objet instancié de la classe ActionEvent, cet événement générique est passé à des écouteurs génériques de l'interface ActionListener, à travers l'ajout de l'écouteur au composant par la méthode addActionListener. 

Nous allons donc reprendre la programmation de notre objet bouton de la classe des Button avec cette fois-ci un écouteur de plus haut niveau : un objet construit à partir d'implementation de l'interface ActionListener.

L'interface  ActionListener, n'a aucun attribut et ne possède qu'une seule méthode traitant l'événement ActionEvent :
 
la Méthode à redéfinir Action déclenchant l'événement
public void actionPerformed ( ActionEvent e ) Toute action possible sur le composant.

Nous pouvons comme dans le traitement par un événement de bas niveau, décomposer les lignes de code en créant une classe implémentant la classe abstraite des ActionListener, ou bien créer une classe anonyme. La démarche étant identique à l'interception de bas niveau, nous livrons directement ci-dessous les deux programmes Java équivalents :
 
Version avec une classe implémentant ActionListener
import java.awt.*;
import java.awt.event.*;

class EventHigh implements ActionListener
{ public void actionPerformed(ActionEvent e)
  {   //.... actions à exécuter.
  }
}
class ApplicationEventHigh
{
   public static void main(String [ ] arg) 
   { ....
    Button bouton = new Button("Entrez");
    bouton.addActionListener(newEventHigh( ));
    .... }
}
 

Version avec une classe anonyme
import java.awt.*;
import java.awt.event.*;

class ApplicationEventHigh
{
   public static void main(String [ ] arg) 
   { ....
    Button bouton = new Button("Entrez");
    bouton.addActionListener(new EventHigh( )
     {public void actionPerformed (ActionEvent e)
       {//.... actions à exécuter.
       }
      } );
    ....
   }
}
 

Nous voyons sur ce simple exemple, qu'il est  impossible d'être exhaustif tellement les cas particuliers foisonnent en Java, aussi allons nous programmer quelques interceptions d'événements correspondant à des situations classiques. Les évolutions sont nombreuses depuis la version 1.0 du JDK et donc seuls les principes sont essentiellement à retenir dans notre approche.

En outre, tous les objets de composants ne sont pas réactifs à l'ensemble de tous les événements existants, ce qui nécessite la connaissance des relations possibles pour chaque composant. Cet apprentissage est facilité par des outils qui classifient les événements par objet et engendrent le squelette du code du traitement à effectuer pour chaque événement.
 

La construction d'une IHM efficace en Java, s'effectuera avec un RAD comme JBuilder équivalent Delphi pour Java ou NetBeans de Sun, qui génère automatiquement les lignes de codes nécessaires à l'interception d'événements et donc simplifie l'apprentissage et la tâche du développeur !

Voici regroupés dans JBuilder la liste des événements auquel un bouton est sensible

Vous remarquerez que actionPerformed et mouseClicked sont les méthodes avec lesquelles nous traiterons l'événement click soit en haut niveau, soit en bas niveau. JBuilder agissant comme générateur de code, construira automatiquement les classes anonymes associées à votre choix.

Appliquons la démarche que nous venons de proposer à un exemple exécutable.
 
 

3.3 Terminer une application par un click de bouton

Pour arrêter la machine virtuelle Java et donc terminer l'application qui s'exécute, il faut utiliser la méthode exit( ) de la classe System. Nous programmons cette ligne d'arrêt lorsque l'utilisateur clique sur un bouton présent dans la fenêtre à l'aide de l'événement de haut niveau..

1°) Implémenter une classe héritant de la classe abstraite des ActionListener :
Cette classe ActionListener ne contient qu'une seule méthode < public void actionPerformed(ActionEvent e) > dont la seule fonction est d'être invoquée dès qu'un événement quelconque est transmis à l'objet ActionListener à qui elle appartient (objet à ajouter au composant), cette fonction est semblable à celle d'un super gestionnaire générique d'événement et c'est dans le corps de cette méthode que vous écrivez votre code.  Comme la classe ActionListener est abstraite, on emploi le mot clef implements au lieu de extends pour une classe dérivée.
Nous devons redéfinir (surcharge dynamique) la méthode actionPerformed(ActionEvent e) avec notre propre code :
 
Classe dérivée de ActionListener
import java.awt.*;
import java.awt.event.*;

class ListenerQuitter implements ActionListener
{ public void actionPerformed(ActionEvent e)
  { System.exit(0); // arrêter la machine java
  }
}

2°) Instancier et ajouter un objet de la classe héritant de ActionListener :

Un objet de cette classe ListenerQuitter doit être créé pour être ensuite ajouté dans le composant qui sera chargé de fermer l'application :
ListenerQuitter gestionbouton = new ListenerQuitter( );

Cet objet maintenant créé peut être ajouté au composant qui lui enverra l'événement. Cet ajout a lieu grâce à la méthode addActionListener de la classe des composants : (par exemple ajouter ce gestionnaire à Button Unbouton) :

Button Unbouton;
Unbouton.addActionListener(gestionbouton);

Les deux actions précédentes pouvant être combinées en une seule équivalente:
Unbouton.addActionListener( new ListenerQuitter( ));
 
Méthode main
public static void main(String [] arg) {
     Frame fen = new Frame ("Bonjour" );
     fen.setBounds(100,100,150,80);
     fen.setLayout(new FlowLayout( ));
     Button quitter = new Button("Quitter l'application");
     quitter.addActionListener(new ListenerQuitter( ));
     fen.add(quitter);
     fen.show( );
   }

 
Le programme Java complet
import java.awt.*;
import java.awt.event.*;

class ListenerQuitter implements ActionListener
{ public void actionPerformed(ActionEvent e)
  { System.exit(0); // arrêter la machine java
  }
}
class AppliBoutonQuitter
{
  public static void main(String [] arg) {
     Frame fen = new Frame ("Bonjour" );
     fen.setBounds(100,100,150,80);
     fen.setLayout(new FlowLayout( ));
     Button quitter = new Button("Quitter l'application");
     quitter.addActionListener(new ListenerQuitter( ));
     fen.add(quitter);
     fen.show( );
   }
}

La fenêtre associée à ce programme :

 

Voici une version de la méthode main du programme précédent dans laquelle nous affichons un deuxième bouton "Terminer l'application" auquel nous avons ajouté le même gestionnaire de fermeture de l'application :
 
Méthode main
public static void main(String [] arg) {
     Frame fen = new Frame ("Bonjour" );
     fen.setBounds(100,100,150,80);
     fen.setLayout(new FlowLayout( ));
     ListenerQuitter obj =  new ListenerQuitter( );
     Button quitter = new Button("Quitter l'application");
     Button terminer = new Button("Terminer l'application");
     quitter.addActionListener(obj);
     terminer.addActionListener(obj);
     fen.add(quitter);
     fen.add(terminer);
     fen.show( );
   }

Les deux boutons exécutent la même action : arrêter l'application

Java permet d'utiliser, comme nous l'vons indiqué plus haut, une classe anonyme (classe locale sans nom) comme paramètre effectif de la méthode addActionListener.

Au lieu d'écrire :
terminer.addActionListener(new ListenerQuitter( ));
 

La classe anonyme remplaçant tout le code :

terminer.addActionListener(new ActionListener( )   {
      public void actionPerformed(ActionEvent e)
      {
         System.exit(0);
      }
   }
);

Nous conseillons au lecteur de reprogrammer cet exemple à titre d'exercice,  avec l'événement click de bas niveau.
 
 

3.4 Evénements et objets

Exemple - 1 :  Toujours des boutons

Intérêt d'implémenter une interface XXXListener

Un événement est donc un message constitué suite à une action qui peut survenir à tout moment et dans divers domaines (click de souris, clavier,...), cela dépendra uniquement de l'objet source qui est le déclencheur de l'événement.

Nous allons à partir d'un bouton accéder à d'autres composants présents sur la même fiche, pour cela nous passerons en paramètre au constructeur de la classe implémentant l'interface ActionListener les objets à modifier lors de la survenue de l'événement. 

L'utilisation d'une telle classe class ListenerGeneral implements ActionListener est évident : nous pouvons rajouter à cette classe des champs et des méthodes permettant de personnaliser le traitement de l'événement.

Soit au départ l'interface suivante :

Nous programmons :
 


 
Le programme Java complet
import java.awt.*;
import java.awt.event.*;

class ListenerGeneral implements ActionListener
{ Label etiq;
  Frame win;
  Button bout
 //constructeur :
  public ListenerGeneral(Button bouton, Label etiquette, Frame window)
  { this.etiq = etiquette;
     this.win = window
     this.bout = bouton
   }
  public void actionPerformed(ActionEvent e)
   // Actions sur l'étiquette, la fenêtre, le bouton lui-même :
  { etiq.setText("changement");
     win.setTitle ("Nouveau titre"); 
     win.setBackground(Color.yellow);
     bout.setLabel("Merci");
  }
}
class ListenerQuitter implements ActionListener
{ public void actionPerformed(ActionEvent e)
  { System.exit(0);
  }
}

class AppliWindowEvent
{
  public static void main(String [] arg) {
   Frame fen = new Frame ("Bonjour" );
   fen.setBounds(100,100,250,120);
   fen.setLayout(new FlowLayout( ));
   Button entree = new Button("Entrez");
   Button quitter = new Button("Quitter l'application");
   Label texte = new Label("Cette ligne est du texte");
   entree.addActionListener(new ListenerGeneral( entree, texte, fen ));
   quitter.addActionListener(new ListenerQuitter( ));
   fen.add(texte);
   fen.add(entree);
   fen.add(quitter);
   fen.show( );
   }
}

Voici ce que devient l'interface  après un click du bouton "Entrez" :
---> 
 
 

Exemple - 2 :  Fermer une fenêtre directement

Intérêt d'hériter d'une classe XXXAdapter

Nous voulons pour terminer les exemples et utiliser un autre composant que le Button, fermer une fenêtre classiquement en cliquant sur l'icône du bouton de fermeture situé dans la barre de titre de la fenêtre et donc arrêter l'application. La démarche que nous adoptons est semblable à celle que nous avons tenue pour le click de bouton.

La documentation Java nous précise que la classe abstraite des écouteurs qui à trait aux événements de bas niveau des fenêtres, se dénomme WindowListener.(équivalente à MouseListener). Les événements de bas niveau sont des objets instanciés à partir de la classe java.awt.event.WindowEvent qui décrivent les différents états d'une fenêtre

Il existe une classe héritée de la classe des WindowListener qui permet d'instancier des écouteurs d'actions sur les fenêtres, c'est la classe des WindowAdapter. Dans ce cas, comme précédemment, il suffit de redéfinir la méthode qui est chargée d'intercepter et de traiter  l'événement de classe WindowEvent  qui nous intéresse.
 
Méthode à redéfinir Action déclenchant l'événement
void windowActivated(WindowEvent e) invoquée lorsqu'une fenêtre est activée.
 
void windowClosed(WindowEvent e) invoquée lorsqu'une fenêtre a été fermée.
 
void windowClosing(WindowEvent e) invoquée lorsqu'une fenêtre va être fermée.
 
void windowDeactivated(WindowEvent e) invoquée lorsqu'une fenêtre est désactivée.
 
void windowDeiconified(WindowEvent e) invoquée lorsqu'une fenêtre est sortie de la barre des tâches.
 
void windowIconified(WindowEvent e) invoquée lorsqu'une fenêtre est mise en icône dans la barre des tâches.
 
void windowOpened(WindowEvent e) invoquée lorsqu'une fenêtre est ouverte.
 

Dans notre cas c'est la méthode void windowClosing(WindowEvent e) qui nous intéresse, puisque nous souhaitons terminer l'application à la demande de fermeture de la fenêtre.

Nous écrivons le code le plus court : celui associé à une classe anonyme .
 
Version avec une classe anonyme
import java.awt.*;
import java.awt.event.*;

class ApplicationCloseWin

   public static void main(String [ ] arg) 
   {
      Frame fen = new Frame ("Bonjour" );
      fen.addWindowListener (new WindowAdapter( )   {
         public void windowClosing(WindowEvent e)
        { System.exit(0);
         }
      });
     fen.setBounds(100,100,250,150);
     fen.show( );
   }
}

Affiche la fenêtre ci-dessous (les 3 boutons de la barre de titre fonctionnent comme une fenêtre classique, en particulier le dernier à droite ferme la fenêtre et arrête l'application lorsque l'on clique dessus)