XAML Custom Classes & Collection Properties
I am trying to define a custom class (hierarchy) to be used in XAML. I've just started with the root of my class hiearchy, and I'm running into problems.
I'm not sure if I'm doing this the right away, so let me provide a bit of context. My root class inherits from ModelVisual3D (as well as my own interfaces), since it is meant to be inserted under a Viewport3D in a XAML file. I do nothing with UserControl. That didn't seem to accomplish what I wanted (i.e., add my root object in various place in a XAML 3D scene hiearchy), but I may be wrong.
Anyway, I ran into problems when trying to add a collection property. My collection is based on KeyedCollection<>. It's exposed as a read-only property of my root object. However, the compiler complains:
'children' property is a read-only IEnumerable property, which means that 'mynamespace.Root' must implement IAddChild.
I can solve that problem by making the collection a read-write property, but I don't understand why. The collection object is created in Root's constructor, so why am I required to make the property read-write? I was following the advice fromhttp://www.myxaml.com/wiki/ow.asp?WritingXamlFriendlyAssemblies, which I liked (I suppose that it *is* very dated...)
The next issue is that I want my XAML to look like this:
<Root x:Class="RootExample.MyRootExample"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="clr-namespace:myNamespace;assembly=ImplementationRoot" >
<children>
<Child id="cell1" />
</children>
</Root>
However, I get a compile time error that children doesn't exist. Instead, I need to do this:
<Root x:Class="RootExample.MyRootExample"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="clr-namespace:myNamespace;assembly=ImplementationRoot" >
<Root.children>
<Child id="cell1" />
</Root.children>
</Root>
Why do I need to use the Root prefix, and can that be avoided? Again I was hoping to achieve what was described athttp://www.myxaml.com/wiki/ow.asp?WritingXamlFriendlyAssemblies, where they don't need to use a prefix.
Thanks!
>> Why do I need to use the Root prefix, and can that be avoided?
Root isn't really a prefix (at least it's not analogous to an xmlns prefix). It is the qualifying element in a property element syntax.
If you really want the syntax:
<Root>
<Children>
<Child/>
</Children>
</Root>
... you'd actually have to go through a lot of trouble (so much trouble that I'd rather not illustrate it). More common in XAML would be:
<Root>
<Child/>
<Child/>
</Root>
To do this, you want to apply a ContentPropertyAttribute to your Root class, naming the Children property as the ContentProperty. Declaring a ContentProperty obviates the property element syntax for exactly one property per type and helps clean up the markup model.
As far as your collection property of type KeyedCollection - I haven't investigated thoroughly, but why not use IDictionary instead? I know for certain that XAML supports IDictionary collections. For keyed-collection support in XAML you will definitely need to either reuse the x:Key markup extension or devise your own means of keying collection elements. ResourceDictionary is a XAML-ready class that uses x:Key and IDictionary to hold arbitrary (object-typed) keyed values, but that class might have too many special behaviors already to use as a base for custom collection properties. However you could still follow its lead and implement IDictionary and use the key value inputs from x:Key. If you wanted stronger typing you could enforce it as part of your indexer implementation.
Thanks Wolf.
I need multiple collection properties on my Root object, so I guess that means I'll stick with <Root.Children> then.
Also, I use KeyedCollection<> because I need both index access and hash-key access to items in the collection. It all seems to compile and run fine without doing anything extra so far.
The weirdness described in my original post seems to be due to my exposing an IList<> interface for my collection instead of the KeyedCollection<> itself. This seems to happen whether I use List<> or KeyedCollection<> as the implementation. I don't understand why.
If I expose IList<> as a read only property (regardless of whether I use List<> or KeyedCollection<> internally), then I get that compile-error mentioned above:
'children' property is a read-only IEnumerable property, which means that 'mynamespace.Root' must implement IAddChild.
If I expose IList<> as a read-write property (regardless of whether I use List<> or KeyedCollection<> internally), then I get the following run-time exception:
Object of type 'myNamespace.Child' cannot be converted to type 'System.Collections.Generic.IList`1[myNamespace.IChild]
However, if I expose the List<> or the KeyedCollection<> itself as a read-only property, then it all works fine. Why?
Thanks!
Well, I've investigated, and I can repro your IList<T> typed property results in the cryptic IAddChild compile error.
But I can't answer your "why" question, it is a mystery to me too.
For XAML collection support, it all boils down to the parser calling this.Add(object) for the element children. It might be that because KeyedCollection supports both IList AND IDictionary, the XAML compiling code gets confused about which casting of Add to call. It doesn't make sense that compiling would get confused specifically BECAUSE you gave it a more direct interface cast, but that's my best guess.
I'm also able to repro your runtime exception. That exception is indicative that the compiler totally missed that the property is supposed to be a collection at all; it's trying to add the collection to itself, whereas a recognized collection would not require the collection property to specify the (already existing and implicit) collection under the property. If my strange theory about how the compiler or loader casts stuff is correct, that could actually explain this behavior too.
So, as far as casting collection properties to IList<constraint> ... how about .... Don't Do That ... ?
Can you get away with just using your custom colllection type that is a KC<K,V> underneath?