views:

362

answers:

1

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
  2. 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
  3. 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)
  2. 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)
  3. 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 strongly-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.

+3  A: 

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

Hope that helps. -Rob Relyea XAML/WPF Teams http://robrelyea.com/blog

Rob Relyea
Yeah sure, you can have more awesome... but not for 4 months. I can't wait for .Net 4.
Bryan Anderson
I had tested the `IProvideValueTarget` service earlier in VS2008, but `TargetObject` and `TargetProperty` always returned `null` (and the big "this type is used internally" callout in the documentation led me to believe it was not usable). I re-tested them in VS2010/.NET 4 just now and both returned useful results. Thank you!
Emperor XLII