Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Populate IEnumerable through XAML neatly?

Tags:

xaml

.net-4.0

I am trying to populate a dependency property from XAML. The dependency property is an IEnumerable<KeyAction> where KeyAction is a complex type.

<loc:MyType.KeyActions>
    <loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
    <loc:KeyAction Action="Hide" Key="Escape" />
</loc:MyType.KeyActions>

Now, this causes the KeyAction property to be 'declared twice' because XAML interprets each item as a candidate for the property, instead of items in a list.

For this to work, it would need to look something like:

<loc:CompletionPopupView.KeyActions>
    <sys:List`KeyAction>
        <loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
        <loc:KeyAction Action="Hide" Key="Escape" />
    </sys:List`KeyAction>
</loc:CompletionPopupView.KeyActions>

And I'd need to add namespaces, and the generic syntax is probably even more terrible, if even doable. Is there any way to make the first example work?

like image 879
Max Avatar asked Apr 29 '12 21:04

Max


1 Answers

There are two different ways you can make this work. One looks exactly like your first example, but requires changes to your class and doesn't operate quite the way you're asking for (which might or might not be a problem to you); the other acts exactly like you're asking but is a little more verbose. You can decide which one is better for you.

Option 1: Adding to a collection

XAML has magic shorthand syntax for initializing collections, using the exact syntax you show in your first example. However, it only works if the property type implements IList. (Yes, that's really the non-generic IList -- not normally a big deal though, all the generic collections that ship with .NET implement both IList<T> and IList.)

So you could do your first example, but only if your KeyActions property was declared as a type that implements IList. For example, you could change your property to:

public ObservableCollection<KeyAction> KeyActions { get {...} }

And then just put multiple child elements inside your property, and it would add them to the collection:

<loc:MyType.KeyActions>
    <loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
    <loc:KeyAction Action="Hide" Key="Escape" />
</loc:MyType.KeyActions>

This isn't quite like what you asked for though, because the XAML doesn't create a new collection -- it adds to the existing collection. So if you choose this option, your class needs to instantiate the collection in its constructor (KeyActions = new ObservableCollection<KeyAction>();), so that you don't get a null reference exception when you start trying to Add elements to it.

Option 2: Creating a new collection

If you do need to create a new collection and assign it to your property, that's doable too. Unfortunately, XAML2006 (the flavor still used by WPF) only supports generics on the root element of the entire document -- so you can't instantiate a List<T> and assign it to a property.

But that's okay; you can use the same workaround that WPF does. Just create your own non-generic class that descends from a generic list.

public class KeyActionCollection : ObservableCollection<KeyAction> {}

Then you can instantiate it in XAML:

<loc:CompletionPopupView.KeyActions>
    <loc:KeyActionCollection>
        <loc:KeyAction Action="Show" Key="Space" Modifiers="LeftCtrl" />
        <loc:KeyAction Action="Hide" Key="Escape" />
    </loc:KeyActionCollection>
</loc:CompletionPopupView.KeyActions>

If you choose this option, you can keep your property declared as IEnumerable<KeyAction> if you wish. The custom list is only necessary to populate the list from XAML.

You could even combine both approaches: make the property read/write and of type KeyActionCollection, instantiate it in your constructor, and then XAML can choose whether to add to the existing collection using the shorthand syntax, or create a new collection; and runtime code could make the same choice.

like image 180
Joe White Avatar answered Oct 26 '22 05:10

Joe White