Collection Attached Properties with DataContext inheritance in WPF
The scenario is as follows: you want to attach a collection to an element and fill it via XAML. The objects that you add to the collection should inherit the element DataContext.
One issue arising is that if you use an attached property for a collection you cannot use a default value in the metadata (since it would be used for all the instances of the property) and that if you don't instantiate it, any attempt to add stuff to it via the markup will result in a collection null exception. If this were a mere dependency property from inside the control, the solution would have been to create the collection in the element constructor. Since it is an attached one, we don't have this mechanism available.
Another solution would be to instantiate it in the property getter, like a normal property with a backing field, but in the case of XAML, the dependency and attached properties are accessed directly via the DependencyObject.GetValue method, bypassing the parent class property getter entirely. The only solution to force going through the getter is to register the attached property with a name string that is different from the convention.
A quick example:
Above we are registering an attached property of type ObservableCollection<MyItem> ingeniously named MyAttachedCollection from a class named ParentClass. The convention says we whould register it using a name that is the DependencyProperty name without the ending "Property". I, however, have added an "Internal" string, which forces the property system to go through the getter!
Here is the getter:
Voila! This allows you to instantiate in the getter the troublesome collection.
Now, the other request in the scenario is to inherit the DataContext from the element the collection is attached to. To do that we at least need that the collection and the items in it to be DependencyObjects in order to have a DataContext, but we are going even further: we need to define them as Freezable! The Freezable abstract class requires you to implement a CreateInstanceCore method. Just make the MyItem class inherit Freezable and throw a NotImplementedException in the CreateInstanceCore method. For the collection itself, we need to inherit from another useful class: FreezableCollection<T>. After replacing ObservableCollection with FreezableCollection, the collection and the items have the same DataContext property as the element the property is attached to. An interesting fact here is that Freezable does NOT inherit from FrameworkElement, but it is a DependencyObject and the DataContext property does propagate down through the element tree.
One last interesting thing: the FreezableCollection class implements INotifyCollectionChanged, but explicitly! In order to use the CollectionChanged event you need to cast it in code to INotifyCollectionChanged.
One issue arising is that if you use an attached property for a collection you cannot use a default value in the metadata (since it would be used for all the instances of the property) and that if you don't instantiate it, any attempt to add stuff to it via the markup will result in a collection null exception. If this were a mere dependency property from inside the control, the solution would have been to create the collection in the element constructor. Since it is an attached one, we don't have this mechanism available.
Another solution would be to instantiate it in the property getter, like a normal property with a backing field, but in the case of XAML, the dependency and attached properties are accessed directly via the DependencyObject.GetValue method, bypassing the parent class property getter entirely. The only solution to force going through the getter is to register the attached property with a name string that is different from the convention.
A quick example:
public static DependencyProperty MyAttachedCollectionProperty =
DependencyProperty.RegisterAttached("MyAttachedCollectionInternal",
typeof (ObservableCollection<MyItem>),
typeof (ParentClass),
new PropertyMetadata(null));
Above we are registering an attached property of type ObservableCollection<MyItem> ingeniously named MyAttachedCollection from a class named ParentClass. The convention says we whould register it using a name that is the DependencyProperty name without the ending "Property". I, however, have added an "Internal" string, which forces the property system to go through the getter!
Here is the getter:
public static void SetMyAttachedCollection(UIElement element, ObservableCollection<MyItem> value)
{
if (element != null)
{
element.SetValue(MyAttachedCollectionProperty, value);
}
}
public static ObservableCollection<MyItem> GetMyAttachedCollection(UIElement element)
{
ObservableCollection<MyItem> collection =
(ObservableCollection<MyItem>) element.GetValue(MyAttachedCollectionProperty);
if (collection == null)
{
collection = new ObservableCollection<MyItem>();
SetMyAttachedCollection(element, collection);
}
return collection;
}
Voila! This allows you to instantiate in the getter the troublesome collection.
Now, the other request in the scenario is to inherit the DataContext from the element the collection is attached to. To do that we at least need that the collection and the items in it to be DependencyObjects in order to have a DataContext, but we are going even further: we need to define them as Freezable! The Freezable abstract class requires you to implement a CreateInstanceCore method. Just make the MyItem class inherit Freezable and throw a NotImplementedException in the CreateInstanceCore method. For the collection itself, we need to inherit from another useful class: FreezableCollection<T>. After replacing ObservableCollection with FreezableCollection, the collection and the items have the same DataContext property as the element the property is attached to. An interesting fact here is that Freezable does NOT inherit from FrameworkElement, but it is a DependencyObject and the DataContext property does propagate down through the element tree.
One last interesting thing: the FreezableCollection class implements INotifyCollectionChanged, but explicitly! In order to use the CollectionChanged event you need to cast it in code to INotifyCollectionChanged.
Comments
Sorry, this is clearer: <TextBox Text="Normal textbox" Name="tbMain">     <local:AttachedProperties.MyAttachedCollection>         <local:MyItem Value="Value1" />         <local:MyItem Value="{Binding DataContextValue}" />     </local:AttachedProperties.MyAttachedCollection> </TextBox>
SideriteWell, I don't think you understood the scope of the article. This is about attaching a property to an element, a property which is a collection, which can be populated from the XAML, while the collection and the items themselves inherit from the DataContext of the element. I redid everything I wrote here (because I haven't been working with XAML in a long time) and it worked. Within my code, the XALM usage was this: <local:AttachedProperties.MyAttachedCollection> <local:MyItem Value="Value1" /> <local:MyItem Value="{Binding DataContextValue}" /> </local:AttachedProperties.MyAttachedCollection> Notice that the element is a standard element, that the attached property is a collection which is being populated from the XAML with MyItem objects and that the Value of one of the items is extracted from the DataContext via a Binding. Let me know if you need more details.
SideriteCan you illustrate its usage in an actual, working Xaml? The DependencyProperty itself operates as expected, but now in the Xaml, the collection is not being populated. Whereas before at least it was being populated, even though with all of the attached property populations.
Michael PowellThanks this was exactly what I was looking for!
shawnshaddock