How To Get ParserContext?
- Currently I am writing a custom MarkupExtension, and I am running into an issue that I want to access all xml namespaces to clr namespaces mapping dictionary under ParserContext.XmlnsDictionary property, in the ProvideValue method I do something like this:
the code always run into an exception, so how can I reference the ParserContext within a MarkupExtension? should I manually parse those mapping info myself?ParserContext context = serviceProvider.GetService(typeof(ParserContext)) as ParserContext;
if (context == null)
{throw new InvalidOperationException("ParserContext is null");
}
Sheva
解答
That should work, but note that the runtime namespace for Dictionary is System.Collections.Generic (with an "s").
As an example, here's some markup that creates a single-argument generic type (here it creates a Collection<string>):
<Window x:Class="GenericsAndXamlTypeResolver.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GenericsAndXamlTypeResolver" Height="300" Width="300"
xmlns:co="clr-namespace:System.Collections.ObjectModel;assembly=mscorlib"
xmlns:local="clr-namespace:GenericsAndXamlTypeResolver"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<Grid>
<Button Content="{local:GenericType1 co:Collection, sys:String}"/>
</Grid>
</Window>
... and here's the code for the markup extension:
namespace GenericsAndXamlTypeResolver
{
public class GenericType1 : MarkupExtension
{
string _typeName;
Type _typeArgument;
public GenericType1(string typeName, Type typeArgument)
{
_typeName = typeName;
_typeArgument = typeArgument;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
Type type = xamlTypeResolver.Resolve(_typeName + "`1");
return type.MakeGenericType(_typeArgument);
}
}
}
It's too bad that it's been difficult to debug. Usually, when you get a XamlParseException, you can look at the inner exceptions ("view details" in Visual Studio) to see what the original exception was.
Now try #3 at your scenario. Here's the markup extension to produce the object. Note that it has a [ContentProperty] to make the second syntax work, and a type converter to make the third syntax work:
[ContentProperty("TypeArguments")]
public class GenericExtension : MarkupExtension
{
// Type arguments. This is read/write so that it can be
// set in Xaml attribute syntax with a type converter.
private Collection<Type> _typeArguments = new Collection<Type>();
[TypeConverter(typeof(TypeCollectionConverter))]
public Collection<Type> TypeArguments
{
get { return _typeArguments; }
set { _typeArguments = value; }
}
// 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 the concrete object of the 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 concreteType = genericType.MakeGenericType(typeArgumentArray);
// Create an instance of that type
return Activator.CreateInstance(concreteType);
}
}
And here's the referenced type converter:
//
// Type converter that converts a string which is a list
// of Xaml type names, e.g. "sys:String,sys:Int32", to a Collection<Type>.
//
public class TypeCollectionConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
IXamlTypeResolver xamlTypeResolver = context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
if (sourceType == typeof(Type) && xamlTypeResolver != null)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
IXamlTypeResolver xamlTypeResolver
= context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
string[] stringArray = (value as string).Split(',');
Collection<Type> types = new Collection<Type>();
for( int i = 0; i < stringArray.Length; i++ )
types.Add( xamlTypeResolver.Resolve( stringArray
.Trim()) );return types;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
return base.ConvertTo(context, culture, value, destinationType);
}
}
And here's some sample Xaml that's supported by these:
<cc:Generic TypeName="co:Dictionary">
<cc:Generic.TypeArguments>
<cc:CollectionOfT TypeArgument="sys:Type">
<x:Type TypeName="sys:String"/>
<x:Type TypeName="sys:Int32"/>
</cc:CollectionOfT>
</cc:Generic.TypeArguments>
</cc:Generic>
<cc:Generic TypeName="co:Dictionary">
<x:Type TypeName="sys:String"/>
<x:Type TypeName="sys:Int32"/>
</cc:Generic>
<Label Content="{cc:Generic TypeName=co:Dictionary, TypeArguments='sys:String, sys:Int32'}"/>
You're right that parameter arrays aren't supported for markup extensions today, but I'll put it on the wish list for the next version.
One way you could accomplish that syntax, though, would be to make the type arguments a string, rather than a Type[]. So the markup extension would look like:
<Label Content="{co:Dictionary, 'sys:String, sys:Int32' }"/>
... Then, in ProvideValue, you can parse the string into a Type[], just like the TypeCollectionConverter above.
所有回覆
The ParserContext isn't available to markup extensions or type converters. The service provider does provide an IXamlTypeResolver service, though, which doesn't give all of the mappings, but does map a "prefix:Name" to a System.Type instance. For example, given this code:
namespace Test
{
public class Foo
{
...
}
}... and this markup:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:test="clr-namespace:Test"
>
<Button Content="{SomeExtension TestType}"/>
</Window>
... the SomeExtension implementation could call IXamlTypeResolver.Resolve("test:Foo") to get back a typeof(Foo) object.
- Actually I am writing a MarkupExtension to enable the xaml to accept generic type, I've use IXamlTypeResolver to resolve the generic type name, what i do is something like this:
If I pass in "co:Dictionary" as the genericTypeName, and two String types as the TypeArguments, "co" prefix actually points to "System.Collection.Generic" clr namespace, and use the IXamlTypeResolver to parse the typeName, the genericType field is always null. I guess IXamlTypeResolver doesn't know how to parse generic type name, that's why I plan to do it myself, but one thing I need to know is which clr namespace prefix "co" points to, that's why I need to access the ParserContext to retrieve the xmlns table, if ParserContext is not available for MarkupExntension and TypeConverter, then in which context we can get the reference to it?IXamlTypeResolver resolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
if (resolver == null)
{throw new InvalidOperationException("Context is not available now");
}String typeName = genericTypeName + "`" + typeArguments.Count.ToString();
genericType = resolver.Resolve(typeName);if (genericType == null)
{throw new InvalidOperationException("genericType is null");
}
Sheva That should work, but note that the runtime namespace for Dictionary is System.Collections.Generic (with an "s").
As an example, here's some markup that creates a single-argument generic type (here it creates a Collection<string>):
<Window x:Class="GenericsAndXamlTypeResolver.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GenericsAndXamlTypeResolver" Height="300" Width="300"
xmlns:co="clr-namespace:System.Collections.ObjectModel;assembly=mscorlib"
xmlns:local="clr-namespace:GenericsAndXamlTypeResolver"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<Grid>
<Button Content="{local:GenericType1 co:Collection, sys:String}"/>
</Grid>
</Window>
... and here's the code for the markup extension:
namespace GenericsAndXamlTypeResolver
{
public class GenericType1 : MarkupExtension
{
string _typeName;
Type _typeArgument;
public GenericType1(string typeName, Type typeArgument)
{
_typeName = typeName;
_typeArgument = typeArgument;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
Type type = xamlTypeResolver.Resolve(_typeName + "`1");
return type.MakeGenericType(_typeArgument);
}
}
}
- Thanks Mike, you code really enlightens me.
Sheva Cool idea, by the way. In fact, I played with it a bit more to create some markup extensions for the basic collection types, posted here.
Also, there was a missing line in the sample code of my last post, corrected here in yellow:
public class GenericType1 : MarkupExtension
{
string _typeName;
Type _typeArgument;
public GenericType1(string typeName, Type typeArgument)
{
_typeName = typeName;
_typeArgument = typeArgument;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
IXamlTypeResolver xamlTypeResolver
= serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
Type type = xamlTypeResolver.Resolve(_typeName + "`1"); // Gets generic Collection<T>
type = type.MakeGenericType(_typeArgument); // Gets concrete Collection<String>
return Activator.CreateInstance(type);
}
}
- Hi Mike, I want to add some properties to my markup extension, what I really want to get is some xaml syntax like followings:
syntax one:
<cc:Generic TypeName="co:Dictionary">
<cc:Generic.TypeArguments>
<x:Type TypeName="sys:String"/>
<x:Type TypeName="sys:Int32"/>
</cc:Generic.TypeArguments>
</cc:Generic>
syntax two:<cc:Generic TypeName="co:Dictionary">
<x:Type TypeName="sys:String"/>
<x:Type TypeName="sys:Int32"/>
</cc:Generic>
syntax three:
<Label Content="{cc:Generic TypeName=cc:Collection, TypeArguments=[sys:String, sys:Int32]}"/>
I wanna have those three syntax all work well, I've tried pretty hard to come up with some code to implement it, but whenever I test the code, I always got XamlParseException, what's more, this exception ain't give me any detailed info about what I am doing wrong, you know, testing custom markup extension is reallly really painful.
Sheva It's too bad that it's been difficult to debug. Usually, when you get a XamlParseException, you can look at the inner exceptions ("view details" in Visual Studio) to see what the original exception was.
Now try #3 at your scenario. Here's the markup extension to produce the object. Note that it has a [ContentProperty] to make the second syntax work, and a type converter to make the third syntax work:
[ContentProperty("TypeArguments")]
public class GenericExtension : MarkupExtension
{
// Type arguments. This is read/write so that it can be
// set in Xaml attribute syntax with a type converter.
private Collection<Type> _typeArguments = new Collection<Type>();
[TypeConverter(typeof(TypeCollectionConverter))]
public Collection<Type> TypeArguments
{
get { return _typeArguments; }
set { _typeArguments = value; }
}
// 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 the concrete object of the 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 concreteType = genericType.MakeGenericType(typeArgumentArray);
// Create an instance of that type
return Activator.CreateInstance(concreteType);
}
}
And here's the referenced type converter:
//
// Type converter that converts a string which is a list
// of Xaml type names, e.g. "sys:String,sys:Int32", to a Collection<Type>.
//
public class TypeCollectionConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
IXamlTypeResolver xamlTypeResolver = context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
if (sourceType == typeof(Type) && xamlTypeResolver != null)
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
IXamlTypeResolver xamlTypeResolver
= context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
string[] stringArray = (value as string).Split(',');
Collection<Type> types = new Collection<Type>();
for( int i = 0; i < stringArray.Length; i++ )
types.Add( xamlTypeResolver.Resolve( stringArray
.Trim()) );return types;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
return base.ConvertTo(context, culture, value, destinationType);
}
}
And here's some sample Xaml that's supported by these:
<cc:Generic TypeName="co:Dictionary">
<cc:Generic.TypeArguments>
<cc:CollectionOfT TypeArgument="sys:Type">
<x:Type TypeName="sys:String"/>
<x:Type TypeName="sys:Int32"/>
</cc:CollectionOfT>
</cc:Generic.TypeArguments>
</cc:Generic>
<cc:Generic TypeName="co:Dictionary">
<x:Type TypeName="sys:String"/>
<x:Type TypeName="sys:Int32"/>
</cc:Generic>
<Label Content="{cc:Generic TypeName=co:Dictionary, TypeArguments='sys:String, sys:Int32'}"/>
- Mike, you rocks, I will try your code later on, and probably I will have more questions regarding custom markup extension.
Sheva - Now, 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 You're right that parameter arrays aren't supported for markup extensions today, but I'll put it on the wish list for the next version.
One way you could accomplish that syntax, though, would be to make the type arguments a string, rather than a Type[]. So the markup extension would look like:
<Label Content="{co:Dictionary, 'sys:String, sys:Int32' }"/>
... Then, in ProvideValue, you can parse the string into a Type[], just like the TypeCollectionConverter above.
- Nice tip Mike, hopefully in the version 2.0 of WPF, there will be more generic enhancement for xaml.
Sheva Very good sample Mike, but ... how can I use the MarkupExtension from code?
For example I have a Dictionary property in a class and I want to be serialized this way.
Thanks,
Laurentiu

