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.