.NET (290) administrative (42) Ajax (42) AngularJS (1) ASP.NET (144) bicycle (2) books (178) browser (8) C# (128) cars (1) chess (27) CodePlex (10) Coma (8) database (46) deployment (3) Entity Framework (2) essay (109) flash/shockwave (2) flex (1) food (3) friend (2) game (20) idea (5) IIS (8) javascript (81) LInQ (2) Linux (6) management (4) manga (43) misc (663) mobile (1) movies (88) MsAccess (1) murder (2) music (65) mysql (1) news (98) permanent (1) personal (66) PHP (1) physics (2) picture (308) places (12) politics (13) programming (495) rant (118) religion (3) science (40) Sharepoint (3) software (57) T4 (2) technology (11) Test Driven Development (4) translation (2) VB (2) video (100) Visual Studio (44) web design (45) Windows API (8) Windows Forms (3) Windows Server (4) WPF/Silverlight (63) XML (11)

Thursday, December 16, 2010

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:

public static DependencyProperty MyAttachedCollectionProperty =
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.

1 comment:

shawnshaddock said...

Thanks this was exactly what I was looking for!