2 out of 2 rated this helpful - Rate this topic

Collection-Type Dependency Properties

Silverlight

This topic provides guidance and suggested patterns for how to implement a dependency property where the type of the property is a collection type.

This topic assumes that you understand dependency properties from the perspective of a consumer of existing dependency properties on Silverlight classes, and that you have read Dependency Properties Overview and Custom Dependency Objects and Dependency Properties.

Collection-type dependency properties are relatively rare in the Silverlight core libraries. In most cases, it is adequate to define the collection such that the item type of the collection is strongly typed to be some kind of DependencyObject, but that the collection property itself is implemented as a conventional CLR property. This is because collections are not necessarily suitable to a number of the typical scenarios where dependency properties are involved:

  • You do not typically animate a collection.

  • You do not typically need to support prepopulating the items in a custom collection property by using a style or a template.

  • Binding to collections is a major scenario, but a collection does not need to be held by a dependency property in order to be a binding source. The Silverlight binding engine has extensive logic that can use standard CLR collections for sources. For binding targets, it is more typical to use existing ItemsControl and DataTemplate principles for items support, or to use view-model patterns. For more information on binding to and from collections, see Data Binding.

  • Notifications for collection changes are better addressed through dedicated CLR interface conventions INotifyPropertyChanged and/or INotifyCollectionChanged, or by deriving the collection type from ObservableCollection<T>.

Nevertheless, scenarios for collection-type dependency properties do exist. The remainder of this topic provides some guidance on how to implement a collection-type dependency property.

For a dependency property in general, the implementation pattern is to define a CLR property wrapper, where that property is backed by a DependencyProperty identifier rather than a field or other construct. You follow this same pattern when you implement a collection-type property. However, if the type that is contained in the collection is itself a DependencyObject derived class, the pattern becomes more complex.

When you create a dependency property, you do not specify the property default value as the initial field value. Instead, you specify the default value through the dependency property metadata. If your property is a reference type, the default value specified in dependency property metadata is not a default value per instance; instead it is a default value that applies to all instances of the type. Therefore, you must be careful to not use the singular static collection defined by the collection property metadata as the working default value for newly created instances of your type. Instead, you must make sure that you deliberately set the collection value to a unique (instance) collection as part of your class constructor logic. Otherwise, you will create an unintentional singleton class.

Consider the following example. The following section of the example shows the definition for an Aquarium class. The class defines the collection-type dependency property AquariumObjects, which uses the generic List<T> type with a FrameworkElement type constraint. In the Register(String, Type, Type, PropertyMetadata) call for the dependency property, the metadata establishes the default value to be a new generic List<T>.


public class Aquarium : DependencyObject
{
    private static readonly DependencyProperty AquariumContentsProperty =
        DependencyProperty.Register(
          "AquariumContents",
          typeof(List<FrameworkElement>),
          typeof(Aquarium),
          new PropertyMetadata(new List<FrameworkElement>())
        );
    public List<FrameworkElement> AquariumContents
    {
        get { return (List<FrameworkElement>)GetValue(AquariumContentsProperty); }
        set { SetValue(AquariumContentsProperty, (List<FrameworkElement>)value); }
    }
    public Aquarium()
        : base() {}
}


However, if you leave the code as shown, that single list default value is shared for all instances of Aquarium. If you ran the following test code, which is intended to show how you would instantiate two separate Aquarium instances and add a single different Fish to each of them, you would see a surprising result: instead of each collection having a count of one, each collection has a count of two!


Aquarium myAq1 = new Aquarium();
Aquarium myAq2 = new Aquarium();
Fish f1 = new Fish();
Fish f2 = new Fish();
myAq1.AquariumContents.Add(f1);
myAq2.AquariumContents.Add(f2);


This is because each Aquarium added its Fish to the default value collection, which resulted from a single constructor call in the metadata and is therefore shared between all instances. This situation is almost never what you want.

To correct this problem, you must reset the collection dependency property value to a unique instance, as part of the class constructor call.


public Aquarium() : base()
{
    SetValue(AquariumContentsProperty, new List<FrameworkElement>()); 
}


Defining the collection as a dependency property does not automatically provide change notification for the items in the collection by virtue of the property system invoking the PropertyChanged callback. If you want notifications for collections or collection items, for example for a data binding scenario, you should implement the interfaces INotifyPropertyChanged and/or INotifyCollectionChanged. For more information, see the "Change Notification" section of Data Binding.

Did you find this helpful?
(1500 characters remaining)
Community Content Add
Annotations FAQ
Initializing the Collection Beyond the Default Value
I would rather prefer to have a PropertyMetadata constructor which takes the initializer action (just like Lazy<T> takes action which gets called later during a lazy-load). Like this: $0new PropertyMetadata(() => new List<FrameworkElement>());$0
Initializing the Collection Beyond the Default Value
The solution described in the section "Initializing the Collection Beyond the Default Value" is only a partial one, and it points to a hole in the dependency property system: If you call SetValue in the class constructor, you will establish a local value for the collection dependency property, and this will have higher precedence than any value set via a Style. This means you can't use a Style to set a custom collection dependency property.

What's missing in the dependency property system is a way to set a per-instance default for the dependency property, so that instead of calling SetValue in the class constructor, you could call (for example) SetDefaultValue. This per-instance default would then have a lower precedence than a value set any other way (except for any static default value), and would elegantly solve this problem.

Absent that, the best way we've found to allow your collection property to be initialized via a Style (and still support all other scenarios) is to follow the instructions in this article, and then create another dependency property, called (in this case) DefaultAquariumContents, and in its PropertyChangedCallback, copy the collection items to the AquariumContents collection. This DefaultAquariumContents property must then only be used to set the collection from a Style. This isn't an elegant solution, but it gets the job done.