SelectedItems Behavior for ListBox and MultiSelector






3.64/5 (5 votes)
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.
private static void OnSelectedItemsChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e) {
IList selectedItems = null;
void CollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs args) {
if (args.OldItems != null) foreach
(var item in args.OldItems)if (selectedItems.Contains(item))selectedItems.Remove(item);
if (args.NewItems != null) foreach
(var item in args.NewItems) if (!selectedItems.Contains(item)) selectedItems.Add(item);
}
if (d is MultiSelector multiSelector) {
selectedItems = multiSelector.SelectedItems;
multiSelector.SelectionChanged += OnSelectionChanged;
}
if (d is ListBox listBox) {
selectedItems = listBox.SelectedItems;
listBox.SelectionMode = SelectionMode.Multiple;
listBox.SelectionChanged += OnSelectionChanged;
}
if (selectedItems == null) return;
if (e.OldValue is INotifyCollectionChanged collection1)
collection1.CollectionChanged -= CollectionChangedEventHandler;
if (e.NewValue is INotifyCollectionChanged collection2)
collection2.CollectionChanged += CollectionChangedEventHandler;
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
var s = sender as DependencyObject;
if (!GetIsBusy(s)) {
SetIsBusy(s, true);
var list = GetSelectedItems((DependencyObject)sender);
foreach (var item in e.RemovedItems) if (list.Contains(item)) list.Remove(item);
foreach (var item in e.AddedItems) if (!list.Contains(item)) list.Add(item);
SetIsBusy(s, false);
}
}
private static readonly DependencyProperty IsBusyProperty =
DependencyProperty.RegisterAttached("IsBusy", typeof(bool),
typeof(SelectedItemsBahavior), new PropertyMetadata(default(bool)));
private static void SetIsBusy(DependencyObject element, bool value) {
element.SetValue(IsBusyProperty, value);
}
private static bool GetIsBusy(DependencyObject element) {
return (bool)element.GetValue(IsBusyProperty);
}
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.