Introduction▲
Rares sont les développeurs Java qui ne sont pas familiers avec les API AWT ou Swing. La première, l'Abstract Windowing Toolkit, est apparue dans la version 1.0 du JDK, en 1995. Le but de cette bibliothèque était de définir un ensemble de composants graphiques communs à une variété de systèmes d'exploitation. Ceux-ci appartiennent à une couche abstraite qui les lie à leurs équivalents natifs sur chaque plate-forme. Grâce à la technologie JNI, chaque classe Java représentant un élément graphique, comme le java.awt.Button, peut effectuer des appels système ce qui implique la réécriture de la couche basse de AWT pour chaque OS. Cette approche particulière permet aux développeurs de n'écrire qu'une seule fois leur application, respectant l'un des idéaux de Java : Write Once, Run Anywhere. L'architecture AWT souffre malheureusement de problèmes particulièrement gênants. Outre ses nombreux bugs que Sun Microsystems a parfois tardé à corriger, cette API se révèle parfois bien trop limitée. Le concept même de AWT impose à ses auteurs de ne choisir que les composants communs à tous les environnements fenêtrés du marché. C'est pourquoi cette bibliothèque ne propose pas de classes pour dessiner des arbres ou des tableaux.
Né au sein du projet Java Foundation Classes, le toolkit Swing est apparu officiellement avec Java2. Le côté natif disparaît totalement pour laisser place à des composants entièrement écrits en Java. Cela signifie que l'aspect même des composants doit émuler celui des systèmes cibles. Les développeurs ont pu constater très rapidement les avantages de cette nouvelle solution. Le plus important étant l'incroyable flexibilité de l'ensemble qui permet de personnaliser chaque petit détail de l'interface. Les composants fournis se révèlent en outre particulièrement puissants et complexes. On peut ainsi afficher des arbres dont les feuilles sont des tableaux contenant eux-mêmes des pages HTML. Malheureusement pour Sun, Swing n'a pas su convaincre tout le monde. Cette puissance implique une API relativement complexe et très déroutante pour les débutants ce qui n'a pas facilité son adoption. Encore plus inquiétant, les utilisateurs n'y trouvaient pas leur compte. La réalisation en Java pur empêche à Swing d'atteindre les performances des solutions natives (bien que les dernières versions de Java et la montée en puissance des machines tendent à minimiser cet effet de manière drastique). Et bien que certains programmeurs talentueux (comme Karsten Lentzsch de jgoodies.com) aient réussi à nous prouver la viabilité et la fiabilité de Swing, certains ont décidé de corriger tous ces problèmes une bonne fois pour toutes.
I. SWT, l'offensive d'IBM▲
IBM, outré par ces déficiences chroniques, a donc simplement décidé de créer son propre toolkit, le Standard Widget Toolkit. Celui-ci a gagné en popularité grâce à l'environnement de développement Eclipse qui est bâti autour. Notons qu'IBM a eu la présence d'esprit et la grandeur d'âme de distribuer SWT sous licence open source. Echaudés par le manque de fonctionnalités d'AWT et par les piètres performances de Swing, les auteurs d'Eclipse ont décidé de suivre une approche intermédiaire. À l'instar de Swing, SWT propose une grande variété de composants puissants et personnalisables. Toutefois, suivant le modèle de son prédécesseur AWT, SWT fait appel au système hôte pour gérer et afficher les composants. La question qui nous brûle les lèvres est la suivante : comment font-ils pour les composants n'appartenant pas à un toolkit natif donné ? Lorsque cela est nécessaire, SWT les émule… comme Swing le ferait. L'exemple le plus probant est celui de l'arbre qui existe nativement sous Windows, mais pas sous Motif. Si vous décidez d'exploiter SWT pour votre application, vous bénéficierez alors du meilleur des deux mondes et l'utilisateur sera bien en peine de faire la distinction entre votre travail et un logiciel natif. En outre, aucune perte de performances ne sera à déplorer. Sachez enfin que les applications SWT sont très facilement compilables avec GCJ, vous donnant l'occasion de distribuer votre travail sous forme de binaires natifs en ne souffrant d'aucune dépendance particulière.
Pour développer une application SWT vous devrez bien entendu récupérer la version de la bibliothèque correspondant à votre système d'exploitation. Le plus simple consiste à installer Eclipse et à en explorer le sous-répertoire plugins/. Vous y trouverez un répertoire intitulé org.eclipse.swt suivi du nom de votre plate-forme et du numéro de version de la bibliothèque. Vous devrez récupérer dans ce dernier les fichiers swt.jar et la ou les extensions natives situées dans le sous-répertoire os/. Bien que vous puissiez également télécharger SWT à part sur le site eclipse.org, nous vous conseillons de procéder ainsi pour utiliser Eclipse en tant qu'environnement de développement. Par exemple sur une station Linux nous choisirons la version GTK2 en cherchant le répertoire org.eclipse.swt.gtk_2.1.2 pour y trouver libswt-gnome-gtk-2135.so, libswt-gtk-2135.so et libswt-pi-gtk-2135.so. Ces dernières devront se trouver dans le dossier d'exécution de votre logiciel ou être désignées à l'aide de l'option -Djava.library.path= de la machine virtuelle.
L'agencement des classes ne devrait pas vous surprendre si vous êtes déjà habitué à développer en Swing. Les deux paquetages primordiaux pour commencer une application sont org.eclipse.swt et org.eclipse.swt.widgets. Le premier ne contient qu'une classe, intitulée SWT, donnant accès à de nombreuses constantes utilisées un peu partout dans la bibliothèque (alignement, styles, etc.) et à quelques méthodes utiles (pour afficher des messages d'erreur par exemple). Le second paquetage contient naturellement les composants graphiques dont nous aurons besoin pour réaliser notre interface graphique. Dans un programme SWT, les fenêtres portent le nom de « shell » et doivent être attachées à une zone d'affichage, désignée sous le terme de Display. Cette dernière doit gérer les communications entre notre application Java et le système d'exploitation. Sa fonction primordiale est donc l'implantation de la boucle principale d'événements. En parcourant sa documentation, vous découvrirez qu'elle offre également un ensemble de méthodes destinées à la découverte des caractéristiques de la plate-forme d'exécution. Vous pourrez par exemple récupérer la police de caractères par défaut ou encore le nombre de couleurs utilisées pour les icônes. Un Display est donc vital pour votre logiciel. Le listing 1 présente un exemple d'application SWT minimale affichant une simple fenêtre à l'écran.
Nous attachons ici notre unique fenêtre au Display créant ainsi la fenêtre principale de l'application. Vous pouvez bien évidemment attacher un Shell à un autre Shell pour reproduire des hiérarchies de fenêtres. Contrairement à Swing, le développeur doit rédiger lui-même la boucle principale du programme dont le principe reste simple. La méthode readAndDispatch() demande au Display de vérifier la présence d'un événement dans la file d'attente du système afin de le faire parvenir au composant adéquat. Lorsqu'un appel à cette méthode renvoie faux, cela signifie que le programme n'a rien à faire et que nous pouvons donc le mettre en attente et laisser la main aux autres logiciels du système. Nous avons appris précédemment que la classe SWT regroupe un ensemble de constantes utilisées par les composants graphiques de la bibliothèque :
Shell shell =
new
Shell
(
display, SWT.CLOSE |
SWT.MIN);
La fenêtre créée de cette manière ne possédera pas de bouton pour la maximiser et ne pourra être agrandie par l'utilisateur. Nous allons maintenant ajouter quelques composants afin de la rendre plus interactive. Le deuxième listing ajoute une étiquette, un champ de saisie et un bouton à notre interface. SWT suit ici le modèle de QT pour l'ajout de composants dans un conteneur, appelé Composite, en demandant une référence vers ce dernier lors de la création de l'instance. Chaque instance reçoit également ses paramètres de style à l'initialisation. Dans notre exemple, le champ texte possède le style SWT.BORDER qui permet de dessiner une bordure autour du composant. En ajoutant les styles SWT.H_SCROLL et SWT.V_SCROLL il se transformerait en une zone de texte équivalente à la JTextArea de Swing. Il s'agit ici de l'aspect le plus gênant pour les transfuges Swing qui ne devront pas perdre de temps à essayer de retrouver la même hiérarchie de composants. Considérez le cas du bouton « Valider » du listing 2. Son style PUSH en fait un bouton classique, mais les styles RADIO, CHECK, ARROW et TOGGLE vous permettent de le remplacer par un bouton radio, une case à cocher, un « spin button » ou un bouton deux états. Vous devrez donc prêter une attention toute particulière au contenu de la classe SWT qui accueille toutes les constantes nécessaires pour manipuler la présentation de vos composants.
II. Les layouts et les événements▲
La bibliothèque d'IBM propose quatre layouts pour vous permettre de placer vos composants de manière optimale, en évitant de fastidieux appels aux méthodes setSize(), setBounds() et setLocation(). Le premier, FilleLayout, permet d'aligner des composants sur une ligne ou une colonne en remplissant tout l'espace. Le deuxième, intitulé RowLayout, vous permet également de gérer des lignes et des colonnes de composants, mais avec un contrôle sur les paramètres d'espacement, de remplissage et de retour à la ligne. Le GridLayout sert quant à lui à disposer les objets dans une grille. Enfin, le FormLayout positionne les éléments grâce à des ancres relatives aux bords du conteneur.
Notre deuxième exemple utilise le RowLayout pour aligner horizontalement les composants. Un layout peut être assigné à un Composite par l'entremise de la méthode setLayout(). L'attribut wrap demande au layout de renvoyer les composants à la ligne si le Composite n'est pas assez large pour tous les accueillir sur la même rangée. Nous pouvons également utiliser les attributs justify, qui répartit les éléments sur tout l'espace disponible quand il vaut true, pack, qui conserve les tailles initiales des composants quand il est vrai, et type pour changer la répartition verticale ou horizontale. Vous pouvez en outre contrôler de manière fine les marges entre les bords du Composite et les composants grâce aux attributs marginLeft, marginRight, marginTop et marginBottom. Enfin spacing spécifie le nombre de pixels séparant chaque élément du suivant. RowLayout se veut un outil de mise en page relativement simple que vous devrez rapidement abandonner au profit de GridLayout et FormLayout. L'emploi de ces derniers dépasse le cadre de notre article, mais vous permet de réaliser des interfaces très précises et redimensionnables à souhait.
Après toutes ces nouveautés, vous serez ravi d'apprendre que la gestion des événements est exactement la même qu'en AWT (et donc Swing). Pour récupérer par exemple un clic sur le bouton de validation afin d'afficher un message, il vous suffira d'ajouter un écouteur ainsi qu'en témoigne le listing 3. Le paquetage org.eclipse.swt.events contient toutes les interfaces et classes nécessaires pour intercepter les événements et les manipuler. Les Adapter vous éviteront notamment d'implémenter toutes les méthodes des interfaces.
Créer des interfaces complexes en SWT vous demandera parfois un grand nombre de lignes de code. Pour simplifier les choses, IBM propose une autre API nommée JFace que vous trouverez également dans Eclipse. Nous avons vu dans le premier listing comme créer une fenêtre applicative en mettant en place le Display, un Shell puis une boucle d'événements. Par l'intermédiaire de JFace, vous pouvez simplement créer une instance de la classe Window. Cette bibliothèque vous permet de condenser votre code tout en vous donnant un accès complet aux possibilités offertes par SWT. JFace est donc une API écrite en pur Java qui n'occasionnera aucune baisse de performances. Vous pouvez vous reporter au listing 4 pour découvrir comment réaliser la même fenêtre que précédemment.
III. Swing ? SWT ? Les deux peut-être ?▲
De nombreuses personnes reprochent à SWT la mauvaise qualité de son API comparée à Swing. Nous pouvons aisément dénoncer les attributs publics sans accesseurs ni mutateurs ou encore les surprises. Par exemple setText() sur un Label permet d'en changer le contenu tandis que setText() sur une MessageBox permet de changer le titre. Malgré tout, les performances et l'intégration graphique parfaite de SWT au sein des systèmes en font un outil de choix. Si vous êtes partagé entre les deux, sachez que vous pouvez tout simplement les choisir tous les deux. Robin Rawson-Tetley a eu l'ingéniosité de proposer une API nommée SwingWT. Celle-ci permet de programmer SWT avec l'API Swing. Cela signifie que vous pouvez également exécuter vos applications Swing sous SWT. Pour cela, ajoutez simplement swingwt.jar à votre classpath et changez les noms de paquetage javax.swing en swingwtx.swing et java.awt en swingwt.awt. Une simple recompilation vous permettra alors de bénéficier du meilleur des deux mondes. Cette bibliothèque n'est pas encore totalement finalisée, mais couvre environ 90 % d'AWT/Swing. À l'inverse, SWTSwing désigne une bibliothèque capable de faire tourner une application SWT à l'aide des composants Swing. Contrairement à SwingWT, aucune recompilation ni modification du code n'est nécessaire. Vous pourrez ainsi permettre à vos utilisateurs d'utiliser votre logiciel même s'ils ne disposent pas de SWT.
import
org.eclipse.swt.*;
import
org.eclipse.swt.widgets.*;
public
class
SWTHello
{
public
static
void
main
(
String[] args)
{
Display display =
new
Display
(
);
Shell shell =
new
Shell
(
display);
shell.open
(
);
while
(!
shell.isDisposed
(
))
{
if
(!
display.readAndDispatch
(
))
display.sleep
(
);
}
display.dispose
(
);
}
}
Shell shell =
new
Shell
(
display, SWT.RESIZE |
SWT.CLOSE |
SWT.MIN);
shell.setText
(
"Login:"
);
RowLayout layout =
new
RowLayout
(
);
layout.wrap =
true
;
shell.setLayout
(
layout);
Label l1 =
new
Label
(
shell, SWT.LEFT);
l1.setText
(
"Prénom : "
);
Text txt1 =
new
Text
(
shell, SWT.BORDER);
Button b1 =
new
Button
(
shell, SWT.PUSH);
b1.setText
(
"Valider"
);
shell.pack
(
);
shell.open
(
);
b1.addSelectionListener
(
new
SelectionAdapter
(
)
{
public
void
widgetSelected
(
SelectionEvent event)
{
MessageBox box =
new
MessageBox
(
shell, SWT.ICON_INFORMATION |
SWT.OK);
box.setText
(
"Validation"
);
box.setMessage
(
"Vous vous appelez "
+
txt1.getText
(
));
box.open
(
);
}
}
);
public
class
JFaceHello extends
Window
{
public
JFaceHello
(
) {
super
(
null
); }
public
Control createContents
(
Composite parent)
{
final
Shell shell =
parent.getShell
(
);
shell.setText
(
"Login:"
);
RowLayout layout =
new
RowLayout
(
);
layout.wrap =
true
;
layout.pack =
true
;
layout.justify =
true
;
parent.setLayout
(
layout);
Label l1 =
new
Label
(
parent, SWT.LEFT);
l1.setText
(
"Prénom : "
);
final
Text txt1 =
new
Text
(
parent, SWT.BORDER);
Button b1 =
new
Button
(
parent, SWT.PUSH);
b1.setText
(
"Valider"
);
return
parent;
}
public
static
void
main
(
String[] args)
{
JFaceHello hello =
new
JFaceHello
(
);
hello.setBlockOnOpen
(
true
);
hello.open
(
);
Display.getCurrent
(
).dispose
(
);
}
}