Working with collection-typed Dependency Properties, part 2

April 27, 2010

In my last post on this subject, I was trying to track down a good way to avoid setting the default for a collection typed Dependency Property in the constructor. See my the other post as to why this makes the property harder to style. I’ve come up with some interesting ideas, but there are still some interesting quirks with how the Xaml design surfaces treat these collections and the associated styles.

The root problem I was trying to address is that when you have xaml such as this:

<local:Palette x:Name="thePalette">
    <local:Palette.Colors>
        <Color>Blue</Color>
        <Color>Orange</Color>
    </local:Palette.Colors>
</local:Palette>

And you have a Color dependency property that is not initialized in the default value or the constructor but is initialized in the style:

<Setter Property="Colors">
    <Setter.Value>
        <local:ColorCollection>
            <Color>Red</Color>
            <Color>Blue</Color>
        </local:ColorCollection>
    </Setter.Value>
</Setter>

Then, oddly, things will appear to work correctly in the designer. Meanwhile, at runtime, as expected, when the Xaml parser goes to add the colors to the Colors property it will find that the Colors property is null.

Silverlight 4 actually gives a good way to avoid this behavior in the form of the ISupportInitialize event. When we have our control implement this class ISupportInitialize.BeginInit will be called when the Xaml parser starts parsing the element, and ISupportInitialize.EndInit will get called when the Xaml parser finishes parsing the element, before the style is applied. Thus, if we want to ensure that there is an empty local collection for the parser to fill, but not leave the collection as a local value if the parse doesn’t find any local values (so that the style precedence properties can apply), we can do something like this:

        private ColorCollection _initialColors =
            new ColorCollection();

        public void BeginInit()
        {
            if (ReadLocalValue(ColorsProperty) ==
                DependencyProperty.UnsetValue)
            {
                _initialColors.Clear();
                Colors = _initialColors;
            }
        }

        public void EndInit()
        {
            if (GetValue(ColorsProperty) == _initialColors &&
                _initialColors.Count == 0)
            {
                ClearValue(ColorsProperty);
            }
            UpdateColors();
        }

Which will basically set a local value as the Xaml parser begins to parse the element, but if no items are added to the collection, clear the value when done parsing, so that the style values can take precedence.

This seems to work for fixing the runtime problem. However, there seem to be other problems with creating style-able collection properties when it comes to the designer. If you, for example start typing out the Palette as above, the designer will apply its style before you even get to the Colors property, so, when you are adding the items to the Colors property collection, you will be modifying the collection from the default style of the Palette, so the Colors properties all Palette instances will be affected. Fun, huh? So I’m left wondering if its a good idea currently to try and create style-able collection-typed Dependency Properties with the current tool support, given their current behavior.

It would nice if we could somehow indicate to Blend/VS that this is the only correct way to specify the Colors collection in Xaml:

<local:Palette x:Name="thePalette">
    <local:Palette.Colors>
        <local:ColorCollection>
            <Color>Blue</Color>
            <Color>Orange</Color>
        </local:ColorCollection>
    </local:Palette.Colors>
</local:Palette>

But I haven’t come across a way to do this yet….

Advertisements

Proper pattern for defining collection type Dependency Properties?

April 23, 2010

I’ll relate a query that I recently posted on the Silverlight forums here, in case someone reading this has a good answer:

So, we all know that when creating a Dependency Property that has a collection type that setting a new collection instance as the default value in the metadata does not have the desired effect as that instance will be shared between all instances of the class as the default value of that property. So the correct solution is to initialize the collection instance in the constructor, seeĀ http://msdn.microsoft.com/en-us/library/cc903961(VS.95).aspx

But what if you want to set a value for this collection via a style? Or set its default value in the default style? For an example, see the Palette property on the Silverlight Toolkit chart. If we provide a value locally in the constructor, this will take precedence over the value provided in the style correct? Or if we avoid initializing the collection in the constructor, as in the Palette example, then this will not work:

<CustomControl.CollectionProperty>
   <CollectionItem />
   <CollectionItem />
</CustomControl.CollectionProperty>

only this will:

<CustomControl.CollectionProperty>
   <CollectionItemCollection>
       <CollectionItem />
       <CollectionItem />
   </CollectionItemCollection>
</CustomControl.CollectionProperty>

So, if you need a collection property to be stylable you can avoid setting it in the constructor and document that when used in xaml it needs a new collection instance to be instantiated. But what happens when tools like blend blithely create the former xml snippet when you use their collection editor?

Does anyone know a good pattern that allows for all these behaviors?