| Les gestionnaires de placement en .NET par Thomas GIL (thomas.gil@valtech.fr) | ||
Dans le monde des clients riches (ou “lourds”), les frameworks Java Swing et Windows Forms ont adopté des stratégies assez éloignées pour répondre au problème du placement des composants graphiques.
Cet article propose un tour d'horizon des mécanismes intégrés à J2SE et à .NET permettant de spécifier les contraintes d'agencement des composants à l'écran. Cela nous permettra de prendre un peu de recul, de nous faire une idée des avantages comparés de chaque approche, et éventuellement d'imaginer un moyen de pallier les limitations rencontrées.
A noter que nous comparons ici les frameworks de composants standards de chaque plate-forme, mais que pour être exhaustifs, il nous aurait fallu tenir compte d'autres boîtes à outil intégrées telles que SWT. Qui sait ? Peut-être cela sera-t-il l'occasion d'écrire un prochain article...
En Java Swing, on ne positionne jamais les composants graphiques de manière absolue (en x et y) à l'écran : tout se fait de manière relative. Mieux, chaque panneau d'affichage nous offre le choix parmi plusieurs algorithmes de placement des composants. Si cette approche a l'inconvénient de dérouter quelque peu les novices, elle offre néanmoins une souplesse appréciable.
Exemple : développons un "Pense-bête" en JavaSwing
Décomposons le petit exemple précédent. La partie graphique est constituée de :
Le code de cette mini-application est le suivant :
package
dng;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class PenseBete extends
JFrame {
private
JTextField nouvelleTache;
private
DefaultListModel modeleToutesTaches;
private
JButton ajoutTache;
public
PenseBete() {
super("Pense Bête DNG");
nouvelleTache
= new JTextField();
modeleToutesTaches = new DefaultListModel();
ajoutTache = new
JButton("Ajout tâche");
ajoutTache.addActionListener(new
ActionListener() {
public
void actionPerformed(ActionEvent evt)
{
modeleToutesTaches.addElement(nouvelleTache.getText());
}
});
JPanel pNord = new JPanel();
pNord.setLayout(new GridLayout(0, 1));
pNord.add(nouvelleTache);
pNord.add(ajoutTache);
getContentPane().add(pNord,
BorderLayout.NORTH);
getContentPane().add(
new
JList(modeleToutesTaches),
BorderLayout.CENTER);
pack();
setSize(200, 300);
}
public
static void
main(String[] args)
{
new PenseBete().setVisible(true);
}
}
Lorsqu'on retaille l'application graphique, le résultat est cohérent :

Il faut bien comprendre que chaque conteneur, en Java Swing comme AWT, délègue à un LayoutManager le calcul de la "meilleure" position pour chacun des composants ou chacun des sous-panneaux d'affichage. Il faut donc préciser sur chaque conteneur (chaque panneau) le type de LayoutManager souhaité. Et le choix est vaste :
Le choix d'un LayoutManager, et donc d'un algorithme de placement des composants, peut même être remis en question : un conteneur supporte sans aucun problème le changement de LayoutManager à chaud lors de l'exécution ! Eh oui, la relation entre le conteneur et le LayoutManager n'est qu'une aggrégation, elle n'est donc pas immuable.
Notez que rien ne nous empêche de développer une nouvelle classe Java qui implémente l'interface LayoutManager ! Cela permettrait de réaliser un placement adapté à nos besoins (par exemple : centré, tabulaire et ajusté à la taille des composants inclus, ou encore un FlowLayout adapté au sens de lecture Arabe ou Chinois, etc...). Ce n'est pas très complexe, il n'y a que 5 méthodes à implémenter.
Chaque conteneur JavaSwing est un composite : il peut contenir soit des composants atomiques, soit d'autres conteneurs... tout simplement parce qu'un conteneur est un composant, il en hérite et peut donc être utilisé dans tout contexte d'emploi d'un composant quelconque.
Le placement des composants dans un conteneur JavaSwing est une statégie : le conteneur délègue à un objet tiers la responsabilité de placer les composants en fonction de leurs tailles préférées, ainsi que des contraintes du conteneur lui-même. Cet objet délégué peut être remplacé au cours de l'exécution : on change dès lors de stratégie.
Ce qui donne, résumé en un diagramme de classes UML :

Fidèle au modèle de développement de VisualBasic 6.0, les WindowsForms proposent (par défaut) un placement absolu des composants en coordonnées cartésiennes. Cette approche a l'avantage d'être triviale à appréhender, même pour le novice, qui peut se reposer complètement sur l'outil de conception graphique pour bâtir son interface graphique.
En effet, en quelques secondes d'une manipulation naïve de l'interface de conception visuelle de VisualStudio.NET (et une simple ligne de code pour gérer l'événement clic sur le bouton...), le résultat est tout à fait satisfaisant :

Le sentiment du développeur est d'avoir eu, lors de la conception, une grande maîtrise du placement des composants "au pixel près" !
Mais les choses se gâtent dès lors que l'on souhaite redimensionner notre fenêtre. Jugez plutôt :

L'inconvénient de cette technique est qu'elle ne survit pas aux redimensionnements de l'interface : les coordonnées des composants sont spécifiées en abscisses et ordonnées, et ne suivent donc pas les agrandissement et rétrécissements de leur conteneur. Il faut donc limiter cette pratique au développement de boîtes de dialogues ou aux fenêtres non redimensionnables.
Heureusement, il existe un autre moyen bien plus élégant et tout aussi simple de placer les composants WindowsForms : il s'agit de la propriété Anchor, qui permet de préciser de quelle manière chaque contrôle doit réagir au redimensionnement de son conteneur.
Pour illustrer son fonctionnement, revenons à la conception graphique de notre application dans VisualStudio.NET. Sélectionnons la liste des tâches de notre application "Pense-bête", et modifions son Anchor à travers la fenêtre de propriétés :

Du coup, notre liste de tâches accompagnera tout redimesionnement de son conteneur, et ce dans toutes les directions :

Il existe une troisième technique permettant d'organiser les interfaces graphiques .NET. Il s'agit du ... BorderLayout. Enfin, ce n'est ni le nom consacré, ni tout à fait la même conception Orientée Objet, mais on retrouve bien le comportement de notre BorderLayout JavaSwing dans le framework .NET !
En effet, il suffit de jouer avec la propriété Dock d'un composant Windows Forms pour lui indiquer de s'afficher au nord, sud, est, ouest ou au centre. Et comme d'habitude, l'outillage nous facilite la tâche en permettant de choisir graphiquement la position de chaque composant.
Vous l'aurez compris, les propriétés Dock et Anchor sont mutuellement exclusives.
En se basant sur cette nouvelle approche, on obtient rapidement un comportement tout à fait cohérent vis-à-vis du redimensionnement, tout en restant très simple à l'usage. Bref, un excellent rapport qualité / complexité.

Les stratégies de placement des contrôles graphiques WindowsForms sont donc peu nombreuses, et reposent exclusivement sur les fonctionnalités intégrées au framework. Serait-il possible de les adapter à nos besoins, d'inventer de nouvelles stratégies ? Pourrait-on ré-implémenter la notion de Layout Manager en .NET ?
A coeur vaillant, rien d'impossible, direz-vous ! Effectivement, un petit détour par la conception objet et les Design Patterns vont nous permettre de répondre aisément à ce besoin. Tout d'abord, explicitons un peu notre besoin :
Tout cela, sachant que la hiérarchie de classes WindowsForms est la suivante (pour les Panel) :

Ces quelques contraintes nous amènent rapidement à la conception suivante :

Cette solution s'inspire de la conception de JavaSwing. Elle nous permettra d'associer à un LayedOutPanel n'importe quel type de LayoutManager, pour peu qu'il implémente l'interface éponyme. A nouveau, on retrouve les Patterns habituels : le LayedOutPanel est un Décorateur de Panel, et utilise la Stratégie LayoutManager. On pourra l'utiliser en lieu et place d'un Panel standard (décorateur) et il délèguera au LayoutManager l'implémentation d'un algorithme de placement (stratégie).
En faisant quelques recherches sur le Web, on s'aperçoit vite que certains, par nostalgie ou poussés par de réels besoins, réimplémentent un système très proche de la notion de LayoutManager dans la plate-forme .NET. En particulier, cela permet de retrouver les comportements GridLayout, FlowLayout et BoxLayout, qui n'ont pas d'équivalent dans le framework .NET standard.
Le modèle de placement JavaSwing est complexe à manipuler. Celui de .NET est nettement plus simple, d'autant plus que l'outillage est là pour nous guider dans son utilisation. Comme souvent, on s'aperçoit que l'approche .NET offre un "Retour sur Investissement" très imporant, mais qu'elle montre ses faiblesses sur des cas complexes. En JavaSwing, la complexité se fait sentir quasiment dès la prise en main du framework, mais les LayoutManagers permettent de laisser libre cours à son imagination pour placer les composants (le meilleur exemple est le GridBagLayout, un manager très puissant mais quasiment inutilisable si l'on n'est pas bien outillé).
Enfin, nous aimerions vous amener à réfléchir au point suivant : nous avons imaginé une implémentation des LayoutManagers en .NET fortement adossée aux Design Patterns. Aurait-il été possible d'imaginer une solution moins intrusive que notre décorateur ? En particulier, aurions-nous pu tirer parti de la conception par Aspects pour enrichir les fonctionnalités du Panel existant ? Une question ouverte qui prépare le terrain pour les articles à venir...
Auteur : Thomas GIL
Copyright © Mars 2003
Ressources
http://www.csharphelp.com/archives/archive7.html
http://www.gbvsoft.com/csharp.html
http://samples.gotdotnet.com/quickstart/winforms/doc/WinFormsFormLayout.aspx