Walkthrough: Creating a Type Converter for the WPF Designer
This walkthrough shows how to create a type converter for a custom type. The WPF Designer for Visual Studio uses the type converter to serialize your custom type to and from Extensible Application Markup Language (XAML).
In this walkthrough, you perform the following tasks:
Create the project.
Create the custom type.
Create the type converter.
Create a control which uses the custom type.
View the custom type in the Properties window.
When you are finished, you will be able to create type converters for your custom types.
Note
The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Working with Settings.
Prerequisites
You need the following components to complete this walkthrough:
- Visual Studio 2010.
Creating the Project
The first step is to create the project for the application.
To create the project
Create a new WPF Application project in Visual Basic or Visual C# named TypeConverterExample. For more information, see How to: Create a New WPF Application Project.
MainWindow.xaml opens in the WPF Designer.
Creating the Custom Type
In this procedure, you create a simple custom type named Complex, which represents a complex number. A complex number has a real part and an imaginary part, and these are exposed as double properties.
To create a custom type
Add a new class named Complex.vb or Complex.cs to the TypeConverterExample project. For more information, see How to: Add New Project Items.
The code file for the Complex class opens in the Code Editor.
Replace the Complex class definition with the following code.
<TypeConverter(GetType(ComplexTypeConverter))> _ Public Class Complex Private realValue As Double Private imaginaryValue As Double Public Sub New() End Sub Public Sub New(ByVal real As Double, ByVal imaginary As Double) Me.realValue = real Me.imaginaryValue = imaginary End Sub Public Property Real() As Double Get Return realValue End Get Set(ByVal value As Double) realValue = value End Set End Property Public Property Imaginary() As Double Get Return imaginaryValue End Get Set(ByVal value As Double) imaginaryValue = value End Set End Property Public Overrides Function ToString() As String Return String.Format( _ CultureInfo.CurrentCulture, _ "{0}{2}{1}", _ Me.realValue, _ Me.imaginaryValue, _ CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator) End Function Public Shared Function Parse(ByVal complexNumber As String) As Complex If String.IsNullOrEmpty(complexNumber) Then Return New Complex() End If 'The parts array holds the real and imaginary parts of the object. Dim separator() As Char = {","} Dim parts() As String = complexNumber.Split(separator) If (parts.Length <> 2) Then Throw New FormatException( _ String.Format( _ "Cannot parse '{0}' into a Complex object because " & _ "it is not in the '<real>, <imaginary>' format.", _ complexNumber)) End If Return New Complex( _ Double.Parse(parts(0).Trim()), _ Double.Parse(parts(1).Trim())) End Function End Class
[TypeConverter( typeof( ComplexTypeConverter ) )] public class Complex { private double realValue; private double imaginaryValue; public Complex() { } public Complex(double real, double imaginary) { this.realValue = real; this.imaginaryValue = imaginary; } public double Real { get { return realValue; } set { realValue = value; } } public double Imaginary { get { return imaginaryValue; } set { imaginaryValue = value; } } public override string ToString() { return String.Format( CultureInfo.CurrentCulture, "{0}{2}{1}", this.realValue, this.imaginaryValue, CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator); } public static Complex Parse(string complexNumber) { if (String.IsNullOrEmpty(complexNumber)) { return new Complex(); } // The parts array holds the real and // imaginary parts of the object. string[] parts = complexNumber.Split(','); if (2 != parts.Length) { throw new FormatException( String.Format( "Cannot parse '{0}' into a Complex object because " + "it is not in the \"<real>, <imaginary>\" format.", complexNumber)); } return new Complex(double.Parse(parts[0].Trim()), double.Parse(parts[1].Trim())); } }
Creating the Type Converter
Now you define the type converter for the Complex class. The ComplexTypeConverter class converts a Complex object to its string representation and back. It also provides a list of default values, which can be displayed in a designer's Properties window.
To create the type converter
After the Complex class definition, insert the following code for the ComplexTypeConverter class.
Public Class ComplexTypeConverter Inherits TypeConverter Private Shared defaultValues As List(Of Complex) = New List(Of Complex)() Shared Sub New() defaultValues.Add(New Complex(0, 0)) defaultValues.Add(New Complex(1, 1)) defaultValues.Add(New Complex(-1, 1)) defaultValues.Add(New Complex(-1, -1)) defaultValues.Add(New Complex(1, -1)) End Sub ' Override CanConvertFrom to return true for String-to-Complex conversions. Public Overrides Function CanConvertFrom( _ ByVal context As ITypeDescriptorContext, _ ByVal sourceType As Type) As Boolean If sourceType Is GetType(String) Then Return True End If Return MyBase.CanConvertFrom(context, sourceType) End Function ' Override CanConvertTo to return true for Complex-to-String conversions. Public Overrides Function CanConvertTo( _ ByVal context As System.ComponentModel.ITypeDescriptorContext, _ ByVal destinationType As System.Type) As Boolean If destinationType Is GetType(String) Then Return True End If Return MyBase.CanConvertTo(context, destinationType) End Function ' Override ConvertFrom to convert from a string to an instance of Complex. Public Overrides Function ConvertFrom( _ ByVal context As ITypeDescriptorContext, _ ByVal culture As System.Globalization.CultureInfo, _ ByVal value As Object) As Object If TypeOf value Is String Then Try Return Complex.Parse(CType(value, String)) Catch ex As Exception Throw New Exception( _ String.Format( _ "Cannot convert '{0}' ({1}) because {2}", _ value, _ value.GetType(), _ ex.Message), ex) End Try End If Return MyBase.ConvertFrom(context, culture, value) End Function ' Override ConvertTo to convert from an instance of Complex to string. Public Overrides Function ConvertTo( _ ByVal context As ITypeDescriptorContext, _ ByVal culture As System.Globalization.CultureInfo, _ ByVal value As Object, _ ByVal destinationType As Type) As Object If destinationType Is Nothing Then Throw New ArgumentNullException("destinationType") End If Dim c As Complex = CType(value, Complex) If (c IsNot Nothing) Then If Me.CanConvertTo(context, destinationType) Then Return c.ToString() End If End If Return MyBase.ConvertTo(context, culture, value, destinationType) End Function Public Overrides Function GetStandardValuesSupported( _ ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean Return True End Function Public Overrides Function GetStandardValues( _ ByVal context As System.ComponentModel.ITypeDescriptorContext) _ As TypeConverter.StandardValuesCollection Dim svc As New StandardValuesCollection(defaultValues) Return svc End Function End Class
public class ComplexTypeConverter : TypeConverter { private static List<Complex> defaultValues = new List<Complex>(); static ComplexTypeConverter() { defaultValues.Add(new Complex(0, 0)); defaultValues.Add(new Complex( 1, 1)); defaultValues.Add(new Complex(-1, 1)); defaultValues.Add(new Complex(-1,-1)); defaultValues.Add(new Complex( 1,-1)); } // Override CanConvertFrom to return true for String-to-Complex conversions. public override bool CanConvertFrom( ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } // Override CanConvertTo to return true for Complex-to-String conversions. public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) { return true; } return base.CanConvertTo(context, destinationType); } // Override ConvertFrom to convert from a string to an instance of Complex. public override object ConvertFrom( ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { string text = value as string; if (text != null) { try { return Complex.Parse(text); } catch (Exception e) { throw new Exception( String.Format("Cannot convert '{0}' ({1}) because {2}", value, value.GetType(), e.Message), e); } } return base.ConvertFrom(context, culture, value); } // Override ConvertTo to convert from an instance of Complex to string. public override object ConvertTo( ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (destinationType == null) { throw new ArgumentNullException("destinationType"); } //Convert Complex to a string in a standard format. Complex c = value as Complex; if (c != null && this.CanConvertTo(context, destinationType)) { return c.ToString(); } return base.ConvertTo(context, culture, value, destinationType); } public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } public override TypeConverter.StandardValuesCollection GetStandardValues( ITypeDescriptorContext context) { StandardValuesCollection svc = new StandardValuesCollection(defaultValues); return svc; } }
At the top of the file, import the System.ComponentModel namespace, which contains the TypeConverter implementation.
Imports System.ComponentModel Imports System.Globalization
using System.ComponentModel; using System.Globalization;
Creating a Control Which Uses the Custom Type
To see your custom type and its type converter in action on the design surface, you create a UserControl with a property of type Complex.
To create a control which uses the custom type
Add a new WPF User Control named ComplexNumberControl.xaml to the TypeConverterExample project. For more information, see How to: Add New Items to a WPF Project.
View the code for ComplexNumberControl.
Replace the ComplexNumberControl class definition with the following code.
Partial Public Class ComplexNumberControl Inherits System.Windows.Controls.UserControl Private complexNumberValue As Complex Public Sub New() InitializeComponent() End Sub Public Property ComplexNumber() As Complex Get Return Me.GetValue(ComplexNumberProperty) End Get Set(ByVal value As Complex) Me.SetValue(ComplexNumberProperty, value) End Set End Property Public Shared ReadOnly ComplexNumberProperty As DependencyProperty = DependencyProperty.Register( _ "ComplexNumber", _ GetType(Complex), _ GetType(ComplexNumberControl), _ New PropertyMetadata(New Complex())) End Class
public partial class ComplexNumberControl : UserControl { public ComplexNumberControl() { InitializeComponent(); } public Complex ComplexNumber { get { return (Complex)this.GetValue(ComplexNumberProperty); } set { this.SetValue(ComplexNumberProperty, value); } } public static readonly DependencyProperty ComplexNumberProperty = DependencyProperty.Register( "ComplexNumber", typeof(Complex), typeof(ComplexNumberControl), new PropertyMetadata(new Complex())); }
Build the project.
Viewing the Custom Type in the Properties Window
You can view your custom type when the ComplexNumberControl is hosted in a WPF window.
To view the custom type in Properties window
Open MainWindow.xaml in the WPF Designer.
In XAML view, replace the Window element with the following code.
<Window x:Class="TypeConverterExample.MainWindow" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:TypeConverterExample" Title="Window1" Height="300" Width="300"> <Grid> <c:ComplexNumberControl ComplexNumber="0,0" /> </Grid> </Window>
Click in Design view. If necessary, click the Information bar at the top to reload the window.
In the XAML view, click the ComplexNumberControl element.
In the Properties window, click the ComplexNumber property.
A drop-down arrow appears next to the ComplexNumber item.
Click the drop-down to see the list of default values. Select the -1,-1 value.
In the XAML view, the ComplexNumber assignment changes to "-1,-1".
See Also
Tasks
How to: Implement a Type Converter