开发者

TypeConverter for generic type used in xaml

I am looking into initializing members of generic types declared in XAML. This is targeting the (reduced) generics support in WPF 4 and future Silverlight. (I have tried the scenarios below using x:TypeArguments and XamlReader.Load in VS2010 Beta 2, but will use TestClassInt32 : TestClass<int> { } for simplicity, as it has the same behavior as using the generic type directly, but is easier to test using compiled xaml today.)


Here are the test types I am using.

public class TestClass<T> {
  [TypeConverter( typeof(StringListToItemsConverter) )]
  public IEnumerable<T> Items { get; set; }
  public TestProperty<T> Property { get; set; }
}

[TypeConverter( typeof(TestPropertyConverter) )]
public struct TestProperty<T> {
  public TestProperty( T value ) : this() { Value = value; }
  public T Value { get; }
}

Here is the example scenario.

<StackPanel>
  <StackPanel.DataContext>
    <test:TestClassInt32 Items="1,2,3" Property="6" />
  </StackPanel.DataContext>

  <TextBox Text="{Binding Property.Value}" />
  <ItemsControl ItemsSource="{Binding Items}" />
</StackPanel>

When I hard-code typeof(int) into the converters for this example, everything works fine, but that approach obviously does not work for TestClass<double> or TestClass<DateTime>. The problem is that the TypeConverter.ConvertFrom method does not have access to the destination type, only the source type. (This was not a problem when TypeConverter was created in .NET 1.0, because the destination type could not be parameterized, but is an unfortunate limitation now.)


Here are the approaches I have looked at to get around this problem:

  1. Make the type converter generic; e.g. [TypeConverter( typeof(TestPropertyConverter<T>) )]
    • .NET does not support type parameters in attributes
    • Have the TypeConverter return an intermediate type that either implements IConvertible.ToType, or has a TypeConvert that can ConvertTo the destination type
    • XAML parser only does one-step conversions: if the returned object cannot be assigned to the destination, it throws an exception
    • Define a custom type descriptor that will return the appropriate converter based on the actual type
    • WPF ignores custom type descriptors, and TypeDescriptor does not even exist in Silverlight

Here are the alternatives I have come up with for "working around" this issue:

  1. Require the destination type to be embedded in the string; e.g. "sys:Double 1,2,3"
    • In Silverlight, have to hard-code a fixed set of supported types (in WPF, can use the IXamlTypeResolver interface to get the actual type that "sys:Double" corresponds to)
    • Write a custom markup extension that takes a type parameter; e.g. "{List Type={x:Type sys:Double}, Values=1,2,3}"
    • Silverlight does not support custom markup extensions (it also does not support the x:Type markup extension, but you could use the hard-coded approach from option 1)
    • Create a wrapper type that takes a type parameter and re-defines all of the generic members as object, forwarding all member accesses to the underlying 开发者_如何学Pythonstrongly-typed object
    • Possible, but makes for a very poor user experience (have to cast to get underlying generic object, have to still use hard-coded list for type parameter on Silverlight, have to cache member assignments until the type argument is assigned, etc, etc; generally loses most of the benefits of strong typing with generics)

Would be happy to hear any other ideas for working around this issue today, or in WPF 4.


In .NET 4, there is an IServiceProvider available through which you can get IDestinationTypeProvider, and then you should be able to do what you need. In .NET 3 or 4, the IProvideValueTarget service can give you the targetObject and targetProperty. From the targetProperty (a PropertyInfo, MethodInfo-for attached, or a DependencyProperty), you can get the type.

To get theIDestinationTypeProvider service provider from within ConvertFrom of a TypeConverter for example:

public override object ConvertFrom(
    ITypeDescriptorContext context,
    CultureInfo culture,
    object value )
{
    var typeProvider =
      (IDestinationTypeProvider)context.GetService( typeof( IDestinationTypeProvider ) );
    Type targetType = typeProvider.GetDestinationType();

    // ... do stuff

    return base.ConvertFrom( context, culture, value );
}


Rob Relyea's answer works, but unfortunately relies on services provided by XAML and will thus not work for other ITypeDescriptorContext's. This also implies you need to reference System.Xaml. This is undesirable for type definitions in assemblies which should not reference System.Xaml.

I therefore opted instead to apply a special TypeConverter to my generic type which redirects its implementation to a converter which is loaded through TypeDescriptor. This allows other assemblies (e.g. using XAML) to specify the type converter to be loaded at runtime using:

TypeDescriptor.AddAttributes(
  typeof( SomeGenericType<> ),
  new TypeConverterAttribute( typeof( SomeGenericTypeConverter ) ) );

I created a RedirectTypeConverter base class for such converters. The key components of the code required to implement this:

protected RedirectTypeConverter( Type type )
{
    _type = type;
}

// Other methods are implemented similarly.
public override object ConvertFrom(
    ITypeDescriptorContext context,
    CultureInfo culture,
    object value )
{
    InitializeConverter();
    return _converter.ConvertFrom( context, culture, value );
}

public void InitializeConverter()
{
    if ( _converter != null )
    {
        return;
    }

    _converter = TypeDescriptor.GetConverter( _type );
    if ( _converter.GetType() == GetType() )
    {
        string message = string.Format(
          "Conversion failed. Converter for {0} is missing in TypeDescriptor.", _type );
        throw new InvalidOperationException( message );
    }
}

A full writeup and extended discussion is available on my blog.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜