Implementing a TypeConverter

The TypeConverter is the base class for classes that support converting to and from existing types. The most common type of conversion is to convert an object to and from a string representation of the type. Examples of existing type converters, include: PointConverter, RectangleConverter, IconConverter, CursorConverter, and ImageConverter to name just a few.

These converters work quietly in the background to support converting the types described by the converter-for example, PointConverter-to and from a string representation. This simple conversion makes it possible to display a string representation of a Point or an Image in the Properties window, seamlessly. When a representation of an Image needs to be displayed in the Properties window the ImageConverter displays a text representation of the image, and a file path representing an image can quickly be converted back to an image by the same converter.

You will find existing TypeConverters useful, and custom TypeConverters will add a bit of fit and finish to your custom types, especially when you are creating custom controls. I will demonstrate how to make and use a TypeConverter by demonstrating a simple Circle class and a CircleConverter that supports converting to and from the representation of the circle.

Elements of a TypeConverter

Custom TypeConverter classes inherit from System.ComponentModel.TypeConverter and overload CanConvertFrom, ConvertFrom, ConvertTo, and CanConvertTo to implement a converter.

CanConvertFrom returns a Boolean and is used to indicate if a converter can create an instance of an object from a source type. ConvertFrom performs the actual conversion. ConvertTo returns a Boolean indicating whether an object can be converted to a specific destination type, and ConvertTo performs the conversion. Notice there was no mention of the specific type of the objects that we are converting from or to. The type of the object converted to and from is only limited by your imagination (and the amount of code you are willing to write.)

Defining a Circle Structure

The Circle structure is roughly consistent with the Point and Rectangle structures. Circle is a basic structure that stores a center and a radius. Supposing that some control may use the Circle structure as a property, we will implement a TypeConverter that facilitates editing the properties of the Circle. Listing 1 provides the complete solution for the Circle structure.

Listing 1: Circle structure, represented by a center point and radius.

1:    Public Structure Circle
2:
3:      Private FX As Integer
4:      Private FY As Integer
5:      Private FRadius As Integer
6:
7:      Public Sub New(ByVal X As Integer, _
8:        ByVal Y As Integer, ByVal R As Integer)
9:        FX = X
10:       FY = Y
11:      FRadius = R
12:    End Sub
13:
14:    Public Property X() As Integer
15:      Get
16:        Return FX
17:      End Get
18:      Set(ByVal Value As Integer)
19:        FX = Value
20:      End Set
21:    End Property
22:
23:    Public Property Y() As Integer
24:      Get
25:        Return FY
26:      End Get
27:      Set(ByVal Value As Integer)
28:        FY = Value
29:      End Set
30:    End Property
31:
32:     Public Property Radius() As Integer
33:      Get
34:        Return FRadius
35:      End Get
36:      Set(ByVal Value As Integer)
37:        FRadius = Value
38:      End Set
39:    End Property
40:
41:    Public Overrides Function ToString() As String
42:      Return String.Format("{0}, {1}, {2}", X, Y, Radius)
43:    End Function
44:
45:  End Structure

As you can quickly determine from the code, the Circle structure simply stores the center and a radius. The center is represented as X and Y integer values, and the radius is stored as an integer.

Defining the CircleConverter

To implement the CircleConverter we need to decide what it is we want to convert. From listing 1 it is clear that we can construct a Circle structure from three integers: X and Y representing the center of the circle and R representing the radius. We will implement the converter to support converting three comma-delimited integers to a Circle object and a Circle object back to three comma delimited integers. The CircleConverter is provided in listing 2.

Listing 2: A custom TypeConverter

1:  Public Class CircleConverter
2:    Inherits TypeConverter
3:
4:    Public Overloads Overrides Function CanConvertFrom( _
5:      ByVal context As ITypeDescriptorContext, _
6:      ByVal sourceType As Type) As Boolean
7:
8:      If (sourceType.Equals(GetType(String))) Then
9:        Return True
10:     Else
11:       Return MyBase.CanConvertFrom(context, sourceType)
12:     End If
13:
14:   End Function
15:
16:   Public Overloads Overrides Function ConvertTo( _
17:     ByVal context As ITypeDescriptorContext, _
18:     ByVal cultureInfo As CultureInfo, _
19:     ByVal value As Object, ByVal destinationType _
                                     As Type) As Object
20:
21:     If (destinationType.Equals(GetType(String))) Then
22:       Return value.ToString()
23:     Else
24:       Return MyBase.ConvertTo(context, cultureInfo, _
                                  value, destinationType)
25:     End If
26:   End Function
27:
28:   Public Overloads Overrides Function CanConvertTo( _
29:     ByVal context As ITypeDescriptorContext, _
30:     ByVal destinationType As Type) As Boolean
31:
32:     If (destinationType.Equals(GetType(String))) Then
33:       Return True
34:     Else
35:       Return MyBase.CanConvertTo(context, destinationType)
36:     End If
37:   End Function
38:
39:   Public Overloads Overrides Function ConvertFrom( _
40:     ByVal context As ITypeDescriptorContext, _
41:     ByVal cultureInfo As CultureInfo, _
42:     ByVal value As Object) As Object
43:
44:     If (TypeOf value Is String) Then
45:       Dim s() As String
46:       s = CType(value, String).Split(New Char() {","})
47:
48:       Try
49:         Return New Circle(Integer.Parse(s(0)), _
50:            Integer.Parse(s(1)), Integer.Parse(s(2)))
51:       Catch
52:         Throw New InvalidCastException(value)
53:       End Try
54:     Else
55:       Return MyBase.ConvertFrom(context, cultureInfo, value)
56:     End If
57:   End Function
58:
59:   Public Overloads Overrides Function GetPropertiesSupported( _
60:     ByVal context As ITypeDescriptorContext) As Boolean
61:     Return True
62:   End Function
63:
64:  Public Overloads Overrides Function GetProperties( _
65:   ByVal context As ITypeDescriptorContext, _
66:   ByVal value As Object, _
67:   ByVal Attribute() As Attribute) _
68:   As PropertyDescriptorCollection
69:
70:   Return TypeDescriptor.GetProperties(value)
71:
72:  End Function
73:
74: End Class

CanConvertFrom and CanConvertTo return True if converter is asked if it can convert from or to a string. If the type we are testing is not a string then the inherited version of these methods are invoked. ConvertTo returns a string version of the Circle on line 22, and ConvertFrom returns a new instance of a Circle object if the value argument is a string in the correct format (see lines 49 and 50). If the value argument is a string and it is not a comma-delimited list of three integer values then an InvalidCastException is thrown on line 52.

The complete implementation of the ConvertFrom method checks to see if value is a string. If value is a string then the shared method string.Split is invoked on line 46, splitting the string at each comma into an element of an array of strings. In the Try block a new Circle object is constructed. If the construction of the Circle object fails then an InvalidCastException is raised.

If you want to show the nested properties (see figure 1) then you must implement the TypeConverter GetPropertiesSupported and GetProperties methods. For example, lines 59 through 72 of listing 2, implements a GetPropertiesSupported method that returns True and GetProperties that returns an array of PropertyDescriptor values. As a consequence we can modify the X, Y, and Radius values of the circle by modifying the comma-delimited value or each individual field.

Figure 1: Show the properties of the Circle object using the TypeConverter GetProperties method.

Associating a TypeConverter with a Class

The final phase of the problem is to associate the TypeConverter with the type that it is converting. The next fragment shows the application of the TypeConverter to the Circle structure using the TypeConverterAttribute. (By convention we drop the Attribute suffix when applying an attribute. TypeConverterAttribute is applied as TypeConverter.) The TypeConverterAttribute is initialized with the Type information of the converter. (The Serializable attribute, also shown, ensures that the values you assign to the Circle are serialized. That is, the Circle is stored in the initialization section of the source code.

<TypeConverter(GetType(CircleConverter)), Serializable()> _
Public Structure Circle

If you apply the TypeConverter to the type then you do not have to apply the TypeConverter when the type is used. TypeConverters work for Windows Forms and Web Forms applications.

Summary

Attributes are used in a wide variety of circumstances. The TypeConverter class and TypeConverterAttribute can be used to make it easy to convert any type to any other suitable type. This is especially useful when you need to represent a type, like an image or the properties of a Circle, in the Properties window.

The most common kind of conversion is between a type and a string representation of that type. However, as you can determine by the code and with a moderate amount of experimentation, you are not limited to string conversions only.

The TypeConverter demonstrates another facet of .NET that will make it an enjoyable tool for component vendors.

About the Author

Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look for his recent book Visual Basic .Net Unleashed on Amazon.com. Paul Kimmel is available to help design and build your .NET solutions and can be contacted at pkimmel@softconcepts.com.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read