EDIT:
In order to clear up all confusions with instanct-closing as duplicates. Please see point (3.) explaining why the accepted answer does not apply. In short, the linked answer is fine as long as you are not using XAML to set the value because XAML will never call PropertyChangedCallback
because it re-uses the default instance.
Question:
Considering a simple WPF's Attached Property of ObservableCollection<T>
type with XAML-defined value:
// public static class MyCollectionExetension.cs
public static ObservableCollection<int> GetMyCollection(DependencyObject obj)
{
return (ObservableCollection<int>)obj.GetValue(MyCollectionProperty);
}
public static void SetMyCollection(DependencyObject obj, ObservableCollection<int> value)
{
obj.SetValue(MyCollectionProperty, value);
}
public static readonly DependencyProperty MyCollectionProperty =
DependencyProperty.RegisterAttached("MyCollection", typeof(ObservableCollection<int>),
typeof(MyCollectionExetension), new PropertyMetadata(null);
public static void DoThisWhenMyCollectionChanged(DependencyObejct assignee, IEnumerable<int> newValues) {
// how can I invoke this?
}
//UserControl.xaml
<Grid xmlns:sys="clr-namespace:System;assembly=mscorlib">
<b:DataGridExtensions.MyCollection >
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
</b:DataGridExtensions.MyCollection>
</Grid>
How can I hook collection changed events with access to the both the DependencyObject
it is attached to and the new items? MyCollection must be definable in XAML.
It seems simple at first, but none of following worked for me:
new UIPropertyMetadata(null, CollectionChanged)
causes crash: XamlObjectWriterException: 'Collection property 'System.Windows.Controls.Grid'.'MyCollection' is null.'
OK, let's provide default value in order to avoid the crash above: new UIPropertyMetadata(new ObservableCollection<int>(), CollectionChanged)
That, however prevent CollectionChanged
from ever firing due to XAML not instantiating new collection but rather adding items to existing collection.
Fixing the above and hook CollectionChanged
while providing default value new UIPropertyMetadata(ProvideWithRegisteredCollectionChanged(), CollectionChanged)
does not work neither because there is no way to pass the DependencyProperty
to ProvideWithRegisteredCollectionChanged()
method due to being in static context.
MyCollection
either in GetMyCollection()
getter or CoerceValueCallback
does not prevent the crash from point 1. above since it does not seem to be called before property is first accessed.You can't correctly assign a non-null default value for a collection-type attached property. Hence you have to create an instance in XAML.
Since declaring an ObservableCollection directly in XAML seems not easily possible, declare an appropriate derived type:
public class MyCollection : ObservableCollection<int>
{
}
and create an instance in XAML like this:
<Grid>
<b:MyCollectionExtension.MyCollection>
<b:MyCollection>
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
</b:MyCollection>
</b:MyCollectionExtension.MyCollection>
</Grid>
The attached property declaration should look like shown below, including the code that attaches and detaches a CollectionChanged
event handler.
public static class MyCollectionExtension
{
public static MyCollection GetMyCollection(DependencyObject obj)
{
return (MyCollection)obj.GetValue(MyCollectionProperty);
}
public static void SetMyCollection(DependencyObject obj, MyCollection value)
{
obj.SetValue(MyCollectionProperty, value);
}
public static readonly DependencyProperty MyCollectionProperty =
DependencyProperty.RegisterAttached(
"MyCollection",
typeof(MyCollection),
typeof(MyCollectionExtension),
new PropertyMetadata(MyCollectionPropertyChanged));
public static void MyCollectionPropertyChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var oldCollection = e.OldValue as MyCollection;
var newCollection = e.NewValue as MyCollection;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= MyCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += MyCollectionChanged;
}
}
public static void MyCollectionChanged(
object o, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
// ...
}
}
}