Introduction
Declaring generic types in XML is something which is sadly lacking from the current incarnation of WPF. Although they are fully supported[^] in XML 2009[^], you cannot use these features in markup-compiled XAML or BAML.
Background
In .NET 3.5, it was relatively simple to create a markup extension for generic types - for example, this implementation on the MSDN forums[^] worked perfectly.
Unfortunately, in .NET 4.0, the IXamlTypeResolver
service was updated to reject type names containing "`
" - the separator between the type name and type argument count for generic types.
Mayur Shah's tip[^] avoids this by side-stepping the XAML type resolver. Whilst this works at runtime, it will fail at design-time. It also makes the type declaration unnecessarily long, since you can't use XML namespaces.
The Solution
The core of the solution involves using two services: IXamlNamespaceResolver
and IXamlSchemaContextProvider
. This is more complicated than the .NET 3.5 solution, but achieves the same result:
var resolver = GetRequiredService<IXamlNamespaceResolver>(serviceProvider);
var schema = GetRequiredService<IXamlSchemaContextProvider>(serviceProvider);
var xamlNs = resolver.GetNamespace(namespaceName);
string genericTypeName = string.Format(CultureInfo.InvariantCulture,
"{0}`{1:D}", typeName, typeArguments.Length);
var xamlTypeName = new XamlTypeName(xamlNs, genericTypeName);
Type genericType = schema.SchemaContext.GetXamlType(xamlTypeName).UnderlyingType;
Type finalType = genericType.MakeGenericType(typeArguments);
Error-handling omitted for brevity.
Using the Code
The attached converter can be used in several ways:
<!---->
<SomeTag Type="{nbs:GenericType ge:Queue, sys:String}"/>
<SomeTag Type="{nbs:GenericType ge:SortedList, sys:String, sys:Int32}"/>
<!---->
<SomeTag Type="{nbs:GenericType BaseTypeName=scg:Dictionary,
TypeArguments={StaticResource types}}"/>
<!---->
<SomeTag Type="{nbs:GenericType BaseTypeName=scg:KeyValuePair,
TypeArguments='sys:String, sys:Int32'}"/>
<!---->
<SomeTag>
<SomeTag.Type>
<nbs:GenericType BaseTypeName="ge:SortedList"
TypeArguments="{StaticResource types}"/>
</SomeTag.Type>
</SomeTag>
<!---->
<SomeTag>
<SomeTag.Type>
<nbs:GenericType BaseTypeName="ge:SortedDictionary">
<x:Type TypeName="sys:String"/>
<!---->
<nbs:GenericType BaseTypeName="scg:List">
<x:Type TypeName="sys:Int32"/>
</nbs:GenericType>
</nbs:GenericType>
</SomeTag.Type>
</SomeTag>
Points of Interest
The attached class library is decorated with the XmlnsPrefix
and XmlnsDefinition
attributes. This makes it easier to use the namespace in a XAML document - instead of:
xmlns:nbs="clr-namespace:XamlGenericTypeExtension;assembly=XamlGenericTypeExtension"
we can simply use:
xmlns:nbs="urn:nbs-wpf-generic"
This has the added advantage that multiple namespaces can be mapped to the same XML namespace.
History
- 6th February, 2015 - Initial version
Further Reading