Accueil > WPF > WPF – Synchroniser des ListBox

WPF – Synchroniser des ListBox

Dans certaines applications, on peut avoir le besoin d’afficher plusieurs contrôles de type ItemsControl et de les synchroniser dès qu’une scrollbar est déplacée.

Pour faire simple, notre exemple va s’appuyer sur une ListBox.

Créez une application WPF et ouvrez le code xaml de la fenêtre principale. Ajoutez ensuite deux ListBox avec dans chacune des données. Peu importe le type des données que vous choisissez.  Dans cette démo, nous allons ajouter des string en tant qu’item. Le type string est présent dans l’assembly mscorlib, il vous faut donc ajouter le namespace:

xmlns:sys="clr-namespace:System;assembly=mscorlib"

 

        <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <ListBox Height="50">
                <ListBox.Items>
                    <sys:String>Alexandre</sys:String>
                    <sys:String>Greg</sys:String>
                    <sys:String>Patrice</sys:String>
                    <sys:String>Amandine</sys:String>
                    <sys:String>Alexandre</sys:String>
                    <sys:String>Greg</sys:String>
                    <sys:String>Patrice</sys:String>
                    <sys:String>Amandine</sys:String>
                </ListBox.Items>
            </ListBox>
            <ListBox Height="50" Margin="10">
                <ListBox.Items>
                    <sys:String>Appartement</sys:String>
                    <sys:String>Appartement</sys:String>
                    <sys:String>Maison</sys:String>
                    <sys:String>Appartement</sys:String>
                    <sys:String>Maison</sys:String>
                    <sys:String>Appartement</sys:String>
                    <sys:String>Appartement</sys:String>
                    <sys:String>Maison</sys:String>
                </ListBox.Items>
            </ListBox>
        </StackPanel>

Maintenant pour synchroniser nos deux listes, nous allons passer par des propriétés attachées. Nous avons que deux listbox ici, mais imaginez que vous souhaitiez synchroniser différents groupes de Listbox. J’insiste sur le mot groupe, car il va s’agir de notre première propriété attachée. Ajoutez une nouvelle classe ScrollSync dans votre solution. Pour créer rapidement votre propriété attachée vous pouvez utiliser le code snippet propa. Cette propriété sera attachée au ScrollViewer de la listbox qui représente une zone dans laquelle les éléments peuvent défiler.

public class ScrollSync : DependencyObject
  {

      public static string GetGroupName(DependencyObject obj)
      {
          return (string)obj.GetValue(GroupNameProperty);
      }

      public static void SetGroupName(DependencyObject obj, string value)
      {
          obj.SetValue(GroupNameProperty, value);
      }

      public static readonly DependencyProperty GroupNameProperty =
          DependencyProperty.RegisterAttached("GroupName",
          typeof(string), typeof(ScrollSync),
          new UIPropertyMetadata(null,
              new PropertyChangedCallback(OnGroupNameChanged)));

      private static void
          OnGroupNameChanged(DependencyObject d,
          DependencyPropertyChangedEventArgs e)
      { }

  }

 

Notez la présence d’une méthode Callback OnGroupNameChanged que nous allons modifier dans quelques instants après avoir créé:

  • un dictionnaire pour stocker le ScrollViewer de nos Listbox participant à la synchonisation.
  • un dictionnaire pour stocker la Scroll Position horizontale d’un groupe
  • un dictionnaire pour stocker la Scroll Position verticale d’un groupe.
private static Dictionary<ScrollViewer, string> scrollViewers = new Dictionary<ScrollViewer, string>();
        private static Dictionary<string, double> horizontalScrollOffsets = new Dictionary<string, double>();
        private static Dictionary<string, double> verticalScrollOffsets = new Dictionary<string, double>();

 

La première chose que nous allons faire dans le callback est de vérifier que la propriété attachée a bien été assignée à un élément de type ScrollViewer.

var scrollViewer = d as ScrollViewer;
if (scrollViewer != null)

 

Ensuite, via le paramètre DependencyPropertyChangedEventArgs  et sa propriété NewValue, nous allons récupérer le nom de groupe qui a été attribué. On commence par vérifier que cette dernière ne vaut pas null, ce qui signifierait qu’on a pas de nom de groupe associé, puis on cherche si dans les dictionnaires HorizontalScrollOffset et VerticalScrollOffset, on a déjà ce nom engistré. Deux cas se présentent à nous.

1- Le nom est effectivement déjà enregistré, donc on utilise la méthode ScrollToHorizontalOffset ou ScrollToVerticalOffset pour placer la ScrollBar au même niveau que les autres du même groupe.

if (horizontalScrollOffsets.Keys.Contains((string)e.NewValue))
{
   scrollViewer.ScrollToHorizontalOffset(horizontalScrollOffsets[(string)e.NewValue]);
}

     

    2- Le groupe n’est pas encore identifié, on l’ajoute dans les deux dictionnaires précédents.

    horizontalScrollOffsets.Add((string)e.NewValue, scrollViewer.HorizontalOffset);

    verticalScrollOffsets.Add((string)e.NewValue, scrollViewer.VerticalOffset);

     

Afin de synchroniser les différentes ListBox dès qu’une ScrollBar est déplacée, nous allons maintenant nous abonner à l’évènement ScrollChanged du ScrollViewer et ajouter le ScrollViewer dans le Dictionnaire scrollViewers.

if (!scrollViewers.ContainsKey(scrollViewer))
                   {
                       scrollViewers.Add(scrollViewer, (string)e.NewValue);
                       scrollViewer.ScrollChanged += new ScrollChangedEventHandler(ScrollViewer_ScrollChanged);
                   }

 

Le Handler ScrollViewer_ScrollChanged met à notre disposition l’EventArgs ScrollChangedEventArgs dans lequel on peut savoir si un changement sur la ScrollBar horizontale ou la ScrollBar verticale a été effectué. Il suffit de tester que le VerticalChange ou le HorizontalChange est différent de 0. Dans ce cas on récupère le ScrollViewer  via le sender et on appelle la méthode Scroll.

private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
       {
           if (e.VerticalChange != 0 || e.HorizontalChange != 0)
           {
               var changedScrollViewer = sender as ScrollViewer;
               Scroll(changedScrollViewer);
           }
       }

 

Dans cette méthode

  • on récupère le groupe du scrollviewer
  • on met à jour le VerticalOffset et le HorizontalOffset
  • on parcours l’ensemble des scrollViewers enregistrés pour ce groupe et on applique la nouvelle position verticale / horizontale via la méthode ScrollToVerticalOffset / ScrollToHorizontalOffset.
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 dernière étape qu’il nous reste à voir est l’application de la propriété attachée pour chaque ScrollViewer des ListBox. Ouvrez le code xaml et définissez le style suivant:

 <ListBox.Style>
       <Style TargetType="{x:Type ListBox}">
            <Style.Resources>
              <Style TargetType="ScrollViewer">
                    <Setter Property="local:ScrollSync.ScrollGroupName" Value="ScrollGroup" />
              </Style>
            </Style.Resources>
        </Style>
 </ListBox.Style>

 

Téléchargez le code ici.

Categories: WPF Tags: , ,