views:

689

answers:

2

I have a class CollectionOfThings. As its name suggests its a simple collection of instances of the Thing class. Thing class has a public default constructor and two simple public get, set properties ID and DisplayName, both are string. CollectionOfThing also has public default constructor.

In XAML I would like to use markup like this:-

<Grid.Resources>
    <local:CollectionOfThings x:Key="Things">
        <local:Thing ID="1" DisplayName="Hello" />
        <local:Thing ID="2" DisplayName="World" />
    <local:CollectionOfThings>
</Grid.Resources>

All is good as long as CollectionOfThings derives from a Collection type. However I want CollectionOfThings to also be a DependencyObject. I thought that's fine creating an implementation of ICollection<T>, INotifyCollectionChanged etc is not that hard. Then I can derive from DependencyObject.

Unfortunately ICollection<T> doesn't cut it for some reason. With ICollection<Thing> I get 'CollectionOfThings does not support Thing as content'. Go back to Collection<Thing> and everything works but leaves me without a DependencyObject implementation.

Suggestions anyone?

+2  A: 

Collections are usually used in xaml so as to define a ContentPropertyAttribute("PropertyName") on the custom class where PropertyName is usually IList or IList<T>.

[ContentProperty(SomeProp)]
class SomeClass
{
    //this is where subitems created in xaml would get added automatically
    public IList<int> SomeProp { get; set; } 
}

However, if a class does not provide a value for the ContentPropertyAttribute, and implements any interface of type IList<T>, IList, ICollection<T> or ICollection, a property of name "Items" (if present, and if of appropriate type, e.g. IList) is detected trough reflection automatically (for the purpose of population trough xaml), and that is the case with the Collection<T> class.

You can even check the class in Reflector to make sure it has no content property defined.

The Collection<T> class has this property

protected IList<T> Items
{
    get
    {
        return _items;
    }
}

and adding something like that to your collection type (even if the property is protected) is enough to have it behave as desired in xaml.

I suppose the behavior is implemented in that way for backward compatibility.

kek444
@kky444: Thanks for the response. However as I stated in my question I'm implementing ICollection<Thing> already yet that doesn't seem to help. I've tried also implementing IList<Thing> and exposing an Items property with a ContentProperty attribute decorating the class. None of this above works. (BTW IList is a ICollection not the other way around).
AnthonyWJones
@AnthonyWJones: okay, to recap, do not set the content property, use IList<Thing>, expose an Items property of type IList<Thing>, and this WILL work, I've just checked it. The xaml editor may argue, that is true, but it will build.
kek444
@kky444: +1 thanks I grasp it now.
AnthonyWJones
+2  A: 

XAML wants System.Collections.IList (the non-generic one!) for collections. If you only implement generic IList<T> interface, it won't cut it.

It also expects to see public Add methods on the class, and derives suitable child types for the collection from arguments of those methods. So the typical approach is to explicitly implement IList - so that its Add(object) method isn't public, and thus isn't picked by XAML parser - and then implicitly implement IList<T> for all child types that you want to support.

The reason why it works for most built-in collection types (List<T>, Collection<T> etc) is because they follow the pattern above and implement both generic and non-generic interfaces.

Pavel Minaev
Thats got it! The vanilla IList was what I was missing, thanks.
AnthonyWJones