Limited generics support in Xaml

In a post to the WPF forum, Zhou Yong had the idea to use a MarkupExtension to make it possible to create a generic dictionary (Dictionary<K,V>) from Xaml. It’s a cool idea, so I played with it a bit, with the result shown below. The end result is that you can do the following, for example, where a ListBox is bound to a Collection<String>:

 

<ListBox xmlns:generic="clr-namespace:MyProject">

  <ListBox.ItemsSource>

    <generic:CollectionOfT TypeArgument="sys:String"> <!-- Create a Collection<String> -->

      <sys:String>Hello</sys:String>

      <sys:String>World</sys:String>

    </generic:CollectionOfT>

  </ListBox.ItemsSource>

</ListBox>

 

… or the following, where a MyGenericType<string, int> is instantiated:

 

<Generic TypeName="mytpes:MyGenericType">

  <x:Type TypeName="sys:String" />

  <x:Type TypeName="sys:Int32" />

</Generic>

But First, Some Background on Generics support in Xaml

 

For the most part, Xaml does not support generics. The one exception to that is that generics are supported on the root tag of your Xaml, if you’re compiling the Xaml. (Therefore, for example, it’s not supported if you’re loading Xaml directly into Internet Explorer.) Here’s an example of where a generic type is supported:

 

<PageFunction

    x:Class="CSharp.MyPageFunction"

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    x:TypeArguments="sys:String"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

   Title="VanillaPageFunction"

    >

  <Grid>

  </Grid>

</PageFunction>

 

… where PageFunction is defined as:

 

public class PageFunction<T> : PageFunctionBase

 

Note that the x:TypeArguments attribute is how you specify type arguments to the generic type in Xaml. So that Xaml is equivalent to:

 

public partial class MyPageFunction : PageFunction<String>

{

    public MyPageFunction()

    {

      ...

    }

    ...

}

 

 

A Helper To Create Collection<T> in Xaml

 

But working with Zhou’s approach of using markup extensions, here’s a markup extensions that helps with the case of a really common generic type: Collection<T> What we’ll end up with is a <CollectionOfT> markup extension.

 

First, here’s a base class that provides some common functionality (we’ll be using this again later for List<T>, etc.):

 

//

// MarkupExtension that is base for an extension that creates

// Collection<T>, List<T>, or Dictionary<T>.

// (CollectionType is either IList or IDictionary).

public abstract class CollectionOfTExtensionBase<CollectionType> : MarkupExtension

    where CollectionType : class

{

    public CollectionOfTExtensionBase(Type typeArgument)

    {

        _typeArgument = typeArgument;

    }

    // Default the collection to typeof(Object)

    public CollectionOfTExtensionBase()

        : this(typeof(Object))

    {

    }

    // Items is the actual collection we'll return from ProvideValue.

    protected CollectionType _items;

    public CollectionType Items

    {

        get

        {

            if (_items == null)

            {

        Type collectionType = GetCollectionType(TypeArgument);

                _items = Activator.CreateInstance(collectionType) as CollectionType;

            }

            return _items;

        }

    }

    // TypeArgument is the "T" in e.g. Collection<T>

    private Type _typeArgument;

    public Type TypeArgument

    {

        get { return _typeArgument; }

        set

        {

            _typeArgument = value;

            // If the TypeArgument doesn't get set until after

            // items have been added, we need to re-create items

            // to be the right type.

            if (_items != null)

            {

                object oldItems = _items;

                _items = null;

                CopyItems(oldItems);

            }

        }

    }

    // Default implementation of CopyItems that works for Collection/List

    // (but not Dictionary).

    protected virtual void CopyItems( object oldItems)

    {

        IList oldItemsAsList = oldItems as IList;

        IList newItemsAsList = Items as IList;

        for (int i = 0; i < oldItemsAsList.Count; i++)

        {

            newItemsAsList.Add(oldItemsAsList[i]);

        }

    }

    // Get the generic type, e.g. typeof(Collection<>), aka Collection`1.

    protected abstract Type GetCollectionType(Type typeArgument);

    // Provide the collection instance.

    public override object ProvideValue(IServiceProvider serviceProvider)

    {

        return _items;

    }

}

 

With that base class in place, here’s the implementation of the CollectionOfT markup extension:

 

//

// MarkupExtension that creates a Collection<T>

//

[ContentProperty("Items")]

public class CollectionOfTExtension : CollectionOfTExtensionBase<IList>

{

    protected override Type GetCollectionType(Type typeArgument)

    {

        return typeof(Collection<>).MakeGenericType(typeArgument);

    }

}

 

And again, here’s that markup extension in action:

 

<generic:CollectionOfT TypeArgument="sys:String"> <!-- Create a Collection<String> -->

  <sys:String>Hello</sys:String>

  <sys:String>World</sys:String>

</generic:CollectionOfT>

 

A Helper to create List<T>, ObservableCollection<T> in Xaml

 

Similarly, here are ListOfT and ObservableCollectionOfT markup extensions for creating List<T> and ObservableCollection<T>:

 

//

// MarkupExtension that creates a List<T>

//

[ContentProperty("Items")]

public class ListOfTExtension : CollectionOfTExtensionBase<IList>

{

    protected override Type GetCollectionType( Type typeArgument )

    {

        return typeof(List<>).MakeGenericType(typeArgument);

    }

}

//

// MarkupExtension that creates an ObservableCollection<T>

//

[ContentProperty("Items")]

public class ObservableCollectionOfTExtension : CollectionOfTExtensionBase<IList>

{

    protected override Type GetCollectionType(Type typeArgument)

    {

        return typeof(ObservableCollection<>).MakeGenericType(typeArgument);

    }

}

 

A Helper to create Dictionary<T> in Xaml

 

Dictionary<K,V> is all that’s left to do. But Dictionary is a bit more complicated, because it has two type arguments, one for the key type and one for the value type. And there I cheated a little; I only put in support for the value type argument, and left the key type as always Object.

 

//

// MarkupExtension that creates an Dictionary<Object,T>

// (Items cannot be the [ContentProperty]).

//

public class DictionaryOfTExtension : CollectionOfTExtensionBase<IDictionary>

{

    protected override Type GetCollectionType(Type typeArgument)

    {

        return typeof(Dictionary<,>).MakeGenericType(typeof(Object), typeArgument);

    }

    protected virtual void CopyItems(IDictionary oldItems)

    {

        IDictionary oldItemsAsDictionary = oldItems as IDictionary;

        IDictionary newItemsAsDictionary = Items as IDictionary;

        foreach( DictionaryEntry entry in oldItemsAsDictionary )

        {

            newItemsAsDictionary[entry.Key] = oldItemsAsDictionary[entry.Key];

        }

    }

}

 

The other complication with Dictionary is that the Items property can’t be the [ContentProperty]. So when you use it in Xaml, you have to specify the explicit <DictionaryOfT.Items> tag. So usage ends up looking like:

 

<generic:DictionaryOfT TypeArgument="sys:String"> <!-- Dictionary<Object,String -->

  <generic:DictionaryOfT.Items>

    <sys:String x:Key="String1">Hello</sys:String>

    <sys:String x:Key="String2">World</sys:String>

  </generic:DictionaryOfT.Items>

</generic:DictionaryOfT>

 

 

A Helper to Create Other Generic Types

 

Finally, this is a more general extension, that can create any generic type. For example, given this class:

 

public class MyGenericClass<T1,T2>

{

    private T1 _prop1;

    public T1 Prop1

    {

        get { return _prop1; }

        set { _prop1 = value; }

    }

    private T2 _prop2;

    public T2 Prop2

    {

        get { return _prop2; }

        set { _prop2 = value; }

    }

}

 

… and this “Generic” markup extension:

 

//

// Markup extension that creates an object from a constructed generic type.

//

 [ContentProperty("TypeArguments")]

public class GenericExtension : MarkupExtension

{

    // The collection of type arguments for the generic type

    private Collection<Type> _typeArguments = new Collection<Type>();

    public Collection<Type> TypeArguments

    {

        get { return _typeArguments; }

    }

     

    // The generic type name (e.g. Dictionary, for the Dictionary<K,V> case)

    private string _typeName;

    public string TypeName

    {

  get { return _typeName; }

        set { _typeName = value; }

    }

    // Constructors

    public GenericExtension()

    {

    }

    public GenericExtension(string typeName )

    {

        TypeName = typeName;

    }

    // ProvideValue, which returns an object instance of the constructed generic type

    public override object ProvideValue(IServiceProvider serviceProvider)

    {

        IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

        if (xamlTypeResolver == null)

            throw new Exception("The Generic markup extension requires an IXamlTypeResolver service provider");

        // Get e.g. "Collection`1" type

        Type genericType = xamlTypeResolver.Resolve(

            _typeName + "`" + TypeArguments.Count.ToString() );

        // Get an array of the type arguments

        Type[] typeArgumentArray = new Type[ TypeArguments.Count ];

        TypeArguments.CopyTo(typeArgumentArray, 0);

        // Create the conrete type, e.g. Collection<String>

        Type constructedType = genericType.MakeGenericType(typeArgumentArray);

        // Create an instance of that type

        return Activator.CreateInstance(constructedType);

    }

}

 

... you can create an instance of MyGenericClass, such as MyGenericClass<string,int>, from Xaml with something like:

 

<generic:Generic TypeName="local:MyGenericClass" >

  <x:Type TypeName="sys:String" />

  <x:Type TypeName="sys:Int32" />

</generic:Generic>

 

GenericsAndXamlTypeResolver.zip