CONSTRUIRE UNE IHM

 

Introduction

 

On désigne sous le nom d’interface, ou bien IHM (Interface homme-machine) la partie visible à l’écran d’une application. L’interface est le 1er contact qu’a un utilisateur avec une application. C’est elle qui détermine la première impression des utilisateurs : il est donc important de lui accorder un intérêt tout particulier.

 

Pour développer des IHM, nous allons étudier la librairie Java : Swing (package), descendante de la librairie AWT (utilisée pour créer des applets si le client n’a pas le plugin Java correspondant)

 

Différence majeur entre AWT et SWING :

AWT : L’apparence des composants graphiques dépend du système d’exploitation car elle utilise les ressources systèmes. SWING : Les éléments graphiques développés à l’aide SWING sont dessinées par la librairie elle-même : l’affichage n’est pas délégué au système : cette architecture est plus souple mais plus consommatrice en temps car les calculs sont exécutés par la machine virtuelle Java et non par le système d’exploitation !

 

Les classes composant la librairie SWING

sont des classes dérivées des classes composant AWT !

Exemple : la classe JFrame (de Swing)

est une classe dérivée de la classe Frame (d’AWT).

Pour les différencier, il suffit d’observer la 1ère lettre !

 

Concepts fondamentaux

 

Les composants :

 

Les gestionnaires de mise en forme : ils sont associés à un conteneur : ils servent à gérer la disposition des composants au sein d’un même conteneur.

 

Mécanisme de construction

 

La conception d’une interface graphique en Java ressemble un peu à un jeu de construction. On imbrique des composants simples dans des conteneurs qui peuvent être eux mêmes imbriqués dans d’autres conteneurs !

 

 

 Ergonomie

 

Vous concevez un logiciel pour un utilisateur type, essayez donc de comprendre ces préoccupations.

 

 

 

Phases de création d'une interface :

 

1. Réflexion sur l'interface :  profitez de votre expérience d'utilisateur de logiciels, faites des interfaces assez semblables … Faites participer le ou les futurs utilisateurs

2. Création :  sur papier des différents écrans

3. Test :  par des futurs utilisateurs !

 

4. Ecriture du code…..

 

Règle : constuire une  Interface normalisée

Pour cela, utiliser toujours les mêmes repères :

 

 

 

Quelques règles en vrac

Ø     Un écran se lit de bas en haut et de gauche à droite. Positionnez donc les informations importantes dans le coin supérieur gauche de l'écran. Alignez les données verticalement et horizontalement !

Ø     Les boutons de commande doivent être soit centrés le long du bord inférieur de l'écran, soit empilés dans le coin supérieur droit ou inférieur droit. Limitez le nombre de boutons. Utilisez une taille identique pour les boutons de commande.

Ø     Un seul style de police peut suffire.

Ø      Affecter des touches d'accès rapide à chacun des éléments de l'interface (au cas où la souris ne fonctionne pas ou mal (portables), ou bien pour les utilisateurs qui vont plus vite au clavier).

Ø     Demander confirmation à l'utilisateur pour toute opération destructrice.

Ø     Désactiver temporairement les contrôles qui n'ont pas de raison d'être utilisés....

 

Choix du type de fenêtre

 

Vous pouvez définir vos propres fenêtres.

 

Dans ce cas, vous utiliserez :

·        Soit la classe JFrame : elle permet la création de fenêtres principales. Rem : Seule une fenêtre de classe JFrame peut comporter un menu !
 
·        Soit la classe JDialog : elle permet la création de fenêtres secondaires ou boites de dialogue. Rem : Seule une fenêtre de classe JDialog peut être modale ! Une fenêtre B est modale par rapport à une fenêtre A si l’affichage de B empêche l’accès et non pas la vue à la fenêtre A.
 
·         Soit la classe JInternalFrame : elle permet la création de fenêtres internes à une fenêtre de classe JFrame.

 

Mais toutes les fenêtres ont des propriétés communes. Elles ont toutes :

·        un contenu que l’on récupère grâce à la méthode : Container getContentPane()

·        une taille par défaut : 0 * 0 que l’on peut modifier avec setSize ou setBounds

·        un titre que l’on peut modifier avec setTitle ( String title)

·        un comportement par défaut : lors de sa fermeture,l’action est de type HIDE_ON_CLOSE que l’on peut modifier à l’aide de la méthode setDefaultCloseOperation(int operation)

·        ….

 

Vous pouvez utiliser des fenetres predefinies

 

classe : JOptionPane

 

avec les méthodes statiques suivantes :

 

classe : JFileChooser

avec les méthodes non statiques suivantes :

·        int showOpenDialog(Component parent)

·        int showSaveDialog(Component parent)

et les constructeurs suivants :

·        JFileChooser(File currentDirectory)

·        JFileChooser()

 

classe : JColorChooser

avec la méthode  statique suivante :

·        Color  showDialog(Component component, String title, Color initialColor)

 

Les étapes lors de la définition d’une classe IHM

 

Définir une IHM :

 

C’est définir une nouvelle classe héritant d’une classe Java.

 

  1. Etape préalable :  import des modules graphiques

 

  1. Choix du type de  fenêtre = choix de  la classe mère : JFrame / JDialog / JInternalFrame

 

  1. Déclaration des composants comme attributs

 

  1. Initialisation des composants

 

  1. Ajout des composants au contenu de la fenêtre

        ( étape préalable : récupérer le contenu de la fenêtre)

 

  1. Positionnement des composants

        ( étape préalable : supprimer tout gestionnaire de mis en forme automatique)

 

 

Exemple : Fichier MaFenetre.java

 

1

 

2

 

 

3

 

 

 

 

4

 

 

 

5

 

 

 

6

import javax.swing.* ;

import java.awt.* ;

class MaFenetre extends JDialog

   {

     private JLabel labNom ;

     private JTextField txtNom ;

     private Container contenu ;

 

     public MaFenetre( )

     {

            super(); // initialise les attributs d’une JDialog

             this.labNom = new JLabel (“nom”);

             this.txtNom = new JTextField (“nom”);

 

             this.contenu = this.getContentPane( ) ;

             this.contenu.add (labNom ) ;

             this.contenu.add (txtNom ) ;

 

            this.contenu.setLayout(null);

             this.labNom.setBounds( 20, 20 , 80, 20 ) ;

             this.txtNom .setBounds( 100 , 20 , 80 , 20) ;

     }

  }

 

Créer une IHM

 

 C’est créer un objet. Exemple :

 

  Class TestIHM

   {

         public static void main ( String [] args)

           {

               MaFenetre maf= new MaFenetre();

               maf.setBounds(20,20,300,300);

               maf.setVisible(true);

             }

    }

 

 

Tour d’horizon des composants simples

 

JLabel

 

Cette classe permet de créer des « labels » : un label permet d’afficher du texte statique et/ou une image. Ce composant ne réagit pas aux interactions de l’utilisateur.

Avec les constructeurs :

·        JLabel()

·        JLabel(String txt)

·        JLabel(Icon image)

      Rem : Icon est une interface. ImageIcon est une classe implémentant Icon.

·        ImageIcon(String path)

 

 

JTextField , JTextArea  ou JPasswordField

 

      Ces classes permettent de créer des objets destinés à la saisie utilisateur.

      Elles ont toutes un constructeur par défaut et des méthodes bien utiles :

·        String getText() : permet de récupérer le texte saisi par l’utilisateur

·        void setText(String txt) : permet de modifier par programmation le contenu d’un champ

 

JButton

 

Cette classe permet de créer des boutons simples, ils peuvent contenir du texte et et/ou une image. Avec les constructeurs :

·        JButton(String txt)

·        JButton(Icon img),...

 

JCheckBox ou JRadioButton

 

Ces classes permettent de créer respectivement des cases à cocher et des boutons radio . Avec les constructeurs suivants :

·        JCheckBox(Icon icon)

·        JCheckBox(String txt)

·        JRadioButton(String txt),...

 

A la seule différence que les boutons radios doivent être regroupés au sein d’un groupe afin qu’un seul par groupe puisse être sélectionné. Il faut alors utiliser la classe ButtonGroup. Avec le constructeur suivant :

·        ButtonGroup()

Et la méthode d’ajout de bouton à un groupe :

·        void add(AbstractButton b)

 

JComboBox

 

Cette classe permet de créer des listes déroulantes. Avec les  constructeurs suivants :

·        JComboBox()

·        JComboBox(Vector list)

Et des méthodes bien utiles :

·         Object getSelectedItem()

·        int getSelectedIndex()

·        void addItem(Object anObject)

 

Rem : Vector est une classe avec un constructeur par défaut et une méthode bien utile :

·        boolean add(Object o)

 

 

JTable

 

Cette classe permet de créer des tables. Remarque : une table peut être créée indépendamment de tout autre composant, cependant, vous avez tout intérêt à l’associer à un conteneur JScrollPane. Exemple : ci dessous :

 

           

Avec les  constructeurs suivants :

·         JTable(Object[][] rowData, Object[] columnNames)

·        JTable()

 

Tour d’horizon des conteneurs autres que les fenêtres

 

JPanel:

 

La classe JPanel est très utilisée, elle permet la création de conteneurs dont l’aspect est réduit au strict minimum. C’est une sorte de rectangle qui peut être invisible !

Munie d’un constructeur par défaut.

 

JScrollPane : containers avec défilement

 

Avec les  constructeurs suivants :

·        JScrollPane()

·        JScrollPane(Component view)

 

JTabbedPane : un panneau à onglets

 

Munie d’un constructeur par défaut. Et d’une méthode bien utile :

·         void addTab(String title, Component component)

 

JInternalFrame

 

Cette classe sert à créer des fenêtres internes. Mais pour cela, il faut :

1.      Créer un autre conteneur de classe JDesktopPane appelé bureau.

      Avec le constructeur JDesktopPane()

2.      L’ajouter à un objet de type JFrame.

3.      Créer une JInternalFrame

4.      L’ajouter au bureau

 

Propriétés communes à tous les composants Swing

 

Beaucoup de propriétés sont communes à tous les composants car ce sont des propriétés définies au niveau de la super classe Component ou JComponent, donc toutes ses classes filles en héritent ! La liste qui suit est loin d’être exhaustive, si vous voulez en savoir plus sur les propriétés communes vous devez alors consulter la documentation sur les classes mères !

 

Tailles et position:

 

A l’aide des méthodes suivantes, vous pouvez positionner et tailler les composants :

·        void setLocation(int x, int y)

·        vois setSize(int width, int height)

·        void setBounds(intx, int y, int width, int height)

 

 

Activation et désactivation d’un composant :

 

Un composant peut être actif ou inactif. Un composant inactif est inaccessible pour l’utilisateur. Un composant peut être visible ou non visible. A l’aide des méthodes suivantes :

·         void setEnabled(boolean b)

·        void setVisible(boolean b)

 

 

Couleurs :

 

La couleur des composants peut être paramétrée grâce aux méthodes :

·        setBackGround (Color bg)

·        setForeGroung (Color fg)

 

 

Bordures :

 

            Il est possible d’associer une bordure à chaque composant à l’aide de la méthode :

·        void setBorder(Border border)

Rem : Border n’est qu’une interface. Il faut alors utiliser les classes suivantes :

LineBorder, MetalBorders.ButtonBorder,….


Créer des menus

                    

 

 

 

 

 

 

 

 

CLASS JMenuBar

Constructeurs

JMenuBar()
          Creates a new menu bar.

Méthodes

JMenu

add(JMenu c)
          Appends the specified menu to the end of the menu bar.

 

 

 

CLASS JMenu

Constructeurs

JMenu(String s)
          Constructs a new
JMenu with the supplied string as its text.

Méthodes

JMenuItem

add(JMenuItem menuItem)
          Appends a menu item to the end of this menu.

Void

addSeparator()
          Appends a new separator to the end of the menu.

 

 

 

CLASS JMenuItem

Constructeurs

JMenuItem(String text)
          Creates a
JMenuItem with the specified text.

JMenuItem(String text, Icon icon)
          Creates a
JMenuItem with the specified text and icon.

 

CLASS JFrame

Methodes

Void

setJMenuBar(JMenuBar mb)

Sets the menu bar for this frame to the specified menu bar.

 



Gestion des évènements

1er exemple : Rendre un bouton interactif

 

Ce 1er exemple traitera de l’événement le plus courant : l’événement Action : celui-ci correspond au clic de souris sur un bouton. Pour qu’il y ait gestion d’événements, il doit y avoir un écouteur d’événement, celui-ci définit les traitements à éxecuter en cas d’événement.

 

 

      Etape préalable : rappel des  librairies  gérant les événements

 

  1. Ajout d’un écouteur au composant – la fenêtre sera l’écouteur !

 

 

  1. Rendre la fenêtre capable d’écouter  : suivre les règles énoncées par l’interface ( ActionListener) qui définit le rôle d’un écouteur d’évènements de type Action.

 

  1. Coder les contraintes liées à la notion d’écouteur.

      ActionListener oblige les classes voulant  être ActionListener à définir la méthode

      actionPerformed.

 

  1. Programmez les traitements devant être déclenchés.

 

Exemple :

 

 

2

 

 

 

 

 

 

 

 

1

 

 

 

 

 

 

 

 

3

 

 

4

import java.awt.event.*;

public class MaFenetre extends JDialog  implements ActionListener

   {

     private JButton butOk ;

     private Container contenu ;

 

     public MaFenetre( )

     {

            super();

             this.butOk = new JButton (“OK”);

             this.butOk.addActionListener (this);

            this.contenu = this.getContentPane( ) ;

             this.contenu.add (butOK ) ;

 

            this.contenu.setLayout(null);

              this.butOK.setBounds( 20, 20 , 80, 20 ) ;

     }

 

    public void actionPerformed( ActionEvent evt)

     {

         if (evt.getSource() == butOK )

                JOptionPane.showMessageDialog(this, “OK”);

     }

  }

 

 

Qu’est ce qu’un événement ?

 

Toutes les interactions de l’utilisateur ( déplacement de la souris, clic sur un bouton, …) donnent naissance à des évènements. Un événement est représenté par un objet. Ces objets contiennent des informations concernant leur contexte d’apparition ( ex : composant graphique source de l’événement à l’aide de la méthode getSource() ) Mais, ils diffèrent selon la nature de l’événement :

 

Interaction utilisateur            => donne naissance à un                Objet de classe

Clic sur un composant simple ( ex : bouton )

ActionEvent

Clic sur un conteneur ( ex : JPanel )

MouseEvent

Sélection ou désélection d’un item

ItemEvent

Fermeture, agrandissement, maximisation  de la fenêtre

WindowEvent

....

 

Et selon leur nature, on peut avoir diverses informations. Par exemple pour un ActionEvent, on peut connaître l’instant de l’événement. Pour un MouseEvent, on peut connaître le nombre de clics, le bouton cliqué,….

Toutes ces classes héritent de la classe EventObject.

 

Class EventObject

getSource

 

 

Class ActionEvent

 long

getWhen()         Returns the timestamp of when this event occurred.

 

 

Class MouseEvent

 int

getButton()     Returns which, if any, of the mouse buttons has changed state.

 int

getClickCount()  Returns the number of mouse clicks associated with this event.

 int

getX()     Returns the horizontal x position of the event relative to the source component.

 int

getY()    Returns the vertical y position of the event relative to the source component.

 boolean

isPopupTrigger()       Returns whether or not this mouse event is the popup menu trigger event for the platform.

 

Class ItemEvent

 Object

getItem()                           Returns the item affected by the event.

 ItemSelectable

getItemSelectable()          Returns the originator of the event.

 int

getStateChange()              Returns the type of state change (selected or deselected).

 

 

Remarque : Le programmeur n’a pas à se soucier de la création des évènements. Mais par contre, il doit les intercepter afin pouvoir les traiter.

 


Comment réagir à un événement ?

 

Pour intercepter des événements, il faut utiliser des objets appelés « listeners ». Ce sont des objets à l’écoute d’un événement précis.

 

Un Objet  « événement »  de classe  peut être écouté       par      un objet  « listener » de classe

ActionEvent

ActionListener

MouseEvent

Mouse Listener

ItemEvent

ItemListener

WindowEvent

WindowListener

....

 

 

N’importe quel objet peut être « listener ». Généralement, on choisit pour écouteur la fenêtre, ainsi la fenêtre gère les événements qui peuvent avoir lieu à l’intérieur d’elle-même.

 

Remarque : ActionListener,… ne sont pas réellement des classes, ce sont des interfaces !

 

Définition : Une interface est une classe particulière, elle ne définit aucune variable d’instance et toutes ses méthodes sont abstraites. Une interface ne fait que représenter un ensemble de comportements, sans les implémenter.

 

Conséquence : Pour qu’une fenêtre soit « listener » d’événement de type ActionEvent, on dit qu’elle implémente l’interface ActionListener ! Ce qui implique qu’elle doit implémenter les comportements exigés par cette interface ( Soit dans ce cas : actionPerformed())

 

Mais ce statut d’écouteur n’est pas automatique, le listener doit s’abonner auprès du composant qui génère l’évènement. En s’abonnant, il demande ainsi au composant de le prévenir en cas d’événement. Cet abonnement se fait à l’aide des méthodes :  add….Listener.

 

 

Interface ActionListener

 void

actionPerformed(ActionEvent e)           Invoked when an action occurs.

 

Interface MouseListener

 void

mouseClicked(MouseEvent e)     Invoked when the mouse button has been clicked (pressed and released) on a component.

 void

mouseEntered(MouseEvent e)    Invoked when the mouse enters a component.

 void

mouseExited(MouseEvent e)      Invoked when the mouse exits a component.

 void

mousePressed(MouseEvent e)    Invoked when a mouse button has been pressed on a component.

 void

mouseReleased(MouseEvent e)  Invoked when a mouse button has been released on a component.

 

Interface ItemListener

 void

itemStateChanged(ItemEvent e)     Invoked when an item has been selected

 

Interface WindowListener

 void

windowActivated(WindowEvent e)          Invoked when the Window is set to be the active Window.

 void

windowClosed(WindowEvent e)               Invoked when a window has been closed as the result of calling dispose on the window.

 void

windowClosing(WindowEvent e)             Invoked when the user attempts to close the window from the window's system menu.

 void

windowDeactivated(WindowEvent e)     Invoked when a Window is no longer the active Window.

 void

windowDeiconified(WindowEvent e)     Invoked when a window is changed from a minimized to a normal state.

 void

windowIconified(WindowEvent e)     Invoked when a window is changed from a normal to a minimized state.

 void

windowOpened(WindowEvent e)      Invoked the first time a window is made visible.

 

 

Les différentes méthodes pour implémenter les listeners

 

Il existe différentes méthodes pour implémenter les listener :

  1. une classe implémentant elle même ses listener
  2. une classe indépendante implémentant les listener liée à une autre classe
  3. une classe interne
  4. une classe interne anonyme

 

Exemple  classe indépendante

public class TestFrame extends JFrame {
   private JButton butOK;
   public TestFrame(String title) {
      super(title);...
      GestEvt ge = new GestEvt();
      butOK.addActionListener (ge);
   }
 }
public class GestEvt implements ActionListener {
   public void actionPerformed(ActionEvent e) {
      System.exit(0);
   }
}

 

Exemple classe interne anonyme

public class TestFrame extends Jframe {
 
   public TestFrame1(String title) {
      super(title);
      addWindowListener(new WindowAdapter() {
         public void windowClosed(.WindowEvent e) {
            System.exit(0);
         };
      });              
   }
   
   public static void main(java.lang.String[] args) 
    {   TestFrame1 tf = new TestFrame1("TestFrame");   }
}
 

 Remarque : new WindowAdapter() {…} renvoie la référence d’un objet dont la classe est anonyme

et  dérive de WindowAdapter .Toutefois, Sun précise qu’il est important de ne pas abuser de ces classes et d’en limiter le nombre d’instructions afin de ne pas rendre le code illisible.

 



Les layouts ou

Les gestionnaires de mise en forme

 

 

 

Pourquoi déléguer la gestion de la position des composants à un layout, alors que le positionnement semble si simple ? Mais que se passe t’il si j’agrandis ma fenêtre ? Les composants doivent-ils s’agrandir ? Sinon, où ajoute t’on de l’espace ? Les layouts implémentent des stratégies de positionnement !

 

 

FlowLayout

 

Par défaut, ce layout est associé aux conteneurs excepté à la  frame.

 

Une stratégie simple et très utile est un placement des éléments graphique les uns à cotés des autres, de gauche à droite ! Lorsqu’il n’y a plus de place dans la fenêtre, les composants sont mis automatiquement en dessous !

 

        

 

Les composants peuvent être alignés à gauche, à droite ou centrés. Pour définir cela, il suffit de passer au constructeur du flowLayout une des valeurs suivantes : FlowLayout.LEFT, FlowLayout.RIGHT, FlowLayout.CENTER

 

FlowLayout(int align)
          Constructs a new
FlowLayout with the specified alignment and a default 5-unit horizontal and vertical gap.

FlowLayout(int align, int hgap, int vgap)
          Creates a new flow layout manager with the indicated alignment and the indicated horizontal and vertical gaps.

 

Exemple :

FlowLayout monLayout = new FlowLayout(FlowLayout.CENTER) ;

JPanel monPanel = new JPanel();

monPanel.setLayout(monLayout) ;

ou

monPanel.setLayout( new FlowLayout(FlowLayout.CENTER) ) ;

 

 


GridLayout

 

Le gridLayout implémente une stratégie simple et efficace. L’espace du conteneur est découpé en une grille Le nombre de colonnes et de lignes composant cette grille est fixé lors de l’instanciation du layout..

Toutes les cases et donc les composants contenus dedans auront donc la même taille.

 

GridLayout(int rows, int cols)
          Creates a grid layout with the specified number of rows and columns.

GridLayout(int rows, int cols, int hgap, int vgap)
          Creates a grid layout with the specified number of rows and columns and the indicated horizontal and vertical gaps.

 

Exemple :

FlowLayout monLayout = new GridLayout(3,2) ;

JPanel monPanel = new JPanel();

monPanel.setLayout(monLayout) ;

ou

monPanel.setLayout( new GridLayout(3,2) ) ;

 

 

BoxLayout

 

Le BoxLayout  permet d’aligner les composants les uns à côtés des autres, ou les uns en dessous des autres.

 

BoxLayout(Container target, int axis)
          Creates a layout manager that will lay out components along the given axis.

 Avec axis={BoxLayout.X_AXIS, BoxLayout.Y_AXIS}

 

Exemple :

JPanel monPanel = new JPanel();

BoxLayout monLayout = new BoxLayout(monPanel, X_AXIS) ;

monPanel.setLayout(monLayout) ;

 

 

BorderLayout

 

Par défaut, ce layout est associé aux conteneurs de type  frame.

 

Le borderLayout implémente une stratégie de positionnement tout à fait particulière. Il réserve dans l’espace de son conteneur 5 cases : case centrale, case nord, case sud, case ouest, case est. On peut mettre q’un seul composant dans une case ! Lorsque l’on utilise ce type  de Layout, il faut alors indiquer dans quelle zone on désire ajouter les autres composants à l’aide des varaiables suivantes : BorderLayout.CENTER, BorderLayout.WEST, BorderLayout.NORTH

 

 

BorderLayout()
          Constructs a new border layout with no gaps between components.

BorderLayout(int hgap, int vgap)
          Constructs a border layout with the specified gaps between components.

 

 

Exemple :

BoxLayout monLayout = new BoderLayout();

JPanel monPanel = new JPanel();

monPanel.setLayout(monLayout) ;

monPanel.add(new Jbutton(“OK”),BorderLayout.CENTER);

 

 

GridBagLayout

 

 

GridBagLayout représente le gestionnaire de placement le plus complexe et le plus flexible de la plateforme Java.

 

GridBagLayout aligne les composants en les plaçant dans une grille composée de colonnes et de lignes. Chaque cellule peut contenir un à plusieurs composants. D'autre part, contrairement à GridLayout, il est possible que les composants soient placés sur une ou plusieurs cellules. De même, les lignes et les colonnes dans la grille ne sont pas nécessairement toutes de la même hauteur et de la même largeur.

La gestion du placement des composants nécessitent l'instanciation de la classe GridBagConstraints chargée d'exposer les contraintes de positionnement.

Exemple :

Container panneauContenu = getContentPane();
GridBagLayout grille = new GridBagLayout();
GridBagConstraints contraintes = new GridBagConstraints();
contentPane.setLayout(grille);
JTextArea zoneTextuelle = new JTextArea("Une zone de texte");
contraintes.gridwidth = 3;
contraintes.gridheight = 5;
contraintes.gridx = 0;
contraintes.gridy = 1;
grille.setConstraints(zoneTextuelle, contraintes);
panneauContenu.add(zoneTextuelle);