À la découverte d'Avalon Partie IV : Les menus dans Avalon

Jean-Alain Baeyens

Après avoir découvert dans le tutoriel précédent comment utiliser les contrôles WinFX usuels avec XAML et .NET, nous allons poursuivre la découverte de « Windows Presentation Foundation » avec les menus. Nous aurons ainsi fait le tour d'une application simple avec les API d'Avalon. ?

- Titre : À la découverte d'Avalon Partie IV : Les menus dans Avalon

- Auteur : Jean-Alain Baeyens

- Parution : 7 novembre 2005

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2005 Jean-Alain Baeyens. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Avertissement

- II. Remerciements

- III. Introduction

- IV. Le menu d'une fenêtre

- IV-A. Le menu principal

- IV-B. Les sous-menus

- IV-C. Les éléments inactifs ou cochés du menu

- IV-D. Associer une action à un menu

- IV-E. Le menu dynamique

- IV-F. Séparer le menu et le reste de la fenêtre

- V. Le menu contextuel

- VI. La barre d'outils (Toolbar)

- VI-A. Une barre d'outils statique

- VI-B. Des toolbars mobiles dans un ensemble

- VII. Conclusion

I - Avertissement

Cet article fait partie d'un ensemble sur Avalon (« Windows Presentation Foundation »). Ceux-ci, bien qu'écrits séparément, forment un tout. À la manière des chapitres d'un livre, vous pouvez accéder directement au sujet qui vous intéresse, mais pour un aperçu complet, il est préférable de commencer la lecture au premier article.

1re partie : Installation
2epartie : Ma première fenêtre avec Avalon
3epartie : Les contrôles courants
4e partie : Le menu
5epartie : Les modèles « Avalon Express Application » et « Navigation Application »

II - Remerciements

Je remercie mon épouse ainsi que « pedro204 » pour la relecture de cet article.

III - Introduction

Ce tutoriel nous permettra d'étudier le fonctionnement des menus. Dans le cadre de cet article découverte, nous nous limiterons aux fonctionnalités de base.

Si vous souhaitez reproduire l'exemple du chapitre, commencez par créer un nouveau projet de type « Avalon Application ».

IV - Le menu d'une fenêtre

IV-A - Le menu principal

Traditionnellement, le menu principal se présente horizontalement en haut de fenêtre sur un fond gris clair. Pour créer notre menu, nous allons utiliser les classes Menu et MenuItem. La propriété VerticalAlignment permet de placer le menu sous la barre de titre comme souhaité.

Code XAML pour un menu principal

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Menu VerticalAlignment="Top" Height="20">
<MenuItem Header="Fichier"/>
<MenuItem Header="Edition"/>
<MenuItem Header="Aide"/>
</Menu>
</Window>

La propriété Height est obligatoire sinon le menu occupera toute la hauteur disponible dans notre fenêtre.

IV-B - Les sous-menus

Pour créer un sous-menu, il suffit de définir un ou des MenuItem dans le nœud MenuItem père. Chaque nouveau MenuItem peut, à son tour, contenir d'autres MenuItem fils.

Code XAML pour un menu avec sous-menu

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Menu VerticalAlignment="Top" Height="20">
<MenuItem Header="Fichier">
<MenuItem Header="Ouvrir"/>
<MenuItem Header="Fermer"/>
<MenuItem Mode="Separator"/>
<MenuItem Header="Vérificateur">
<MenuItem Header="1.0"/>
<MenuItem Header="2.0"/>
</MenuItem>
</MenuItem>
<MenuItem Header="Edition">
<MenuItem Header="Copier"/>
<MenuItem Header="Coller"/>
</MenuItem>
<MenuItem Header="Aide"/>
</Menu>
</Window>

Notez l'utilisation de MenuItem Mode=« Separator » pour créer un séparateur.

IV-C - Les éléments inactifs ou cochés du menu

Pour rendre un élément d'un menu inactif (grisé), il suffit d'utiliser la propriété IsEnabled. Pour cocher un élément d'un menu, il suffit d'utiliser la propriété IsChecked.

Code XAML pour rendre une option du menu Disabled ou Checked

<MenuItem Header="Fichier">
<MenuItem Header="Ouvrir"/>
<MenuItem Header="Fermer" IsEnabled="False"/>
<MenuItem Mode="Separator"/>
<MenuItem Header="Vérificateur">
<MenuItem Header="1.0" IsChecked="True"/>
<MenuItem Header="2.0"/>
</MenuItem>
</MenuItem>

IV-D - Associer une action à un menu

Pour associer une méthode au clic sur un élément du menu, il suffit d'assigner le nom de la méthode à l'attribut Click de l'élément correspondant. Dans l'exemple, nous allons changer le statut (Enabled, Checked) de certains éléments du menu dans les actions déclenchées.

Code XAML pour associer un élément du menu à une méthode

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Menu VerticalAlignment="Top" Height="20">
<MenuItem Header="Fichier">
<MenuItem Name="Ouvre" Header="Ouvrir" Click="OpenClick"/>
<MenuItem Name="Ferme" Header="Fermer" IsEnabled="False"
Click="CloseClick"/>
<MenuItem Mode="Separator"/>
<MenuItem Header="Vérificateur">
<MenuItem Name="V1" Header="1.0" IsChecked="True"
Click="V1Click"/>
<MenuItem Name="V2" Header="2.0" Click="V2Click"/>
</MenuItem>
</MenuItem>
<MenuItem Header="Edition">
<MenuItem Header="Copier"/>
<MenuItem Header="Coller"/>
</MenuItem>
<MenuItem Header="Aide"/>
</Menu>
</Window>

Pour pouvoir agir directement sur un élément du menu, le plus simple est de lui donner un nom en utilisant l'attribut « Name ».

Code C# associé à notre menu

private void OpenClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = false;
this.Ferme.IsEnabled = true;
}
private void CloseClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = true;
this.Ferme.IsEnabled = false;
}
private void V1Click(object sender, RoutedEventArgs e)
{
this.V1.IsChecked = true;
this.V2.IsChecked = false;
}
private void V2Click(object sender, RoutedEventArgs e)
{
this.V1.IsChecked = false;
this.V2.IsChecked = true;
}

IV-E - Le menu dynamique

Pour pouvoir modifier dynamiquement un menu depuis le code C#, il suffit de manipuler notre objet de la classe Menu. Nous devons commencer par le nommer.

Nommer le menu

<Menu Name="MainMenu" VerticalAlignment="Top" Height="20">

Une fois cette opération réalisée, nous pouvons atteindre la propriété Items de notre menu. Pour l'exemple, nous allons ajouter l'élément « Traiter » dans notre menu lorsque l'on effectue « Ouvre » et le retirer lorsque l'on effectue « Ferme ». Le menu « Traiter » contient deux éléments.

En premier, nous devons créer nos trois MenuItem. Nous devons remplir les différentes propriétés utiles et associer les différents évènements, au minimum la propriété Header et vraisemblablement l'évènement Click. Ici, je me limiterai à Header. Nous ajoutons avec la méthode Add les deux options à « Traiter ». Il ne reste alors qu'à insérer « Traiter » dans le menu principal en utilisant la méthode Insert. Le premier paramètre de la méthode Insert indique la position où doit être inséré le menu. N'oubliez pas que le premier élément est en position 0.

Ajout dynamique dans le menu

private void OpenClick(object sender, RoutedEventArgs e)
{
MenuItem mnuTraiter = new MenuItem();
MenuItem mnuTraiter1 = new MenuItem();
MenuItem mnuTraiter2 = new MenuItem();
mnuTraiter.Header = "Traiter";
mnuTraiter1.Header = "Traitement principal";
mnuTraiter2.Header = "Traitement secondaire";
mnuTraiter.Items.Add(mnuTraiter1);
mnuTraiter.Items.Add(mnuTraiter2);
this.Ouvre.IsEnabled = false;
this.Ferme.IsEnabled = true;
this.MainMenu.Items.Insert(1, mnuTraiter);
}

Pour retirer un élément, il suffit d'utiliser la méthode RemoveAt du menu contenant l'élément tout en précisant la position de l'élément à supprimer.

Suppression dynamique dans le menu

private void CloseClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = true;
this.Ferme.IsEnabled = false;
this.MainMenu.Items.RemoveAt(1);
}

Une alternative est de créer dans XAML le menu complet avec tous les éléments. Ensuite le code C#, dans le constructeur ou dans la méthode associée à l'évènement loaded de votre fenêtre, vous retirez les éléments qui ne doivent pas être présents. Il ne reste ensuite qu'à les insérer et les retirer aux moments opportuns.

Code XAML modifié pour disposer des éléments complémentaires

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Menu Name="MainMenu" VerticalAlignment="Top" Height="20">
<MenuItem Header="Fichier">
<MenuItem Name="Ouvre" Header="Ouvrir" Click="OpenClick"/>
<MenuItem Name="Ferme" Header="Fermer" IsEnabled="False"
Click="CloseClick"/>
<MenuItem Mode="Separator"/>
<MenuItem Header="Vérificateur">
<MenuItem Name="V1" Header="1.0" IsChecked="True"
Click="V1Click"/>
<MenuItem Name="V2" Header="2.0" Click="V2Click"/>
</MenuItem>
</MenuItem>
<MenuItem Name="Traiter" Header="Traiter">
<MenuItem Header="Traitement principal"/>
<MenuItem Header="Traitement secondaire"/>
</MenuItem>
<MenuItem Name="Editer" Header="Edition">
<MenuItem Name="Copie" Header="Copier"/>
<MenuItem Name="Coller" Header="Coller"/>
</MenuItem>
<MenuItem Header="Aide"/>
</Menu>
</Window>

Le code C# qui adapte le menu

public Window1()
{
InitializeComponent();
this.MainMenu.Items.Remove(Traiter);
}

private void OpenClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = false;
this.Ferme.IsEnabled = true;
this.MainMenu.Items.Insert(1,Traiter);
}
private void CloseClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = true;
this.Ferme.IsEnabled = false;
this.MainMenu.Items.Remove(Traiter);
}

Notez au passage que j'ai utilisé la méthode Remove au lieu de RemoveAt. Cela est rendu possible par le fait que l'objet MenuItem est nommé. Cela aurait également pu être possible avec la technique précédente, mais nous aurions dû définir notre objet au niveau de la classe et non dans la méthode.

Au lieu d'utiliser les méthodes Insert et Remove, nous pouvons utiliser la propriété Visibility.

Modifier le menu en utilisant Visibility

public Window1()
{
InitializeComponent();
this.Traiter.Visibility = Visibility.Collapsed;
}

private void OpenClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = false;
this.Ferme.IsEnabled = true;
this.Traiter.Visibility = Visibility.Visible;
}
private void CloseClick(object sender, RoutedEventArgs e)
{
this.Ouvre.IsEnabled = true;
this.Ferme.IsEnabled = false;
this.Traiter.Visibility = Visibility.Collapsed;
}

IV-F - Séparer le menu et le reste de la fenêtre

Bien que notre menu se présente parfaitement, il est judicieux de séparer celui-ci du reste de la fenêtre. Si votre fenêtre ne contient qu'un menu qui lui-même ouvrira des fenêtres filles, alors il n'y a pas de problème. Par contre, si votre fenêtre contient d'autres contrôles, l'aspect du menu va s'en trouver altéré. Je ne saurais donc trop vous conseiller d'utiliser une grille dont la première ligne contiendra le menu.

Le menu inclus dans une grille

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" Name="MainMenu" VerticalAlignment="Top" Height="20">
</Menu>
</Grid>
</Window>

En utilisant un Grid, vous avez une entière liberté pour positionner votre menu. Dans le même esprit, rien ne vous empêche de créer plusieurs menus dans la même fenêtre.

V - Le menu contextuel

Pour illustrer le menu contextuel, nous allons ajouter un contrôle de type TextBox dans notre fenêtre. Nous devons ensuite utiliser la propriété ContextMenu du contrôle et y assigner un objet de type ContextMenu. Celui-ci reçoit les éléments du menu souhaité.

Création d'un menu contextuel en XAML

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu Grid.Row="0" Name="MainMenu" VerticalAlignment="Top" Height="20">
</Menu>
<TextBox Grid.Row="1" Name= "Nom" Width="100" Height="30">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Copier"/>
<MenuItem Header="Coller"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</Grid>
</Window>

Le menu contextuel peut également être réalisé dans le code .NET. Pour cela, il suffit d'appliquer les techniques vues dans le paragraphe Le menu dynamique, mais en assignant la propriété ContextMenu du contrôle. Cette technique est particulièrement efficace pour assigner un même menu à différents contrôles.

VI - La barre d'outils (Toolbar)

VI-A - Une barre d'outils statique

Pour réaliser une barre d'outils statique sous le menu, nous devons commencer par ajouter une ligne dans notre Grid. Dans l'exemple, j'ai fixé la hauteur de la ligne à 30 points ce qui limitera du même coup la hauteur de notre barre d'outils.

La barre d'outils elle-même est créée grâce à un objet de la classe ToolBar. Pour placer les éléments dans la barre d'outils, il n'y a qu'à ajouter au sein du nœud ToolBar les contrôles que vous souhaitez voir apparaître. Dans l'exemple, j'ai placé en premier une image qui permet une identification visuelle de la barre d'outils. Cette image est suivie de trois boutons. Les boutons incluent eux-mêmes une icône au lieu d'un texte. N'oubliez pas d'assigner la propriété ToolTip de votre bouton pour permettre une identification facile par l'utilisateur de la fonction de chaque bouton. Pour associer une action à notre bouton, il ne nous reste qu'à assigner la propriété Click.

Code à ajouter pour une barre d'outils statique.

...
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="30"/>
<RowDefinition/>
</Grid.RowDefinitions>
...
<ToolBar Grid.Row="1" VerticalContentAlignment="Center"  >
<Image>
<Image.Source>
<Binding Source="I1.ico"/>
</Image.Source>
</Image>
<Button ToolTip="Ouvre" Click="OpenClick">
<Image>
<Image.Source>
<Binding Source="I2.ico"/>
</Image.Source>
</Image>
</Button>
<Button ToolTip="Ferme" Click="CloseClick">
<Image>
<Image.Source>
<Binding Source="I3.ico"/>
</Image.Source>
</Image>
</Button>
<Button ToolTip="Change de vérificateur">
<Image>
<Image.Source>
<Binding Source="I4.ico"/>
</Image.Source>
</Image>
</Button>
</ToolBar>

VI-B - Des toolbars mobiles dans un ensemble

Les applications modernes ne se contentent pas d'afficher une seule barre d'outils, mais bien un ensemble que vous pouvez déplacer ou redimensionner à votre guise. Avec Avalon, nous pouvons créer un container dans lequel nous allons placer nos barres d'outils. Elles pourront être alors déplacées ou redimensionnées par l'utilisateur dans l'espace déterminé par le container. Si l'utilisateur réduit trop la taille d'une barre d'outils, les éléments qui ne sont plus affichables sont automatiquement ajoutés dans une zone de dépassement et rendus accessibles via la petite flèche qui marque la fin de la barre d'outils. Le container est créé en utilisant un objet de la classe ToolBarTray. La propriété IsLocked permet de spécifier si les barres d'outils contenues sont mobiles ou non.

Pour l'exemple, j'ai doublé la hauteur de la ligne de la grille qui contient la barre d'outils dont j'ai fixé la hauteur avec l'incontournable propriété Height. J'ai également ajouté une seconde barre d'outils qui contient une image et une ComboBox.

Utilisation du ToolBarTray

<Window x:Class="AvalonMenu.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
Text="Les menus avec Avalon"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="60"/>
<RowDefinition/>
</Grid.RowDefinitions>
...
<ToolBarTray Grid.Row="1" IsLocked="False">
<ToolBar VerticalContentAlignment="Center" Height="30">
<Image>
<Image.Source>
<Binding Source="I1.ico"/>
</Image.Source>
</Image>
<Button ToolTip="Ouvre" Click="OpenClick">
<Image>
<Image.Source>
<Binding Source="I2.ico"/>
</Image.Source>
</Image>
</Button>
<Button ToolTip="Ferme" Click="CloseClick">
<Image>
<Image.Source>
<Binding Source="I3.ico"/>
</Image.Source>
</Image>
</Button>
</ToolBar>
<ToolBar VerticalContentAlignment="Center" Height="30">
<Image >
<Image.Source>
<Binding Source="logo.gif"/>
</Image.Source>
</Image>
<ComboBox>
<ComboBoxItem IsSelected="true">Express</ComboBoxItem>
<ComboBoxItem>Complet</ComboBoxItem>
</ComboBox>
</ToolBar>
</ToolBarTray>
...
</Grid>
</Window>

Vous pouvez utiliser la propriété Band pour placer les barres d'outils sur des lignes différentes.

VII - Conclusion

Avec Avalon, la gestion des menus est vraiment très simple et permet très facilement de réaliser des menus et des barres d'outils déjà très complexes.

Avec ce tutoriel se termine notre tour d'horizon du modèle d'application Windows « classique ». Dans le prochain article, nous verrons qu'il est possible avec Avalon d'avoir des applications dans un autre modèle de développement que le traditionnel « WinForm ».