====== Introduction aux interfaces graphiques ======
L'objectif de ce TP est de comprendre comment fonctionnent les interfaces graphiques en java.
Il comporte deux parties:
- Introduction aux composants swing et au positionnement;
- La programmation évènementielle et les écouteurs.
===== Les composants graphiques en java =====
Il existe plusieurs API en java permettant d'utiliser des composants graphiques. Nous allons en utiliser une en particulier: l'API **swing**.
Cette API est décrite dans la javadoc de java; son package est **javax.swing**.
Les composants de cette api ont donc comme référence **javax.swing.NonDuComposant**.
La plupart commencent par la lettre **J**: **J**Frame (fenêtre), **J**Button (bouton), **J**TextField (champ texte), etc.
==== Création d'une fenêtre ====
Voici comment créer une fenêtre (**JFrame**)
import javax.swing.JFrame;//utilisation du composant JFrame du package javax.swing
/**
* Cette classe nous servira de test pour les composants graphiques
*/
public class TestGraphique
{
/**
* Constructeur: fabrique une fenêtre
*/
public TestGraphique()
{
JFrame fenetre = new JFrame("Ma première fenêtre");
fenetre.setVisible(true);//pour afficher la fenêtre
}
public static void main(String [] arguments)
{
new TestGraphique();
}
}
Compilez et exécutez le code. Où se trouve la fenêtre?
Comment quitter le programme? (Rappel: **ctrl + C** permet d'interrompre un programme java dans la console).
Pour remédier à ce problème, ajoutez l'instruction suivante:
fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
**Conclusion**: nous savons faire une fenêtre, mais elle est très (trop) simple pour l'instant!
==== Créer une fenêtre personnalisée ====
Java nous donne une version basique de fenêtre, mais cette fenêtre ne nous permet pas de faire grand chose.
En fait, nous voudrions faire une fenêtre personnalisée, avec des boutons, des zones de texte, etc.
**JFrame** est intéressant car il nous offre un cadre (**JContainer**). A nous maintenant d'y ajouter ce que l'on veut!
=== 1ère technique: créer un cadre personnalisé ===
public TestGraphique()
{
JFrame fenetre = new JFrame("Ma première fenêtre");
fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel cadre = new JPanel(); //cadre personnalisé pour la fenêtre
//création des composants à incorporer:
JLabel etiquette = new JLabel("Entrez une addition");
JTextField champSaisie = new JTextField(10);
JButton bouton = new JButton("Calculer");
//ajout des composants au cadre (panneau)
cadre.add(etiquette);
cadre.add(champSaisie);
cadre.add(bouton);
//changement du cadre de la fenêtre avec notre cadre personnalisé
fenetre.setContentPane(cadre);
fenetre.setVisible(true);
}
=== 2ème technique: l'héritage ===
Il est aussi possible d'utiliser l'héritage pour fabriquer une fenêtre personnalisée.
Faites hériter la classe **TestGraphique** de **JFrame** et adaptez le code.
Pour accéder directement au cadre, vous pouvez utilisez **getContentPane()** de la classe **JFrame**.
==== Positionner les composants ====
Pour l'instant, nos composants sont ajoutés sur la même ligne les uns à la suite des autres.
Nous allons changer ce comportement du cadre en lui donnant un nouveau type de gestionnaire de positionnement (ou gestionnaire de répartition).
Il existe plusieurs types de gestionnaires de positionnement (Layout): **BorderLayout**, **GridLayout**, **GridBagLayout**, **FlowLayout**, etc.
Nous allons utiliser un des plus simples, le **BorderLayout** du package **java.awt**.
Ce composant permet d'indiquer où placer un composant avec 5 contraintes: au nord, au sud, à l'est, à l'ouest, ou au centre.
On ne peut placer qu'un seul composant sur une position. Profitons-en pour donner une taille raisonnable à notre fenêtre.
//utilisation d'un nouveau Layout pour le cadre
cadre.setLayout(new BorderLayout());
//ajout des composants au cadre (panneau)
cadre.add(etiquette,BorderLayout.WEST);
cadre.add(champSaisie, BorderLayout.CENTER);
cadre.add(bouton, BorderLayout.SOUTH);
fenetre.setSize(300,100);//change la taille de la fenêtre
Et voilà pour la base! Complétez votre interface graphique en ajoutant d'autres composants, en changeant les couleurs, en utilisant un autre gestionnaire de positionnement, etc.
===== 2ème partie: les évènements =====
==== Les bases ====
Nous savons maintenant faire une interface graphique de base en java. Mais nous n'avons pas encore de possibilités d'interaction avec elle.
En java, une interface graphique génère une quantité d'évènements: déplacement de la fenêtre, clic sur un composant, déplacement de la souris, frappe au clavier, etc... La plupart du temps, seul un tout petit nombre de ces évènements nous intéresse.
Dans notre cas, nous allons commencer par gérer l'évènement produit lorsqu'on clique ou qu'on active le bouton.
Ce type d'évènement est un **ActionEvent**. Pour pouvoir l'intercepter, nous allons utiliser un composant spécifique, un **ActionListener**.
**ActionListener** est une interface. Il va donc falloir l'implémenter pour pouvoir écrire notre code.
- Commençons par faire implémenter ActionListener par TestGraphique. Quelle méthode doit-on redéfinir?
- Indiquons maintenant au bouton quel(s) est (sont) son (ses) écouteur(s):
bouton.addActionListener(this);
Que se passe-t-il maintenant lorsqu'on clique sur le bouton?
Que se passe-t-il si on écrit deux fois //bouton.addActionListener(this);//?
Utilisez maintenant la méthode //calcule()// suivante pour réaliser le calcul. Pourquoi est-elle **private**?
Que devez-vous modifier pour pouvoir l'utiliser?
/** Mais que fait donc cette méthode? */
private void calcule()
{
String resultat = "";
String saisie = champSaisie.getText();
String [] operandes = saisie.split("[+]");//+doit être échappé car il est utilisé dans les expressions régulières
if(operandes.length == 2)
{
int i = 0;
int j = 0;
try
{
i = Integer.parseInt(operandes[0]);
j = Integer.parseInt(operandes[1]);
resultat = saisie + "=" + (i+j);
}
catch(NumberFormatException nfe)
{
resultat = "Erreur de saisie";
}
}
else
{
resultat = "Erreur: addition simple uniquement";
}
champSaisie.setText(resultat);
}
==== Autres méthodes ====
Il existe d'autres moyens de lier une méthode avec un ActionListener et un JButton.
=== Classe externe implémentant ActionListener ===
Rien n'empêche d'utiliser un ActionListener défini dans une autre classe!
Ajouter une instance de MonActionListener à votre bouton. Que se passe-t-il?
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
/**
* Cette classe implémente un ActionListener qui ferme l'application en cours
*/
public class MonActionListener implements ActionListener
{
//constructeur
public MonActionListener(){}
public void actionPerformed(ActionEvent ae)
{
System.out.println("Au revoir");
System.exit(0);
}
}
Les principaux avantages:
* éviter de trop surcharger la classe fenêtre
* séparer la partie graphique de la partie fonctionnelle (principe du M-V-C)
* pouvoir utiliser des techniques d'objet sur les écouteurs (héritage, redéfinitions, etc.)
* regrouper les traitements évènementiels
Les principaux inconvénients:
* si l'évènement concerne uniquement la partie graphique, il est plus simple de le gérer dans la partie graphique!
* la conception est un peu plus complexe...
* ... et la mise en œuvre un peu plus technique, même si elle permet souvent de réaliser un code "propre" et modulaire
=== Implémentation à la volée (à la JDev) ===
Il est aussi possible de réaliser les opérations d'affectation (//addActionListener//), d'implémentation (définition de //actionPerformed//) et d'appel à méthode extérieure(//calcule()//) en une seule instruction!
Le principe est le suivant: créer un ActionListener et lui indiquer comment redéfinir la méthode //actionPerformed// AU MOMENT de sa création.
C'est la technique utilisée par défaut par JDev. **//Ca ne veut pas dire que vous êtes obligés de l'utiliser!//**
bouton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
System.out.println("Je t'ai reconnu JDev");
calcule();
}
});
Les principaux avantages:
* tout est écrit au même endroit au même moment
Avantage ou inconvénient:
* l'interface graphique est directement liée à la méthode appelée (//calcule()//)
Les principaux inconvénients:
* il n'est pas facile de modifier l'ActionListener
* 1 composant = 1 ActionListener, pas de regroupement
* le code n'est pas facile à comprendre, et souvent les programmeurs ne comprennent pas ce qu'ils font... ou ont l'impression que ça fonctionne par "magie"!
===== Pour finir... =====
Voici quelques pistes pour vous permettre de faire évoluer votre calculatrice:
- ajoutez un bouton "effacer" qui vide le champ de saisie; vous pourrez avoir besoin de la méthode **getSource()** de ActionEvent...
- utilisez un GridLayout ou un GridBagLayout à la place du BorderLayout (voir tutoriels);
- utilisez un WindowsListener qui affiche "au revoir" lorsque vous fermez la fenêtre;
- utiliser un KeyListener pour contrôler la saisie....
- etc!
===== Tutoriels et liens =====
* Un des meilleurs et des plus simples à ma connaissance: [[http://www.infres.enst.fr/~charon/coursJava/interSwing/index.html|tutoriel swing d'Irène Charon]]
* Sur le site du zéro: [[http://www.siteduzero.com/tutoriel-3-10601-programmation-en-java.html#part_10599|Java et la programmation évènementielle]]