Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SelectedItems Behavior for ListBox and MultiSelector

0.00/5 (No votes)
14 Oct 2017 2  
This is an alternative for SelectedItems Behavior for ListBox and MultiSelector

Introduction

I found the originals concept ( <- please follow the link ) good, but the implementation contains several great mistakes.

Issues of the Original

public class ViewModel{
   public string[] ItemsSource { get; } = new[] 
   { "Frist Item", "Second Item", "Third Item", "Fourth Item" };
   public ObservableCollection<string> SelectedItems { get; } = new ObservableCollection<string>();
}

On each access to this SelectedItems-Property, a new ObservableCollection will be created!
Binding several Controls to that, or access such Properties by Code will lead to complex mis-behavior.

public class SelectedItemsBahavior    {
   public static readonly DependencyProperty SelectedItemsProperty =
      DependencyProperty.RegisterAttached
      ("SelectedItems", typeof(INotifyCollectionChanged), typeof(SelectedItemsBahavior),
      new PropertyMetadata(default(IList), OnSelectedItemsChanged));

   public static void SetSelectedItems(DependencyObject d, INotifyCollectionChanged value)        
   {
      d.SetValue(SelectedItemsProperty, value);
   }
   public static IList GetSelectedItems(DependencyObject d)        {
      return (IList)d.GetValue(SelectedItemsProperty);
   }
   //...

This is puzzling: Register a DependencyProperty as INotifyCollectionChanged, but the Property-Getter retrieves an IList-Object??
Moreover - what is it good for, to Register the INotifyCollectionChanged - Type?

In the following you see the CollectionChanged-Event subscribed, but step-wise debugging shows, that the code executes, but has no effect at all.

  1  private static void OnSelectedItemsChanged
  2     (DependencyObject d, DependencyPropertyChangedEventArgs e)  {
  3     IList selectedItems = null;
  4     void CollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs args)  {
  5           if (args.OldItems != null) foreach 
  6           (var item in args.OldItems)if (selectedItems.Contains(item))selectedItems.Remove(item);
  7           if (args.NewItems != null) foreach 
  8           (var item in args.NewItems) if (!selectedItems.Contains(item)) selectedItems.Add(item);
  9     }
 10     if (d is MultiSelector multiSelector)   {
 11           selectedItems = multiSelector.SelectedItems;
 12           multiSelector.SelectionChanged += OnSelectionChanged;
 13     }
 14     if (d is ListBox listBox)   {
 15           selectedItems = listBox.SelectedItems;
 16           listBox.SelectionMode = SelectionMode.Multiple;
 17           listBox.SelectionChanged += OnSelectionChanged;
 18     }
 19     if (selectedItems == null) return;
 20     if (e.OldValue is INotifyCollectionChanged collection1)
 21           collection1.CollectionChanged -= CollectionChangedEventHandler;
 22     if (e.NewValue is INotifyCollectionChanged collection2)
 23           collection2.CollectionChanged += CollectionChangedEventHandler;
 24  }
 25  
 26  private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
 27     var s = sender as DependencyObject;
 28     if (!GetIsBusy(s))   {
 29           SetIsBusy(s, true);
 30           var list = GetSelectedItems((DependencyObject)sender);
 31           foreach (var item in e.RemovedItems) if (list.Contains(item)) list.Remove(item);
 32           foreach (var item in e.AddedItems) if (!list.Contains(item)) list.Add(item);
 33           SetIsBusy(s, false);
 34     }
 35  }
 36  private static readonly DependencyProperty IsBusyProperty =
 37     DependencyProperty.RegisterAttached("IsBusy", typeof(bool), 
 38     typeof(SelectedItemsBahavior), new PropertyMetadata(default(bool)));
 39  
 40  private static void SetIsBusy(DependencyObject element, bool value) {
 41     element.SetValue(IsBusyProperty, value);
 42  }
 43  
 44  private static bool GetIsBusy(DependencyObject element) {
 45     return (bool)element.GetValue(IsBusyProperty);
 46  }

There is no need, that the behavior consumes a .CollectionChanged - Event of anything (lines #2-6 + #16-20) - Moreover the IsBusy-Dependancy-Property (lines #25-26, #30, #33-42) has no effect too.


Proposal to fix it

public static class SelectedItemsBahavior {
   public static readonly DependencyProperty SelectedItemsProperty =
         DependencyProperty.RegisterAttached
         ("SelectedItems", typeof(IList), typeof(SelectedItemsBahavior),
         new PropertyMetadata(default(IList), OnAttach));
   public static void SetSelectedItems(DependencyObject d, IList value) {
      d.SetValue(SelectedItemsProperty, value);
   }
   public static IList GetSelectedItems(DependencyObject d) {
      return (IList)d.GetValue(SelectedItemsProperty);
   }
   private static void OnAttach(DependencyObject d, DependencyPropertyChangedEventArgs e) {
      var multiSelector = d as MultiSelector;
      if (multiSelector != null) {
         multiSelector.SelectionChanged += OnSelectionChanged;
         return;
      }
      var listBox = d as ListBox;
      if (listBox != null) {
         listBox.SelectionChanged += OnSelectionChanged;
         listBox.SelectionMode = SelectionMode.Multiple;
      }
   }
   private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
      var list = GetSelectedItems((DependencyObject)sender);
      foreach (var item in e.RemovedItems) list.Remove(item);
      foreach (var item in e.AddedItems) list.Add(item);
   }
}

Common implementation of an IList-DependencyProperty, and the behaviors job is simply to keep that lists content up to date.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here