Generic TypeConverter for XAML

TypeConverter‘s in WPF are part of the underlying mechanism which allow you to assign values to attributes of complex types within XAML using plain strings. For example, whenever you specify Point‘s.

<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">

Typically, you specify a TypeConverter for a certain type by applying the TypeConverterAttribute to it.

[TypeConverterAttribute( typeof( PointConverter ) )]
public struct Point : IFormattable

Since attribute arguments cannot use type parameters, this prevents you from specifying a generic TypeConverter for a generic class. I came across this issue while implementing a type converter to support specifying instances of my generic Interval class from XAML. The following is not possible.

// attribute argument cannot use type parameters
[TypeConverter( typeof( IntervalTypeConverter<T, TSize> ) )]
public class Interval<T, TSize>

However, you can write a non-generic TypeConverter which is capable of converting strings to several different target types. When using XAML you can obtain a IDestinationTypeProvider service through the context which is passed to the ConvertFrom method. From here you can retrieve the fully specified target type, including generic type parameters. Through reflection you can then call a Parse method on the specific type to take care of initializing an instance of the desired type. An example for Interval can be found in my library.

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

    // ... convert to desired target type using a parsing method

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

However, it is important to note that this type provider will only work for XAML, since the IDestinationTypeProvider service is only provided by XAML’s ServiceProviderContext. It is thus not desirable to add this TypeProvider to types which work independent from XAML, like my Interval class. In addition this would require referencing the System.Xaml assembly.

How then to make XAML use this type provider without applying the attribute to the type definition? One option is applying the TypeConverter attribute on a per-property basis.

The per-property type converter technique is particularly useful if you choose to use a property type from Microsoft .NET Framework or from some other library where you cannot control the class definition and cannot apply a TypeConverterAttribute there.

However, this implies a lot of redundancy, having to apply the attribute to every property of that specific type. Using TypeDescriptor.AddAttributes() you can assign a TypeConverter at runtime.

TypeDescriptor.AddAttributes(
  typeof( Interval<,> ),
  new TypeConverterAttribute( typeof( IntervalTypeConverter ) ) );

Any subsequent TypeDescriptor.GetConverter() call will then return the converter for the specified type. Unfortunately this does not work for XAML, since XAML does not seem to take component modifications at runtime into account. Therefore, in order for this to work, we need to implement this runtime behavior ourselves. Recall that XAML does load TypeConverter‘s specified in TypeConverterAttribute‘s. We can thus use a special type converter which redirects its implementation to a converter loaded through TypeDescriptor to hook into XAML’s type conversion runtime. I implemented a RedirectTypeConverter which can be used as a base class for this exact purpose. Each of its methods first ensures the converter is initialized using TypeDescriptor, and then redirects the call to this converter. When TypeDescriptor.GetConverter( _type ) returns the redirecting type converter itself, this means no converter was specified using TypeDescriptor, hence no conversion is supported for this type.

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

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 );
	}
}
class RedirectIntervalTypeConverter : RedirectTypeConverter
{
	public RedirectIntervalTypeConverter()
		: base( typeof( Interval<,> ) )
	{
	}
}

Applying this converter using TypeConverterAttribute to generic types thus allows redirecting type conversion to a converter which supports multiple target types in environments which provide information about the target type (like XAML using IDestinationTypeProvider). You only need to add the converter once, and it will be supported for all of your dependency properties using them.

TypeDescriptor.AddAttributes(
  typeof( Interval<,> ),
  new TypeConverterAttribute( typeof( IntervalTypeConverter ) ) );
Advertisements
  1. #1 by frogger3d on September 15, 2017 - 10:58 am

    The explanation is a little terse, do you have example code of what IntervalTypeConverter looks like?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: