MSDN > 論壇首頁 > Windows Presentation Foundation (WPF) > How To Get ParserContext?
發問發問
 

已答覆How To Get ParserContext?

  • 2006年10月4日 下午 05:21Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    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:
    ParserContext context = serviceProvider.GetService(typeof(ParserContext)) as ParserContext;
    if (context == null)
    {
    	throw new InvalidOperationException("ParserContext is null");
    }


    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?

    Sheva

解答

  • 2006年10月5日 下午 05:31Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     已答覆

    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);

     

            }

        }

    }

     

  • 2006年10月6日 下午 06:50Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     已答覆

    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( stringArrayIdea.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'}"/>

     

  • 2006年10月10日 下午 11:45Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     已答覆

    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.

所有回覆

  • 2006年10月5日 上午 12:03Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     

    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.

     

  • 2006年10月5日 上午 04:29Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    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:
    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");
    }


        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?

    Sheva
  • 2006年10月5日 下午 05:31Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     已答覆

    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);

     

            }

        }

    }

     

  • 2006年10月5日 下午 06:19Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    Thanks Mike, you code really enlightens me.

    Sheva
  • 2006年10月6日 下午 04:17Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     

    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);

        }

    }

     

     

  • 2006年10月6日 下午 05:27Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    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
  • 2006年10月6日 下午 06:50Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     已答覆

    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( stringArrayIdea.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'}"/>

     

  • 2006年10月7日 上午 05:49Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    Mike, you rocks, I will try your code later on, and probably I will have more questions regarding custom markup extension.

    Sheva
  • 2006年10月8日 下午 04:28Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    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
  • 2006年10月10日 下午 11:45Mike Hillberg - MSFT 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     已答覆

    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.

  • 2006年10月11日 下午 04:15Zhou Yong 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     
    Nice tip Mike, hopefully in the version 2.0 of WPF, there will be more generic enhancement for xaml.

    Sheva
  • 2006年11月10日 上午 09:07DotNetWise 使用者勳章使用者勳章使用者勳章使用者勳章使用者勳章
     

    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