Création d’une Behavior par un développeur
Pour vous expliquer plus concrètement ce qu’est une behavior, je vous renvoie vers le billet de Christian Schormann qui se charge parfaitement de faire ça.
Si vous parcourez de temps en temps ce blog vous aurez peut être remarqué qu’un des derniers billets portait sur la synchronisation de listbox lors du déplacement d’une scrollbar. Dans ce billet je vous propose de voir comment atteindre le même objectif mais en utilisant cette fois une behavior, le gros avantage étant que celle-ci soit utilisable directement sous Blend par un designer.
Premier point avant de commencer à coder, si vous n’avez pas une version de Blend au moins équivalente à la version 3 installée, veuillez télécharger le sdk suivant, de manière à pouvoir ajouter une référence sur System.Windows.Interactivity.
Ensuite reprenez le projet lié au billet ci-dessus nommé et ajoutez une classe qu’on appellera ScrollSyncBehavior. Pour indiquer qu’il s’agit de la création d’une behavior, nous allons faire hériter notre classe de la classe de base Behavior<T>, T étant un DependencyObject. Dans notre cas ce DependencyObject correspond à un contrôle de type ListBox.
Une fois cette opération effectuée, surchargez les deux méthodes OnAttached et OnDetaching.
protected override void OnAttached() { base.OnAttached(); } protected override void OnDetaching() { base.OnDetaching(); }
Pour cet exemple la behavior est toujours associée à un élément de type Listbox. Vous pouvez le récupérer via la propriété AssociatedObject de la classe de base.
L’élément qui permet de faire défiler les différents items dans une ListBox est un ScrollViewer. Pour récupérer ce ScrollViewer, nous pouvons parcourir l’arborescence visuelle via la classe helper VisualTreeHelper. Comme au départ, nous ne savons pas qu’elle est la position exacte du ScrollViewer, nous allons utiliser la méthode suivante pour parcourir l’arbre de manière récursive jusqu’à ce que nous tombions dessus. Notez au passage que cette méthode pourrait être transformée pour être utilisée comme une méthode d’extension d’un DependencyObject, mais ce n’est pas le sujet ici.
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; }
Dans la méthode OnAttached récupérez donc le ScrollViewer de l’objet associé.
this.AssociatedObject.Loaded += (s, e) => { ScrollViewer scrollViewer = FindVisualChild<ScrollViewer>(this.AssociatedObject);
Pour la suite du code nous nous rapprochons de ce qui avait été fait dans ScrollSync. Ajoutez une DependencyProperty GroupName pour spécifier le groupe de synchronisation auquel participe la ListBox. Reprenez ensuite le code qu’on avait dans OnScrollGroupChanged et modifiez le de manière à refléter le fait qu’on ne s’appuie pas ici sur une propriété attachée pour le nom du groupe.
if (scrollViewer != null) { if (!string.IsNullOrEmpty(GroupName)) { if (horizontalScrollOffsets.Keys.Contains(GroupName)) { scrollViewer.ScrollToHorizontalOffset (horizontalScrollOffsets[GroupName]); } else horizontalScrollOffsets .Add(GroupName, scrollViewer.HorizontalOffset); if (verticalScrollOffsets.Keys.Contains(GroupName)) { scrollViewer.ScrollToVerticalOffset (verticalScrollOffsets[GroupName]); } else verticalScrollOffsets.Add(GroupName, scrollViewer.VerticalOffset); if (!scrollViewers.ContainsKey(scrollViewer)) { scrollViewers.Add(scrollViewer, GroupName); scrollViewer.ScrollChanged += new ScrollChangedEventHandler(ScrollViewer_ScrollChanged); } } }
private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { if (e.VerticalChange != 0 || e.HorizontalChange != 0) { var changedScrollViewer = sender as ScrollViewer; Scroll(changedScrollViewer); } } private static void Scroll(ScrollViewer changedScrollViewer) { var group = scrollViewers[changedScrollViewer]; verticalScrollOffsets[group] = changedScrollViewer.VerticalOffset; horizontalScrollOffsets[group] = changedScrollViewer.HorizontalOffset; foreach (var scrollViewer in scrollViewers .Where((s) => s.Value == group && s.Key != changedScrollViewer)) { if (scrollViewer.Key.VerticalOffset != changedScrollViewer.VerticalOffset) { scrollViewer.Key .ScrollToVerticalOffset(changedScrollViewer.VerticalOffset); } if (scrollViewer.Key .HorizontalOffset != changedScrollViewer.HorizontalOffset) { scrollViewer.Key. ScrollToHorizontalOffset(changedScrollViewer.HorizontalOffset); } } }
La seule différence réside ensuite dans l’ajout de la méthode OnDetaching. Dans cette méthode, nous allons tout simplement nous détacher de l’event ScrollChanged et supprimer le ScrollViewer correspondant de nos listes internes.
protected override void OnDetaching() { base.OnDetaching(); ScrollViewer scrollViewer = FindVisualChild<ScrollViewer>(this.AssociatedObject); if (scrollViewers.ContainsKey(scrollViewer)) { scrollViewer.ScrollChanged -= new ScrollChangedEventHandler(ScrollViewer_ScrollChanged); scrollViewers.Remove(scrollViewer); } if (!scrollViewers.ContainsValue(GroupName)) { if (horizontalScrollOffsets.ContainsKey(GroupName)) horizontalScrollOffsets.Remove(GroupName); if (verticalScrollOffsets.ContainsKey(GroupName)) verticalScrollOffsets.Remove(GroupName); } }
Au niveau du code xaml, supprimez la propriété attachée ajoutée au niveau des styles car elle n’a plus de raison d’exister, et ajoutez la behavior dans l’élément ListBox comme ceci.
<ListBox Height="50"> <i:Interaction.Behaviors> <local:ScrollSyncBehavior GroupName="GroupTest"/> </i:Interaction.Behaviors>
i pointant sur System.Windows.Interactivity
xmlns:i="clr-namespace:System.Windows.Interactivity; assembly=System.Windows.Interactivity"
Retrouvez les sources ici.