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.