IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 13 - Création de fenêtres & d'Applets

pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 

Il est également possible de créer un package de look and feel sur mesure, par exemple si on crée un environnement de travail pour une société qui désire une apparence spéciale. C'est un gros travail qui est bien au-delà de la portée de ce livre (en fait vous découvrirez qu'il est au-delà de la portée de beaucoup de livres dédiés à Swing).name="_Toc481064830">

Le presse-papier [clipboard]

JFC permet des opérations limitées avec le presse-papier système (dans le package java.awt.datatransfer). On peut copier des objets String dans le presse-papier en tant que texte, et on peut coller du texte depuis le presse-papier dans des objets String. Bien sûr, le presse-papier est prévu pour contenir n'importe quel type de données, mais la représentation de ces données dans le presse-papier est du ressort du programme effectuant les opérations de couper et coller. L'API Java clipboard permet ces extensions à l'aide du concept de «parfum» [flavor]. Les données en provenance du presse-papier sont associées à un ensemble de flavorsdans lesquels on peut les convertir (par exemple, un graphe peut être représenté par une chaîne de nombres ou par une image) et on peut vérifier si les données contenues dans le presse-papier acceptent le flavor qui nous intéresse.

Le programme suivant est une démonstration simple de couper, copier et coller des données String dans une face="Georgia">JTextArea. On remarquera que les séquences clavier utilisées normalement pour couper, copier et coller fonctionnent également. Mais si on observe un JTextField ou un JTextArea dans tout autre programme, on verra qu'ils acceptent aussi automatiquement les séquences clavier du presse-papier. Cet exemple ajoute simplement un contrôle du presse-papier par le programme, et on peut utiliser ces techniques pour capturer du texte du presse-papier depuis autre chose qu'un JTextComponent.

//: c13:CutAndPaste.java
// Utilisation du presse-papier.
importjavax.swing.*;
importjava.awt.*;
importjava.awt.event.*;
importjava.awt.datatransfer.*;
importcom.bruceeckel.swing.*;

publicclassCutAndPaste extendsJFrame  {
JMenuBar mb = newJMenuBar();
JMenu edit = newJMenu("Edit");
JMenuItem
   cut = newJMenuItem("Cut"),
   copy = newJMenuItem("Copy"),
   paste = newJMenuItem("Paste");
JTextArea text = newJTextArea(20, 20);
Clipboard clipbd =
   getToolkit().getSystemClipboard();
publicCutAndPaste()  {
   cut.addActionListener(newCutL());
   copy.addActionListener(newCopyL());
   paste.addActionListener(newPasteL());
   edit.add(cut);
   edit.add(copy);
   edit.add(paste);
   mb.add(edit);
   setJMenuBar(mb);
   getContentPane().add(text);
}
classCopyL implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     String selection = text.getSelectedText();
     if(selection == null)
       return;
     StringSelection clipString =       newStringSelection(selection);
     clipbd.setContents(clipString,clipString);
   }
}
classCutL implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     String selection = text.getSelectedText();
     if(selection == null)
       return;
     StringSelection clipString =       newStringSelection(selection);
     clipbd.setContents(clipString, clipString);
     text.replaceRange("",
       text.getSelectionStart(),
       text.getSelectionEnd());
   }
}
classPasteL implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     Transferable clipData =       clipbd.getContents(CutAndPaste.this);
     try{
       String clipString =         (String)clipData.
           getTransferData(
             DataFlavor.stringFlavor);
       text.replaceRange(clipString,
         text.getSelectionStart(),
         text.getSelectionEnd());
     } catch(Exception ex) {
       System.err.println("Not String flavor");
     }
   }
}
publicstaticvoidmain(String[] args) {
   Console.run(newCutAndPaste(), 300, 200);
}
} ///:~

La création et l'ajout du menu et du JTextArea devraient être maintenant une activité naturelle. Ce qui est différent est la création du champ Clipboard clipbd, qui est faite à l'aide du Toolkit.

Toutes les actions sont effectuées dans les listeners. Les listeners CopyL et CutL sont identiques à l'exception de la dernière ligne de CutL, qui efface la ligne qui a été copiée. Les deux lignes particulières sont la création d'un objet StringSelection à partir du String, et l'appel à setContents() avec ce StringSelection. C'est tout ce qu'il y a à faire pour mettre une String dans le presse-papier.

Dans PasteL, les données sont extraites du presse-papier à l'aide de name="Index1777">getContents(). Ce qu'il en sort est un objet Transferableassez anonyme, et on ne sait pas exactement ce qu'il contient. Un moyen de le savoir est d'appeler getTransferDataFlavors(), qui renvoie un tableau d'objets name="Index1780">DataFlavorindiquant quels flavors sont acceptés par cet objet. On peut aussi le demander directement à l'aide de isDataFlavorSupported(), en passant en paramètre le flavor qui nous intéresse. Dans ce programme, toutefois, on utilise une approche téméraire : on appelle getTransferData()en supposant que le contenu accepte le flavor String, et si ce n'est pas le cas le problème est pris en charge par le traitement d'exception.

Dans le futur, on peut s'attendre à ce qu'il y ait plus de flavors acceptés.

Empaquetage d'une applet dans un fichier JAR

Une utilisation importante de l'utilitaire JAR est l'optimisation du chargement d'une applet. En Java 1.0, les gens avaient tendance à entasser tout leur code dans une seule classe, de sorte que le client ne devait faire qu'une seule requête au serveur pour télécharger le code de l'applet. Ceci avait pour résultat des programmes désordonnés, difficiles à lire (et à maintenir), et d'autre part le fichier .class n'était pas compressé, de sorte que le téléchargement n'était pas aussi rapide que possible.

Les fichiers JAR résolvent le problème en compressant tous les fichiers .class en un seul fichier qui est téléchargé par le navigateur. On peut maintenant avoir une conception correcte sans se préoccuper du nombre de fichiers .class qui seront nécessaires, et l'utilisateur aura un temps de téléchargement beaucoup plus court.

Prenons par exemple TicTacToe.java. Il apparaît comme une seule classe, mais en fait il contient cinq classes internes, ce qui fait six au total. Une fois le programme compilé on l'emballe dans un fichier JAR avec l'instruction :

jar cf TicTacToe.jar *.class

Ceci suppose que dans le répertoire courant il n'y a que les fichiers .class issus de TicTacToe.java (sinon on emporte du bagage supplémentaire).

On peut maintenant créer une page HTML avec le nouveau tag name="Index1785">archive pour indiquer le nom du fichier JAR. Voici pour exemple le tag utilisant l'ancienne forme du tag HTML :

<head><title>TicTacToe Example Applet
</title></head>
<body>
<applet code=TicTacToe.class
       archive=TicTacToe.jar
       width=200 height=100>
</applet>
</body>

Il faudra le mettre dans la nouvelleforme (confuse, compliquée) montrée plus haut dans ce chapitre pour le faire fonctionner.

Techniques de programmation

La programmation de GUI en Java étant une technologie évolutive, avec des modifications très importantes entre Java 1.0/1.1 et la bibliothèque Swing, certains styles de programmation anciens ont pu s'insinuer dans des exemples qu'on peut trouver pour Swing. D'autre part, Swing permet une meilleure programmation que ce que permettaient les anciens modèles. Dans cette partie, certains de ces problèmes vont être montrés en présentant et en examinant certains styles de programmation.

Lier des événements dynamiquement

Un des avantages du modèle d'événements Swing est sa flexibilité. On peut ajouter ou retirer un comportement sur événement à l'aide d'un simple appel de méthode. L'exemple suivant le montre :

//: c13:DynamicEvents.java
// On peut modifier dynamiquement le comportement sur événement.
// Montre également plusieurs actions pour un événement.
// <applet code=DynamicEvents
//  width=250 height=400></applet>
importjavax.swing.*;
importjava.awt.*;
importjava.awt.event.*;
importjava.util.*;
importcom.bruceeckel.swing.*;

publicclassDynamicEvents extendsJApplet {
ArrayList v = newArrayList();
inti = 0;
JButton
   b1 = newJButton("Button1",
   b2 = newJButton("Button2");
JTextArea txt = newJTextArea();
classB implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     txt.append("A button was pressed\n");
   }
}
classCountListener implementsActionListener {
   intindex;
   publicCountListener(inti) { index = i; }
   publicvoidactionPerformed(ActionEvent e) {
     txt.append("Counted Listener "+index+"\n");
   }
}
classB1 implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     txt.append("Button 1 pressed\n");
     ActionListener a = newCountListener(i++);
     v.add(a);
     b2.addActionListener(a);
   }
}
classB2 implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     txt.append("Button2 pressed\n");
     intend = v.size() - 1;
     if(end >= 0) {
       b2.removeActionListener(
         (ActionListener)v.get(end));
       v.remove(end);
     }
   }
}
publicvoidinit() {
   Container cp = getContentPane();
   b1.addActionListener(newB());
   b1.addActionListener(newB1());
   b2.addActionListener(newB());
   b2.addActionListener(newB2());
   JPanel p = newJPanel();
   p.add(b1);
   p.add(b2);
   cp.add(BorderLayout.NORTH, p);
   cp.add(newJScrollPane(txt));
}
publicstaticvoidmain(String[] args) {
   Console.run(newDynamicEvents(), 250, 400);
}
} ///:~

Les nouvelles astuces dans cet exemple sont :

  1. Il y a plus d'un listener attaché à chaque Button. En règle générale, les composants gèrent les événements en tant que multicast, ce qui signifie qu'on peut enregistrer plusieurs listeners pour un seul événement. Pour les composants spéciaux dans lesquels un événement est géré en tant que unicast, on obtiendra une exception TooManyListenersException.
  2. Lors de l'exécution du programme, les listeners sont ajoutés et enlevés du Button b2 dynamiquement. L'ajout est réalisé de la façon vue précédemment, mais chaque composant a aussi une méthode removeXXXListener() pour enlever chaque type de listener.

Ce genre de flexibilité permet une grande puissance de programmation.

Il faut remarquer qu'il n'est pas garanti que les listeners d'événements soient appelés dans l'ordre dans lequel ils sont ajoutés (bien que la plupart des implémentations le fasse de cette façon).

Séparation entre la logique applicative [business logic] et la logique de l'interface utilisateur [UI logic]

En général on conçoit les classes de manière à ce que chacune fasse une seule chose. Ceci est particulièrement important pour le code d'une interface utilisateur, car il arrive souvent qu'on lie ce qu'on fait à la manière dont on l'affiche. Ce genre de couplage empêche la réutilisation du code. Il est de loin préférable de séparer la logique applicative de la partie GUI. De cette manière, non seulement la logique applicative est plus facile à réutiliser, mais il est également plus facile de récupérer la GUI.

Un autre problème concerne les systèmes répartis [multitiered systems], dans lesquels les objets applicatifs se trouvent sur une machine séparée. Cette centralisation des règles applicatives permet des modifications ayant un effet immédiat pour toutes les nouvelles transactions, ce qui est une façon intéressante d'installer un système. Cependant, ces objets applicatifs peuvent être utilisés dans de nombreuses applications, et de ce fait ils ne devraient pas être liés à un mode d'affichage particulier. Ils devraient se contenter d'effectuer les opérations applicatives, et rien de plus.

L'exemple suivant montre comme il est facile de séparer la logique applicative du code GUI :

//: c13:Separation.java
// Séparation entre la logique GUI et les objets applicatifs.
// <applet code=Separation
// width=250 height=150> </applet>
importjavax.swing.*;
importjava.awt.*;
importjavax.swing.event.*;
importjava.awt.event.*;
importjava.applet.*;
importcom.bruceeckel.swing.*;

classBusinessLogic {
privateintmodifier;
publicBusinessLogic(intmod) {
   modifier = mod;
}
publicvoidsetModifier(intmod) {
   modifier = mod;
}
publicintgetModifier() {
   returnmodifier;
}
// Quelques opérations applicatives :
publicintcalculation1(intarg) {
   returnarg * modifier;
}
publicintcalculation2(intarg) {
   returnarg + modifier;
}
}

publicclassSeparation extendsJApplet {
JTextField
   t = newJTextField(15),
   mod = newJTextField(15);
BusinessLogic bl = newBusinessLogic(2);
JButton
   calc1 = newJButton("Calculation 1",
   calc2 = newJButton("Calculation 2");
staticintgetValue(JTextField tf) {
   try{
     returnInteger.parseInt(tf.getText());
   } catch(NumberFormatException e) {
     return0;
   }
}
classCalc1L implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     t.setText(Integer.toString(
       bl.calculation1(getValue(t))));
   }
}
classCalc2L implementsActionListener {
   publicvoidactionPerformed(ActionEvent e) {
     t.setText(Integer.toString(
       bl.calculation2(getValue(t))));
   }
}
// Si vous voulez que quelque chose se passe chaque fois
// qu'un JTextField est modifié, ajoutez ce listener :
classModL implementsDocumentListener {
   publicvoidchangedUpdate(DocumentEvent e) {}
   publicvoidinsertUpdate(DocumentEvent e) {
     bl.setModifier(getValue(mod));
   }
   publicvoidremoveUpdate(DocumentEvent e) {
     bl.setModifier(getValue(mod));
   }
}
publicvoidinit() {
   Container cp = getContentPane();
   cp.setLayout(newFlowLayout());
   cp.add(t);
   calc1.addActionListener(newCalc1L());
   calc2.addActionListener(newCalc2L());
   JPanel p1 = newJPanel();
   p1.add(calc1);
   p1.add(calc2);
   cp.add(p1);
   mod.getDocument().
     addDocumentListener(newModL());
   JPanel p2 = newJPanel();
   p2.add(newJLabel("Modifier:"));
   p2.add(mod);
   cp.add(p2);
}
publicstaticvoidmain(String[] args) {
   Console.run(newSeparation(), 250, 100);
}
} ///:~

On peut voir que BusinessLogic est une classe toute simple, qui effectue ses opérations sans même avoir idée qu'elle puisse être utilisée dans un environnement GUI. Elle se contente d'effectuer son travail.

Separationgarde la trace des détails de l'interface utilisateur, et elle communique avec BusinessLogic uniquement à travers son interface public. Toutes les opérations sont concentrées sur l'échange d'informations bidirectionnel entre l'interface utilisateur et l'objet BusinessLogic. De même, Separation fait uniquement son travail. Comme Separation sait uniquement qu'il parle à un objet BusinessLogic (c'est à dire qu'il n'est pas fortement couplé), il pourrait facilement être transformé pour parler à d'autres types d'objets.

Penser à séparer l'interface utilisateur de la logique applicative facilite également l'adaptation de code existant pour fonctionner avec Java.

Une forme canonique

Les classes internes, le modèle d'événements de Swing, et le fait que l'ancien modèle d'événements soit toujours disponible avec de nouvelles fonctionnalités des bibliothèques qui reposent sur l'ancien style de programmation, ont ajouté un nouvel élément de confusion dans le processus de conception du code. Il y a maintenant encore plus de façons d'écrire du mauvais code.

A l'exception de circonstances qui tendent à disparaître, on peut toujours utiliser l'approche la plus simple et la plus claire : les classes listener (normalement des classes internes) pour tous les besoins de traitement d'événements. C'est la forme utilisée dans la plupart des exemples de ce chapitre.

En suivant ce modèle on devrait pouvoir réduire les lignes de programmes qui disent : «Je me demande ce qui a provoqué cet événement». Chaque morceau de code doit se concentrer sur une action, et non pas sur des vérifications de types. C'est la meilleure manière d'écrire le code; c'est non seulement plus facile à conceptualiser, mais également beaucoup plus facile à lire et à maintenir.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par P. Boite ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java