Accueil Nos publications Blog Comment passer à WPF lorsqu’on a toujours fait du Windows Forms ? (Partie 1)

Comment passer à WPF lorsqu’on a toujours fait du Windows Forms ? (Partie 1)

Windows Forms est une technologie « ancienne » basée sur win32 et inspirée de Visual Basic. Elle a l’avantage d’être facile à apprendre et rapidement performante. Malheureusement, il arrive souvent lorsque l’on cherche à construire des formulaires complexes d’être « limité ».

Microsoft a introduit Windows Presentation Foundation (WPF) avec la version 3.0 du Framework .net, cette spécification graphique permet à l’aide du langage XAML, un langage basé sur XML, de spécifier vos formulaires à la manière d’ASP.net : c’est à la fois très facile… et déroutant !

WPF abandonne GDI+ et s’appuie désormais sur DirectX qui lui permet d’offrir en plus un support de la 3D et surtout un rendu vectoriel, il est donc possible très facilement d’effectuer des zooms, des rotations, des flips…

Si comme moi vous avez fait beaucoup de Windows Forms, la transition vers WPF peut vous faire peur car tout semble différent… voire étrange ! Nous allons donc découvrir comment, à partir de notre expérience Windows Forms, il est possible (je n’ai pas dit simple) de se mettre à WPF. Commençons donc par quelques banalités et voyons les containers.

Premiers pas

Pour commencer, ouvrez Visual Studio et créez une nouvelle application WPF. A la fin du processus de création, Visual Studio vous affiche un premier formulaire :

Faisons ensemble le tour des informations qui viennent de s’afficher.

Comme en Windows Forms, à droite, s’affiche l’arborescence de la solution avec 2 fichiers visibles : App.xaml, qui correspond à peu de chose près au fichier program.cs de Windows Forms ; et MainWindow.xaml contenant le formulaire ouvert par défaut. Sur la partie centrale de l’éditeur, dans une fenêtre coupée en deux panneaux, se trouve un aperçu du formulaire nouvellement créé et son équivalent en XAML.

Sous chacun de ces fichiers se trouvent des fichiers .xaml.cs dont le rôle ressemble fortement à ceux que l’on trouve en ASP .NET : porter le « code behind », c’est-à-dire la logique du formulaire. Ainsi, le fichier MainWindow.xaml.cs contiendra le code C# associé aux actions du formulaire MainWindow.

Ajoutons maintenant un bouton : 2 solutions pour cela.

  • Comme en Windows Forms, à partir de la boîte à outil (Toolbox) — qui se trouve généralement à gauche du designer, glisser et déposer un bouton à l’endroit souhaité sur votre formulaire (dans la capture précédente, le panneau est replié).
  • Comme en ASP .NET, dans la partie XAML du designer et entre les balises <Grid></Grid> ajoutez le code suivant :
    <Button Content=”Button” Name=”button1″ Height=”24″ Width=”64″ />

Et voilà, notre formulaire contient maintenant un bouton affichant le texte « Button » et se nommant « button1 ». Aucune action étant définie, cliquer dessus n’aura donc aucun effet. Pour associer une opération il suffit de relier l’évènement désiré à un handler, ce qui se fait très facilement : car comme en ASP .NET, il suffit d’affecter au cœur de la balise <Button /> un handler à l’évènement désiré ou bien via le designer de l’associer en cliquant sur l’objet et choisir l’évènement désiré dans le gestionnaire d’évènements du panneau Propriétés.

Par exemple, pour afficher une MessageBox à chaque clic sur le bouton il suffira de procéder comme ceci :

Dans le fichier MainWindow.xaml :

<Button Content="Button" Name="button1" Height="24" Width="64" Click="button1_Click" />

Dans le fichier MainWindow.xaml.cs :

private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Test !");
}

Et voilà ! Simple non ?!?

Panneau

Pour faciliter le positionnement des contrôles dans les formulaires, en Windows Forms, nous avons l’habitude de juste les glisser et déposer sur le formulaire, pourtant il existe de base 5 containers différents : FlowLayoutPanel, GroupBox, Panel, SplitContainer et TableLayoutPanel.

Comparons ces containers avec leur équivalent WPF.

GroupBox

Le GroupBox existe à la fois en Windows Forms et en WPF. Ce contrôle permet d’afficher un contenu — typiquement un sous-formulaire, surmonté d’un titre et entouré d’un fin liseré.

En WPF, quelques lignes suffisent :

<GroupBox Header="Titre" Height="100" HorizontalAlignment="Left"  Name="groupBox1" VerticalAlignment="Top" Width="200">
<Grid>
<TextBlock>Contenu</TextBlock>
</Grid>
</GroupBox>

Panel et TableLayoutPanel

J’ai fait le choix de parler de ces 2 containers de Windows Forms en même temps car ils ont le même équivalent en WPF : <Grid></Grid>. En effet, la Grid de WPF permet de positionner les contrôles d’un formulaire en fonction de coordonnées — ce qui est le cas du Panel, ou bien dans un tableau — comme le TableLayoutPanel.

Panel

Ainsi pour placer un contrôle précisément dans un formulaire, on doit d’abord déterminer comment il doit se comporter lors du redimensionnement : généralement, on ancre les contrôles par rapport au coin en haut à gauche du container. Avec ce contrôle, ça se traduit par préciser l’alignement horizontal et vertical : VerticalAlignment=”Top” et HorizontalAlignment=”Left”. Ce n’est pas sans rappeler la propriété Anchor présente dans Windows Forms. Non ? En bien, en réalité, c’est exactement la même chose, voici un tableau résumant tout cela :

VerticalAlignement

HorizontalAlignement

AnchorStyle

Top

Left

AnchorStyle.Top | AnchorStyle.Left

Top

Right

AnchorStyle.Top | AnchorStyle.Right

Top

Stretch

AnchorStyle.Top | AnchorStyle.Left | AnchorStyle.Right

Top

Center

AnchorStyle.Top

Bottom

Left

AnchorStyle.Bottom | AnchorStyle.Left

Bottom

Right

AnchorStyle. Bottom | AnchorStyle.Right

Bottom

Stretch

AnchorStyle. Bottom | AnchorStyle.Left | AnchorStyle.Right

Bottom

Center

AnchorStyle. Bottom

Stretch

Left

AnchorStyle.Top | AnchorStyle.Bottom | AnchorStyle.Left

Stretch

Right

AnchorStyle.Top | AnchorStyle. Bottom | AnchorStyle.Right

Stretch

Stretch

AnchorStyle.Top | AnchorStyle. Bottom | AnchorStyle.Left | AnchorStyle.Right

Stretch

Center

AnchorStyle.Top | AnchorStyle. Bottom

Center

Left

AnchorStyle.Left

Center

Right

AnchorStyle.Right

Center

Stretch

AnchorStyle.Left | AnchorStyle.Right

Center

Center

AnchorStyle.None

Une fois ce comportement défini, nous devons le positionner. Avec Windows Forms, nous utilisions la propriété Location, en WPF, il faut utiliser la propriété Margin. Cette propriété permet de définir l’écart entre les bords du contrôle et son containeur.

Voici donc un exemple d’utilisation de ces propriétés pour le positionnement d’un libellé à 10 pixels du haut et à gauche d’un Grid :

<Grid>
<TextBlock Margin="10,10,0,0" HorizontalAlignment="Left"
VerticalAlignment="Top">Contenu</TextBlock>
</Grid>

Plusieurs combinaisons de valeurs sont possibles pour la propriété Margin :

  • Margin=”gauche, haut, droite, bas” : les marges seront égales aux valeurs respectives ;
  • Margin=”horizontal, vertical” : les marges gauche et droite seront égales à la valeur horizontal, alors que les marges haute et basse recevront la valeur vertical ;
  • Margin=”valeur” : toutes les marges seront égales à valeur.

TableLayoutPanel

Le TableLayoutPanel permet de mettre en forme votre formulaire à la manière d’un tableau — tel <table /> en HTML. C’est très utile pour aligner parfaitement les formulaires de saisie complexe.

Imaginons, la mise en place d’un formulaire de renseignement d’un utilisateur, nous demandons la saisie des nom et prénom, date de naissance, adresse, url et email.

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<TextBlock Grid.Column="0" Grid.Row="0" Text="Nom :" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2" />

<TextBlock Grid.Column="0" Grid.Row="1" Text="Prénom :" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2" />

<TextBlock Grid.Column="0" Grid.Row="2" Text="Date de naissance :" HorizontalAlignment="Right" VerticalAlignment="Center" />
<DatePicker Grid.Column="1" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="2" />

<TextBlock Grid.Column="0" Grid.Row="3" Text="Adresse :" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2" />
<TextBox Grid.Column="1" Grid.Row="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="2" />
<StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="5" Margin="2">
<TextBox Width="50" VerticalAlignment="Stretch" Margin="0,0,2,0" />
<TextBox Width="150" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</StackPanel>

<TextBlock Grid.Column="0" Grid.Row="6" Text="Site Internet :" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="6" Margin="2" />

<TextBlock Grid.Column="0" Grid.Row="7" Text="Email :" HorizontalAlignment="Right" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="7" Margin="2" />

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Column="1" Grid.Row="9">
<Button Content="Enregistrer" Margin="2"  />
<Button Content="Annuler" Margin="2" />
</StackPanel>
</Grid>

Dans cet exemple, on peut voir que l’on définit au début du <Grid /> un ensemble de colonnes et de lignes grâce aux propriétés <ColumnDefinitions /> et <RowDefinitions />. Ensuite, pour chaque contrôle, on précise la cellule dans laquelle il doit se positionner à l’aide des propriétés Grid.Column et Grid.Rows. Notez la notation Grid.[Propriété] qui permet d’accéder aux propriétés dépendantes de l’objet <Grid />, on les appelle des propriétés attachées (attached properties).

Vous pouvez remarquer qu’à l’intérieur de chaque cellule, il est possible d’ajouter un ou plusieurs contrôles et de les aligner de la même façon que nous l’avons vu précédemment grâce aux propriétés Margin, HorizontalAlignment et VerticalAlignment. Cependant, pour ajouter plusieurs éléments, il est nécessaire de redéfinir un nouveau container : dans le cas présent, j’ai utilisé des StackPanel, contrôles que nous verrons dans quelques instants.

Enfin, pour déterminer la taille des cellules, il faut définir la taille des lignes et des colonnes à l’aide des propriétés Height de RowDefinition et Width de ColumnDefinition. Ces propriétés acceptent les valeurs suivantes :

  • Auto : la colonne ou la ligne adapte sa taille en fonction de l’élément le plus long ou le plus haut ;
  • * : la colonne ou la ligne occupe tout l’espace restant, si plusieurs lignes ou colonnes utilisent cette valeur, elles se partageront l’espace restant de manière égale.
  • Valeur numérique : précise la taille en pixel de la ligne ou de la colonne.

La valeur par défaut étant *, si aucune taille n’est définie, WPF utilisera l’espace disponible.

Panel — Canvas

Un autre container de WPF permet de positionner précisément les contrôles qu’il contient. Il s’agit de <Canvas />. Il est moins fréquent d’utiliser ce contrôle par rapport au <Grid /> mais il a l’avantage de pouvoir positionner facilement les contrôles qu’il contient en utilisant les propriétés attachées Left, Right, Top et Bottom. Cependant seule une coordonnée verticale et horizontale n’est autorisée. De plus lors d’un redimensionnement du <Canvas /> les contrôles resteront “accrochés” au bord correspondant sans être redimensionnés.

<Canvas>
<TextBlock Canvas.Left="0" Canvas.Top="0">Test</TextBlock>
<TextBlock Canvas.Left="0" Canvas.Top="16">Test</TextBlock>
<TextBlock Canvas.Right="0" Canvas.Bottom="0">Test</TextBlock>
</Canvas>

FlowLayoutPanel — StackPanel

En Windows Forms, le FlowLayoutPanel permet d’ajouter des contrôles dans un « flux » : les contrôles s’alignement automatiquement les uns à la suite des autres : soit horizontalement, soit verticalement. Arrivé au bord du formulaire, ils recommencent une nouvelle ligne (ou nouvelle colonne).

En WPF, le contrôle StackPanel reprend exactement ce principe, et comme l’exemple précédent le montre, la syntaxe est très simple :

<StackPanel Orientation="Horizontal">
<TextBox Width="50" VerticalAlignment="Stretch" Margin="0,0,2,0" />
<TextBox Width="150" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</StackPanel>

La propriété Orientation permettant de définir le sens du flux.

SplitPanel

Avec l’apparition du Framework .net 2.0, Windows Forms s’est enrichie d’un contrôle SplitPanel, celui-ci se constitue de deux Panels et d’un Splitter. Ce contrôle est très pratique lorsqu’il est nécessaire de pouvoir redimensionner des espaces au sein d’un formulaire.

En WPF, il n’existe pas d’équivalent à ce contrôle, c’est donc « à l’ancienne » avec un Grid et un GridSplitter que nous allons devoir travailler.

Un exemple vaut mille mots :

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Rectangle Grid.Column="0" Fill="Black" />
<GridSplitter Grid.Column="1" Background="Azure" HorizontalAlignment="Center" VerticalAlignment="Stretch" ShowsPreview="True" Width="3" />
<Rectangle Grid.Column="2" Fill="WhiteSmoke" />
</Grid>

Dans cet exemple, nous utilisons un Grid composé de 3 colonnes : les première et troisième colonnes vont servir à recevoir du contenu tandis que la colonne du milieu contiendra le GridSplitter. La seule erreur à ne pas commettre est d’oublier de définir une largeur (ou hauteur) pour qu’il puisse être « attrapé ».

DockPanel

Le DockPanel n’a pas d’équivalent en Windows Forms… ou plutôt pas sous la forme d’un contrôle dédié.

En effet, en Windows Forms, chaque contrôle possède une propriété Dock de type DockStyle lui permettant de « s’accrocher » sur un bord de son containeur et d’en suivre les mouvements automatiquement, l’idée est géniale… mais, dans la réalité, souvent hasardeuse ! Ici, en WPF, un containeur spécifique permet de se simplifier la vie… réellement ! Ainsi pour obtenir le résultat ci-dessous :

ce simple code suffit :

<DockPanel HorizontalAlignment="Stretch" Margin="0" Name="dockPanel1" VerticalAlignment="Stretch">
<Button Content="Top" DockPanel.Dock="Top" />
<Button Content="Bottom" DockPanel.Dock="Bottom" />
<Button Content="Left" DockPanel.Dock="Left" />
<Button Content="Right" DockPanel.Dock="Right" />
<Button Content="None" />
</DockPanel>

Simple et efficace !

Conclusion

Nous venons de faire un rapide tour des containeurs de chacune des technologies. Nous voyons à présent comment construire proprement un formulaire. Je vous invite toutefois à creuser les multiples propriétés de chacun de ces contrôles car certaines sont vraiment très utiles.

Je vous donne rendez-vous dans la suite de cet article prochainement, nous y traiterons du DataBinding en WPF : un clin d’œil celui publié ici-même il y a quelques semaines.