다음을 통해 공유


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

Comments

  • Anonymous
    October 06, 2006
    I've made a couple of updates to this since I first posted it.  One was a typo in the CollectionOfT type, the other was to add the GenericExtension in the last section of the post.

  • Anonymous
    October 09, 2006
    Hey Mike, I have another question, it seems xaml parser doesn't recognize ParamArrayAttribute, you know, when I define my custom markup extension's constructor this way: public GenericTypeExtension(String typeName, params Type[] typeArguments) {    //... } And I use it in xaml this way: <Label Content="{co:Dictionary, sys:String, sys:Int32}"/> When I do this, I get an exception saying that GenericTypeExtension doesn't have a constructor which accepts three arguments, which means that xaml parser don't treat params the way we anticipate, is this for true? is there any workaround to this problem? or actually the current implementation of WPF prevents me from doing this, if so, I wish this feature can be added in the next version of WPF. Sheva

  • Anonymous
    October 09, 2006
    As an aside, I want to point out a minor mistake you made in your code, you wrote: // Create the conrete type, e.g. Collection<String>        Type concreteType = genericType.MakeGenericType(typeArgumentArray); actually I think when the generic types  are fed with type arguments, they actually becomes constructed types, not concrete types, you know, when you talking about concret types, I always think about its contrary aka abstract types:) Sheva

  • Anonymous
    October 09, 2006
    Regarding parameter arrays, you're right that they aren't supported for markup extensions today, but I'll put it on the wish list for the next version. Regarding "concrete" vs "constructed", you're right.  I updated the text above. Thank you for the comments!

  • Anonymous
    June 07, 2007
    Mike, First, I would just like to say thanks for the useful and insightful code.  It has been quite useful for implementing support of generics in my use of XAML. I have a question regarding how this could be used with the XAMLWriter.  I believe I must use a TypeConverter to convert the property from a List<T> to a ListOfTExtension.  I have attempted to do so by applying the the TypeConverterAttribute to the property I wish to convert.  Alas, the only conversion that the type converter is asked to perform is to a System.String and not to a MarkupExtension. I performed some preliminary investigation and found that if I apply the TypeConverterAttribute to the generic class definition instead of the property in the class then a conversion to a MarkupExtension is requested.  Unfortunately I cannot apply the TypeConverterAttribute to the List<T> class since it is defined in the framework.  As I mentioned I have only performed some preliminary investigation into this issue and will perform a more in depth search over the next couple of days.  I was just interested if you had any insight into this issue or if I am missing something very simple (which I probably am). Thanks in advance for any help or guidance you are able to provide, Cheers, Trevor

  • Anonymous
    June 25, 2007
    Hi Trevor, yes, I see this too.  The one workaround I can think of is to not generate a List<T>, but a subclass that just exists to set the [TypeConverter].  Something like: [TypeConverter(typeof(MyListConverter))] public class MyList : List {    public MyList()    {    } }

  • Anonymous
    November 06, 2007
    Hi Mike, Could you please provide with small example on how to use this with XamlWriter ? How TypeConverters can be used to serialize generic classes ? Thanks in advance, Zohrab.

  • Anonymous
    November 06, 2007
    Hi Mike, Cool ! We can instantiate generic class MyGenericClass<T1,T2>, but how we can set values for Prop1 and Prop2 of MyGenericClass<T1,T2> in XAML ? Thanks,

  • Anonymous
    June 14, 2009
    Hi Mike, This is exactly what I'm looking for; thank you. I'm getting a compile error in the XAML trying to use the DictionaryOfT sample: "The attachable property 'Items' was not found in type 'DictionaryOfT'." Do you have a working sample, or advice on what I'm doing wrong? Thanks, Carl.

  • Anonymous
    October 29, 2009
    I see this error too showing up in the Visual Studio Error List window.  I'm not sure what the cause is of that, but the project actually does build and run successfully. I'm also attaching a project file with the code. Thanks, Mike

  • Anonymous
    September 22, 2010
    Mike, excellent article but... When I try to put into a DataTemplate.DataType:        <DataTemplate>            <DataTemplate.DataType>                    <g:ListOfT TypeArgument="sys:String">                        <sys:String>Hello</sys:String>                        <sys:String>World</sys:String>                    </g:ListOfT>            </DataTemplate.DataType>            <StackPanel Orientation="Horizontal">                <Label Content="Generic" />            </StackPanel>        </DataTemplate> I get the following error: A key for a dictionary cannot be of type 'GenericsAndXamlTypeResolver.ListOfTExtension'. Only String, TypeExtension, and StaticExtension are supported. Is there a way to write this in a string format so that TypeExtension can recognize these generic types ? Thank you.

  • Anonymous
    September 27, 2012
    Good article and good sample project