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. ShevaAnonymous
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:) ShevaAnonymous
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, TrevorAnonymous
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, MikeAnonymous
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