I. Introduction▲
Quand vous développez une application graphique, vous faites en sorte que l'interface soit la plus agréable possible à regarder et à manipuler. Mais bien souvent les développeurs ne pensent pas que les utilisateurs peuvent avoir une résolution d'écran différente de la leur et dès lors, la belle interface du développeur se transforme en monstruosité pour l'utilisateur. Il existe pourtant un moyen simple et puissant de rendre une interface graphique insensible au changement de résolution : le docking et les composants de placement.
Il va sans dire que les composants de placement ne peuvent pas être utilisés sans la notion de « docking ».
Nous allons donc voir au travers d'une application de démonstration comment utiliser ces deux techniques afin de rendre votre interface insensible au changement de résolution et de taille de fenêtre.
Cet article se base sur le framework 2.0 pour les composants de placement (FlowLayoutPanel et TableLayoutPanel), mais reste valable en 1.1 pour la partie « docking ».
II. Le « Docking » des composants▲
II-A. Principe de fonctionnement▲
Le « docking » est la technique permettant de signaler à un composant qu'il doit épouser une forme particulière de son conteneur. Si vous dockez à gauche de la fenêtre un composant, vous remarquerez lors du redimensionnement de celle-ci que votre composant s'agrandit avec. Cela vous permet de définir facilement des zones possédant elles aussi leurs composants et des conteneurs. Mais attention les composants dockés suivent des règles très strictes et parfois pas évidentes à comprendre. En effet vous pouvez grâce aux notions de premier plan et arrière-plan définir quel composant doit être devant ou non.
II-B. Utilisation dans l'application de démonstration▲
Voyons maintenant comment utiliser ces propriétés de dock.
Commençons d'abord par localiser la propriété dans l'éditeur :
La majorité des composants possèdent cette propriété. Elle vous permet de définir où le composant doit être docké dans son conteneur.
La place des boutons définit la zone de dock.
Voyons maintenant les effets du docking sur l'application une fois lancée.
Comme vous le voyez, l'application réagit très bien à la maximisation.
Vous remarquerez que le composant a été agrandi comme la fenêtre : il est « collé » au bord gauche de celle-ci. En revanche sa largeur n'a pas été modifiée, car rien ne dit qu'elle doit l'être.
Nous allons maintenant enrichir notre application avec tout ce qu'il faut pour en faire un petit client mail.
Construisons pas à pas cette première fenêtre ensemble :
Prenons une form vide et ajoutons-y un TreeView. Ce TreeView est docké à gauche.
Je vous laisse le soin de nommer à votre guise les composants que vous ajoutez à votre formulaire. Le nommage n'est là qu'à titre d'exemple.
Ajoutons ensuite un Splitter vertical afin de pouvoir redimensionner nos composants facilement pendant l'exécution du programme. Ce Splitter est docké par défaut à gauche. Nous ne modifions pas cette propriété vu qu'elle nous convient :
Ajoutons maintenant de quoi voir les mails de notre application.
Pour ce faire nous ajoutons un RichTextBox et nous positionnons sa propriété Dock à bottom.
Afin de pouvoir redimensionner le RichTextBox nous allons ajouter un deuxième splitter entre ce RichTextBox et le futur ListView. Nous mettrons sa propriété Dock à bottom afin qu'il agisse sur les composants situés au-dessus et en dessous de lui.
Et pour finir, nous allons ajouter un ListView qui va prendre toute la place restante dans la fenêtre. Pour ce faire nous allons définir sa propriété Dock à Fill.
Les propriétés de docking ne sont pas bien compliquées à comprendre, mais leur ordre de priorité si. Le mieux pour les comprendre reste encore de faire les tests soi-même.
Voici quelques informations qui vous aideront à mieux comprendre cela. Sur une fenêtre :
- l'arrière-plan vertical est à gauche la fenêtre ;
- l'arrière-plan horizontal est en bas de la fenêtre ;
- l'ordre d'insertion des composants définit leur position initiale dans la hiérarchie de docking ;
- une mise à l'avant-plan ou à l'arrière-plan ne repositionne un composant que par rapport à ses deux composants voisins.
Afin d'illustrer le principe, voyons cela sur des captures d'écran. Les actions effectuées sont détaillées en dessous :
À cette étape nous n'avons que ramené la ListView au premier plan. En mettant la ListView au premier plan nous demandons à ce qu'elle soit affichée entièrement, or à cause des propriétés de docking, celle-ci doit être redimensionnée afin que tout soit affiché.
La ListView est donc maintenant à sa place.
Globalement il ne reste pas grand-chose, l'application apparait comme il faut si vous regardez bien, vous remarquerez que les Splitter ne sont pas bien positionnés. Nous allons remédier à cela.
Nous avons maintenant remis le Splitter vertical à sa place en le mettant en arrière-plan.
Mais vous remarquerez qu'un autre problème, en plus du deuxième Splitter, est apparu : le menu n'est plus correct ! Il n'occupe que la place au-dessus de la ListView.
Résolvons ces deux petits problèmes :
Le menu a été mis en arrière-plan et le splitter en avant-plan.
Le placement des composants avec l'avant-plan et l'arrière-plan n'est pas une science exacte. Il arrive parfois des choses un peu étranges par exemple le fait de mettre le menu en arrière-plan pour qu'il occupe tout le haut de la fenêtre.
Si vous refaites l'application de A à Z, je ne peux pas garantir que vous aurez les mêmes résultats avec le jeu des avant/arrière-plans.
Voilà notre fenêtre principale est finie et supporte sans problème le redimensionnement.
Dans la suite nous allons voir comment combiner le docking aux composants de placement afin d'avoir une interface propre.
III. Placement dynamique des composants▲
III-A. Présentation▲
Le but est de réaliser un menu basé sur un fichier XML. Cette approche nous permettra de voir les différentes façons de procéder.
Le menu que nous allons créer est tout simple (dans la théorie). Nous allons ajouter un contrôle de type Button et le « coller » en haut d'un contrôle de type Panel (propriété Dock à Top). À ce bouton nous associerons un TabPage, plusieurs boutons pouvant être associés au même TabPage.
Vous verrez que si l'idée est simple, la réalisation l'est moins :(
Imports
System.Xml
Public
Class
Form1
Private
Sub
ToolStripButton1_Click
(
ByVal
sender As
System.Object
,
ByVal
e As
System.EventArgs
) Handles
ToolStripButton1.Click
Dim
xmlfile As
XmlDocument =
New
XmlDocument
(
)
xmlfile.Load
(
"ListeMenu.xml"
)
Dim
elems As
XmlNodeList =
xmlfile.SelectNodes
(
"/menu"
).Item
(
0
).ChildNodes
Dim
mess As
String
=
""
For
i As
Integer
=
0
To
elems.Count
-
1
AddElemMenu
(
elems
(
i).InnerText
, elems
(
i).Attributes
(
"tabpage"
).Value
)
Next
End
Sub
Private
Sub
BtnMenu_click
(
ByVal
sender As
Object
, ByVal
e As
EventArgs)
Dim
tbpname As
String
=
CType
(
sender, Control).Name.Replace
(
"btn"
, ""
)
Me
.TabControl1.SelectTab
(
tbpname)
End
Sub
Private
Sub
AddElemMenu
(
ByVal
itemText As
String
, ByVal
associatedTP As
String
)
' C'est ici que nous nous occuperons d'ajouter les différents éléments du menu.
End
Sub
End
Class
III-B. Sans la gestion de placement▲
En utilisant AddElemMenu dans la version suivante, vous allez avoir des surprises.
Private
Sub
AddElemMenu
(
ByVal
itemText As
String
, ByVal
associatedTP As
String
)
' Création du bouton pour le menu
Dim
b As
Button =
New
Button
(
)
b.Size
=
New
Size
(
100
, 40
)
b.Location
=
New
Point
(
10
, 10
)
b.Dock
=
DockStyle.Top
b.Text
=
itemText
b.FlatStyle
=
FlatStyle.Popup
b.Name
=
"btn"
&
associatedTP
AddHandler
b.Click
, AddressOf
BtnMenu_click
' Si le tabpage associé n'existe pas, on le crée
If
Me
.TabControl1.Controls
(
associatedTP) Is
Nothing
Then
Dim
tbp As
TabPage =
New
TabPage
tbp.Name
=
associatedTP
tbp.Text
=
associatedTP
Me
.TabControl1.TabPages.Add
(
tbp)
End
If
' On ajoute le bouton au menu
Me
.Panel1.Controls.Add
(
b)
End
Sub
Tous les éléments du menu apparaissent inversés. Pourquoi ? Jetons un œil au code généré par Visual Studio pour l'interface de base de ce programme, principalement la partie concernant Form1.
'
'Form1
'
Me
.AutoScaleDimensions
=
New
System.Drawing.SizeF
(
6.0
!, 13.0
!)
Me
.AutoScaleMode
=
System.Windows.Forms.AutoScaleMode.Font
Me
.ClientSize
=
New
System.Drawing.Size
(
703
, 462
)
Me
.Controls.Add
(
Me
.TabControl1
)
Me
.Controls.Add
(
Me
.Panel1
)
Me
.Controls.Add
(
Me
.ToolStrip1
)
Me
.Name
=
"Form1"
Me
.Text
=
"Exemple de placement des composants"
Me
.ToolStrip1.ResumeLayout
(
False
)
Me
.ToolStrip1.PerformLayout
(
)
Me
.ResumeLayout
(
False
)
Me
.PerformLayout
(
)
Pour réaliser cette interface, constituée d'une ToolStrip, d'un Panel et d'un TabControl, j'ai commencé par poser le ToolStrip, dont la propriété Dock est par défaut Top, ensuite le panel, que j'ai Docké à Left et pour finir le TabControl, Docké en Fill.
Vous constatez que le dernier contrôle que j'ai ajouté est placé en premier dans la collection de contrôles de ma Form et que le premier se situe en dernier. En partant de ce constat, on comprend que le dernier élément ajouté est « prioritaire » au niveau de la propriété Dock, et que c'est donc lui qui sera traité en premier lors de la gestion du placement.
Comment remédier à ce « problème » ?
III-C. Première méthode - En trichant sur l'ordre de chargement des éléments du menu▲
Pour pallier ce problème, la première solution est la ruse, mais elle n'est pas forcément toujours applicable malheureusement.
Comment faire ? Tout simplement en traitant le contenu de notre fichier XML à l'envers, en insérant le dernier élément en premier et ainsi de suite.
Private
Sub
ToolStripButton1_Click
(
ByVal
sender As
System.Object
,
ByVal
e As
System.EventArgs
) Handles
ToolStripButton1.Click
' ...
For
i As
Integer
=
elems.Count
-
1
To
0
Step
-1
AddElemMenu
(
elems
(
i).InnerText
, elems
(
i).Attributes
(
"tabpage"
).Value
)
Next
' ...
End
Sub
Toujours pas convaincant et surtout pas applicable dans tous les cas. Passons à la deuxième méthode.
III-D. Deuxième méthode - En réordonnant nous-mêmes les contrôles▲
Imports
System.Xml
Public
Class
Form1
' ...
Private
Sub
AddElemMenu
(
ByVal
itemText As
String
, ByVal
associatedTP As
String
)
' ...
' On ajoute le bouton au menu avec la gestion du placement
If
Panel1.Controls.Count
=
0
Then
Me
.Panel1.Controls.Add
(
b)
Else
Dim
controls
(
) As
Control =
New
Control
(
Panel1.Controls.Count
) {}
Me
.Panel1.Controls.CopyTo
(
controls, 0
)
Array
.Copy
(
controls, 0
, controls, 1
, controls.Length
-
1
)
controls
(
0
) =
b
Panel1.Controls.AddRange
(
controls)
End
If
End
Sub
' ...
End
Class
Comme ControlCollection ne possède pas de méthode InsertAt malheureusement, nous allons la réécrire nous-mêmes. On copie son contenu dans un tableau, on décale tous ses éléments d'un cran et on insère le nôtre en position 0.
Si par exemple, vous aviez besoin de rajouter un élément en 2e position du menu, il faudrait jouer sur les paramètres sourceIndex, destinationIndex et length de la méthode Copy de la classe Array.
Array
.Copy
(
controls, 1
, controls, 2
, controls.Length
-
2
)
controls
(
1
) =
b
Voilà, j'espère que je vous aurai aidé un peu à comprendre le placement des contrôles à l'exécution. Amusez-vous bien ;)
III-E. Sources▲
Vous pouvez télécharger les sources de ces exemples.
Le zip contient la version du projet pour la deuxième méthode.
IV. Le composant SplitContainer▲
Le composant SplitContainer vous permet de créer deux Panels séparés par un Splitter. La nouveauté étant que c'est le SplitContainer lui-même qui gère directement les deux Panels.
Cela vous permet de définir facilement deux zones dans lesquels vous pourrez ajouter des composants. Vous pourrez également facilement définir l'orientation du Splitter (horizontal ou vertical).
V. Le composant TableLayoutPanel▲
Le composant TableLayoutPanel est un composant permettant de placer des composants à la manière d'un tableau. Cela se révèle pratique dans le cas où vous souhaitez aligner des composants qui n'ont pas nécessairement la même taille.
Les principales propriétés de ce composant sont évidemment le nombre de lignes et le nombre de colonnes. (Rows et Columns.)
Grâce à ce composant, nous allons aligner les TextBox du destinataire et du sujet. De plus le RichTextBox sera également aligné sur les deux TextBox donnant un aspect plus fini à l'application.
Ce composant est très simple d'emploi. Il suffit de le créer et d'y mettre le nombre de lignes et de colonnes que l'on souhaite. Ensuite il suffit d'y faire glisser les composants et ceux-ci adoptent la bonne place.
Le gros avantage est donc d'avoir un bon rendu, mais malheureusement ce composant ne supporte pas très bien le redimensionnement.
VI. Le composant FlowLayoutPanel▲
Le composant FlowLayoutPanel vous permet de définir l'ordre de placement des composants à l'intérieur de celui-ci.
En effet si vous ajoutez des composants vous remarquerez qu'ils vont tous se coller en haut à gauche du conteneur.
Ce contrôle vous permet donc de faire par, exemple, une barre d'outils très facilement et ainsi d'arranger les divers composants afin de garder une uniformité dans l'interface graphique.
De plus vous pouvez par le code modifier facilement l'organisation du contenu en modifiant la propriété FlowDirection. Cette méthode vous permet de maitriser à tout moment la disposition de vos composants.
VII. Remerciements▲
Je tiens à remercier titi pour la partie sur le placement dynamique des composants.
Je remercie également Khany pour la correction orthographique.