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. A 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.

1er partie: Installation
2ème partie: Ma première fenêtre avec Avalon
3ème partie: Les contrôles courants
4ème partie: Le menu
5ème partie: 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 bases.

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

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
Sélectionnez
<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.

Image non disponible

B. Les sous-menus

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

Code XAML pour un menu avec sous-menu
Sélectionnez
<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>
Image non disponible

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

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
Sélectionnez

<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>
Image non disponible

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
Sélectionnez
<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
Sélectionnez
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;
}

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
Sélectionnez
<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
Sélectionnez
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
Sélectionnez
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ésent. Il ne reste ensuite qu'à les insérer et les retirer aux moments opportuns.

Code XAML modifié pour disposer des éléments complémentaires
Sélectionnez
<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
Sélectionnez
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 du 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
Sélectionnez
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;
}

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
Sélectionnez
<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
Sélectionnez
<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>
Image non disponible

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)

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 noeud 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 3 boutons. Les boutons incluent eux même 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.
Sélectionnez

...
<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>
Image non disponible

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 rendu accessible 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
Sélectionnez
<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>
Image non disponible

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 des 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".