Jonathan Birkholz

ItemsSourceChanged Event Using Attached Dependency Properties

If you stumble across this blog, you might have been searching for the non-existent ItemsSourceChanged event on a ListBox or ListView in Wpf.

Yeah… it isn’t there and it sucks.

But, there is a workable that I wouldn’t define as a hack. More of a Wpf extension. :D

I am a huge fan of Attached Dependency Properties. They are a perfect tool to extend the functionality of closed controls. Attached Dependency Properties also circumvent the pain of created your own custom control.

I am going to use an attached dependency property to mimic an ItemsSource Changed event.

The full code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/// <summary>
/// ItemsSourceChanged Behavior uses an Attached Dependency Property
/// to add and raise a rotued event whenever an ItemsControl's ItemsSource property
/// changes. Also looks for INotifyCollectionChanged on the ItemsSource and raises the
/// event on every collection changed event
/// </summary>
public static class ItemsSourceChangedBehavior
{
/// <summary>
/// ItemsSourceChanged Attached Dependency Property with Callback method
/// </summary>
public static readonly DependencyProperty ItemsSourceChangedProperty =
DependencyProperty.RegisterAttached("ItemsSourceChanged",
typeof(bool), typeof(ItemsSourceChangedBehavior),
new FrameworkPropertyMetadata(false, OnItemsSourceChanged));
/// <summary>
/// Static Get method allowing easy Xaml usage and to simplify the
/// GetValue process
/// </summary>
/// <param name="obj">The dependency obj.</param>
/// <returns>True or False</returns>
public static bool GetItemsSourceChanged(DependencyObject obj)
{
return (bool)obj.GetValue(ItemsSourceChangedProperty);
}
/// <summary>
/// Static Set method allowing easy Xaml usage and to simplify the
/// Setvalue process
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="value">if set to <c>true</c> [value].</param>
public static void SetItemsSourceChanged(DependencyObject obj, bool value)
{
obj.SetValue(ItemsSourceChangedProperty, value);
}
/// <summary>
/// Dependency Property Changed Call Back method. This will be called anytime
/// the ItemsSourceChangedProperty value changes on a Dependency Object
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = obj as ItemsControl;
if (itemsControl == null)
return;
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
if (!oldValue && newValue) // If changed from false to true
{
// Create a binding to the ItemsSourceProperty on the ItemsControl
Binding b = new Binding
{
Source = itemsControl,
Path = new PropertyPath(ItemsControl.ItemsSourceProperty)
};
// Since the ItemsSourceListenerProperty is now bound to the
// ItemsSourceProperty on the ItemsControl, whenever the
// ItemsSourceProperty changes the ItemsSourceListenerProperty
// callback method will execute
itemsControl.SetBinding(ItemsSourceListenerProperty, b);
}
else if (oldValue && !newValue) // If changed from true to false
{
// Clear Binding on the ItemsSourceListenerProperty
BindingOperations.ClearBinding(itemsControl, ItemsSourceListenerProperty);
}
}
#endregion
#region Items Source Listener Property
/// <summary>
/// The ItemsSourceListener Attached Dependency Property is a private property
/// the ItemsSourceChangedBehavior will use silently to bind to the ItemsControl
/// ItemsSourceProperty.
/// Once bound, the callback method will execute anytime the ItemsSource property changes
/// </summary>
private static readonly DependencyProperty ItemsSourceListenerProperty =
DependencyProperty.RegisterAttached("ItemsSourceListener",
typeof(object), typeof(ItemsSourceChangedBehavior),
new FrameworkPropertyMetadata(null, OnItemsSourceListenerChanged));
/// <summary>
/// Dependency Property Changed Call Back method. This will be called anytime
/// the ItemsSourceListenerProperty value changes on a Dependency Object
/// </summary>
/// <param name="obj">The obj.</param>
/// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnItemsSourceListenerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = obj as ItemsControl;
if (itemsControl == null)
return;
INotifyCollectionChanged collection = e.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += delegate
{
itemsControl.RaiseEvent(new RoutedEventArgs(ItemsSourceChangedEvent));
};
}
if (GetItemsSourceChanged(itemsControl))
itemsControl.RaiseEvent(new RoutedEventArgs(ItemsSourceChangedEvent));
}
#endregion
#region Items Source Changed Event
/// <summary>
/// Routed Event to raise whenever the ItemsSource changes on an ItemsControl
/// </summary>
public static readonly RoutedEvent ItemsSourceChangedEvent =
EventManager.RegisterRoutedEvent("ItemsSourceChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ItemsControl));
#endregion
}

By setting the ItemsSourceChanged property on any ItemsControl to true, the ItemsSourceListener property will be bound to the ItemsSource property. The ItemsSourceListener callback will be executed anytime the ItemsSource changes and therefore can raise the ItemsSourceChanged routed event.

Also if the ItemsSource implements INotifyCollectionChanged, I added a delegate so that on the CollectionChanged event, the ItemsSourceChanged event will also be raised.

Feel free to use the code and adapt it to your needs.

Peace out!

C#, WPF