Introduction
Basically what brought on this example was the need to convert a delimited list to a collection. In the application I was working on, I was using the TypeConverter
class to convert values from string to specific types using Reflection. I could define a class with the proper types and then take string values and create a new instance of the class. Thus I was familiar with the TypeConverter
, but had never implemented a TypeConverter
. Creating a TypeConverter
for a collection from a delimited list made sense. Then I thought: could I use this for binding in WPF to both a Text
property and a ItemsSource
property? It turns out that you can.
The title indicates that this article is specific to binding to an object in WPF, but it is really about implementing a TypeConverter
for a class. This article will show you how to implement a class
that will provide type conversion for another class. In this particular case, we will be providing conversion to and from a string. In WPF, a TypeConverter
can be used to reduce the complexity of interfacing a custom object in a ViewModel
to a View
.
The TypeConverter
The TypeConverter
is used to convert values between data types, and to assist property configuration at design time by providing text-to-value conversion. It uses InstanceDescriptor
and System.Reflection
to provide the information necessary to initialize a property at run time. Most native types have an associated TypeConverter
. The default type converters are in the System.ComponentModel
namespace and are named TypeConverterNameConverter
. WPF and Silverlight make extensive use of the TypeConverter
in binding.
To define a TypeConverter
to support binding conversion for a specific class, you have to create a class
that inherits from TypeConverter
, and overrides the ConvertTo
or ConvertFrom
and also the corresponding CanConvertTo
and CanConvertFrom
:
public class StringListTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
return new StringList((string)value);
}
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
return value == null ? null : string.Join(", ", (StringList)value);
}
}
If you try to delete the CanConvertTo
and CanConvertFrom
you will see that binding will no longer work.
There are a lot of other properties that can be overridden in the TypeConverter
class, but only these four are important for binding. The TypeConverter
is also used for Drag and Drop, which may be why these methods are needed. Obviously if you are not going to need to support two-way binding, you do not need to override and implement the corresponding methods.
The class that I am writing this TypeConverter
for is intended to take a string that would take a comma or semicolon delimited list and convert it to a list of strings. In this case the target is IEnumerable<string>
:
[TypeConverter(typeof(StringListTypeConverter))]
class StringList : IEnumerable<string>
{
private readonly IEnumerable<string> _enumerable;
private readonly string _original;
public StringList(string value)
{
_original = value;
if (!string.IsNullOrEmpty(value))
{
_enumerable = value.Split(",;".ToCharArray()).
Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => i.Trim());
}
}
protected StringList(IEnumerable<string> value)
{
_enumerable = value;
_original = string.Join(", ", value);
}
public StringList() { }
public IEnumerator<string> GetEnumerator()
{
return _enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
public override string ToString()
{
return _original;
}
}
You will notice that the class is decorated with a TypeConverter
attribute that provides information on the class
that provides the TypeConverter
for the class. This provides the magic.
Now we have a class that can take a string
and can convert it to a list, and be bound both to a DependencyProperty
that is expecting a list of string
objects and a string
object. The XAML for the simple example I created to demonstrate using the TypeConverter
is as follows:
<Window x:Class="TypeConverterExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TypeConverterExample"
Title="TypeConverter Binding Example"
Height="250"
Width="375">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
Text="{Binding Source}"
/>
<ListBox Grid.Row="1"
ItemsSource="{Binding Source, Mode=TwoWay}"
/>
</Grid>
</Window>
The XAML above is actually simplified from what is in the code. When run and a list entered into the text box, the following will appear:
Using the TypeConverter Directly
Using a TypeConverter
directly just requires a little bit of code which is used to find the associated TypeConverter
with the class using the GetConverter
method of the TypeDescriptor
class:
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
string convertedValue = converter.ConvertFrom(value);
This code will convert to the target for the TypeConverter
. Notice that the signature used is not the same as coded, which has a signature of ConvertTo(ITypeDescriptorContext, CultureInfo, object, Type)
. This is because the parent TypeConverter
class has another signature that takes one argument, and calls the overridden TypeConverter
method. Within WPF I have used the TypeConverter
within a IValueConverter
.
Summary
I really do not have any immediate need for a class that can used in WPF and be bound to both an IEnumerable
and a string
, this could become useful in some future project. The concept, however, was very useful in my current application. I can see that there would be cases where TypeConverter
can simplify coding in support of binding of a custom object to WPF controls without adding unnecessary conversions in the ViewModel
. I think that the flexibility of the TypeConverter
is another indication of the quality of tools that Microsoft has provided the developer community.