En Silverlight comme en WPF, il existe un contrôle nommé ListBox auquel on peut Binder un IEnumerable<T> ce qui nous donne une liste populée avec un ensemble de ListBoxItem.
Mon souhait était de créer un Menu composé d’HyperLinkButton afin d’utiliser notamment la propriété TargetName très utile en Silverlight 3 pour la navigation, et de lancer une animation automatiquement sur le HyperLinkButton avec le VisualStateManager quand l’url courante correspond au NavigateUri.
J’ai donc décidé de faire une classe MenuItem avec trois propriétés ( NavigateUri, Content et TargetName), qui au chargement de l’application sera utilisée pour créer une liste de MenuItem Bindée à mon contrôle Menu
Au niveau Xaml nous avons le code suivant:
<ListBox ItemsSource=”{Binding MaSource}”>
<ListBox.ItemTemplate>
<DataTemplate>
<HyperLinkButton TargetName=”{Binding TargetName}” NavigateUri=”{Binding NavigateUri}” Content=”{Binding Content}” />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Ensuite via un module interne à l’application mon contrôle menu connait à tout moment l’url courante. Cette Url est donnée via une DependencyProperty contenant une PropertyMetadata avec un PropertyChangedCallBack. A chaque fois que l’url change le CallBack est appelé et je peux maintenant lancer l’animation.
Il nous reste plus qu’à trouver le bon HyperlinkButton dans la Listbox. Là est l’intérêt de ce billet. Comment faire ?
Si vous parcourez la propriété Items de votre List elle contiendra une collection de MenuItem c’est à dire la liste bindée au début. Seulement ce que nous voulons c’est le HyperLinkButton et non le MenuItem, car la méthode GoToState du VisualStateManager prend en paramètre le contrôle sur lequel lancer l’animation, le nom du VisualState et un boolean pour indiquer si il y a une transition.
Même si nous avons ajouté notre propre DataTemplate, la liste contient toujours des ListBoxItem que nous pouvons récupérer de la manière suivante:
foreach (MenuItem itm in MaListBox.Items)
{
DependencyObject obj = MaListBox.ItemContainerGenerator.ContainerFromItem(itm);
}
Regardez le dependencyObject, nous avons bien un ListBoxItem. Comme vous devez vous en douter le ListBoxItem contient notre HyperLinkButton du départ. Pour l’extraire vous devrez faire appel à cette méthode Helper VisualTreeHelper présente dans System.Windows.Media ce qui au final nous donne ce code:
Contenu du CallBack:
NavigationMenu nav = d as NavigationMenu;
if (nav != null )
{
foreach (MenuItem itm in nav.menuListBox.Items)
{
DependencyObject obj = nav.menuListBox.ItemContainerGenerator.ContainerFromItem(itm);
HyperlinkButton btn = FindVisualChild<HyperlinkButton>(obj);
if (btn != null)
{
if (btn.NavigateUri.ToString() == e.NewValue.ToString())
VisualStateManager.GoToState(btn, NavigationMenu.ActiveLinkState, true);
else
VisualStateManager.GoToState(btn, NavigationMenu.InactiveLinkState, true);
}
}
}
Méthode appelée dans le CallBack et contenant VisualTreeHelper:
public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Voilà, nous arrivons au bout de ce petit billet, en espérant que ca puisse vous servir. Bon coding :)