Introduction to TypeConverters, Part II: Expandable Object Converter...

Utilize the technology of the TypeConverter, its properties, and its methods.


By: Brian Mains Date: April 8, 2004 Download the code.

There are many TypeConverters available that .NET objects can take advantage of, such as strings, enumerations, etc. These are defined by the framework and are readily available. TypeConverters can also be used for more complex objects, such as classes, where the properties can be exposed at design-time. One of the ways to achieve this is through the ExpandableObjectConverter class. This class is a TypeConverter that creates a plus/minus sign box that can expand/contract the group of properties within that class. This converter will be used throughout this article.

If you haven't read the "Introduction to TypeConverters, Part I" document previously posted, you need to know that it explains the TypeConverter basics and how it handles the conversion between types. This document will explain a technical implementation of a specific converter.

The following class will be used throughout the article below.

Public Class Highlighting
  Private m_objBackColor As System.Drawing.Color
  Public Property BackColor() As System.Drawing.Color
    Get
      Return m_objBackColor
    End Get
    Set(ByVal Value As System.Drawing.Color)
      m_objBackColor = Value
    End Set
  End Property

  'Specify the color property (has it's own type converter)
  Private m_objForeColor As System.Drawing.Color
  Public Property ForeColor() As System.Drawing.Color
    Get
      Return m_objForeColor
    End Get
    Set(ByVal Value As System.Drawing.Color)
      m_objForeColor = Value
    End Set
  End Property

  'Specify a stylesheet class;
  Private m_strClass As String
  Public Property CSSClass() As String
    Get
      Return m_strClass
    End Get
    Set(ByVal Value As String)
      m_strClass = Value     End Set
  End Property

  Private m_blnEnabled As Boolean
  Public Property Enabled() As Boolean
    Get
      Return m_blnEnabled
    End Get
    Set(ByVal Value As Boolean)
      m_blnEnabled = Value
    End Set
  End Property
End Class

For the properties to appear correctly in the designer, public variables cannot be used as a replacement to properties. Properties can be assigned ComponentModel attributes that variables cannot. So far what has been defined is simple; four properties that get or set the values for the four variables that are behind-the-scenes. However, we aren't quite done with this class yet.

The TypeConverter class declaration is below, which inherits from the ExpandableObjectConverter class. Our custom TypeConverter class now has all the appearances of this predefined class.

Public Class HighlightingTypeConverter
  Inherits ExpandableObjectConverter

Next, the CanConvertFrom() method verifies that the conversion can happen.

  Public Overloads Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
    If (sourceType Is GetType(String)) Then
      Return True
    End If

    Return MyBase.CanConvertFrom(context, sourceType)
  End Function

The function checks the System.Type passed into the function. If the type is a string, then the function returns true; otherwise, the function allows the base class to make the determination of whether the type is allowable. Because we are defining the sourceType as a string in the following conversion method, which we will get to shortly, we know that this is the desired type. Next, the ConvertFrom() method handles the conversion from a string type to the Highlighting class that was designed above:

  'Converts string to Highlighting class object
  Public Overloads Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
    If (TypeOf (value) Is String) Then
      Dim objLight As Highlighting

      Try
        If (value.ToString() <> String.Empty) Then
          Dim strValues() As String = value.ToString().Split("@".ToCharArray())
          objLight = New Highlighting

          objLight.BackColor = Color.FromName(strValues(0))
          objLight.CSSClass = strValues(1)
          objLight.Enabled = Boolean.Parse(strValues(2))
          objLight.ForeColor = Color.FromName(strValues(3))

          Return objLight
        Else
          Return New Highlighting
        End If

      Catch ex As Exception
        Trace.WriteLine("Can't convert property: " & ex.ToString())
        Return Nothing
      Finally
        objLight = Nothing
      End Try
    End If

    Return MyBase.ConvertFrom(context, culture, value)
  End Function

The ConvertFrom() method, first and foremost, has implemented structured error handling, which is not important in the conversion, but is a good practice. What is most important in this method is that the value is converted to a string array by using the Split() method to break apart the string. We've defined the "@" character as a separator for the text. Please note that you can use whatever syntax to format the data that you would like; it can be a single character, multiple characters, or different characters separating each field.

After the string is separated, the Highlighting class object is created and instantiated, with the parameters being passed in from the string array. Note that a conversion must take place to get the string values into the desired data types, especially if option strict is turned on. The ConvertFrom() method returns an object, which is our new Highlighting class.

The last method implemented in this TypeConverter is the ConvertTo() method:

  'Converts highlighting class object to string value
  Public Overloads Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
    If (destinationType Is GetType(String)) Then
      Dim objLight As Highlighting

      Try
        objLight = CType(value, Highlighting)

        If (objLight.BackColor.Equals(Color.Empty) And _
          objLight.ForeColor.Equals(Color.Empty) And _
          objLight.Enabled = False And _
          objLight.CSSClass = String.Empty) Then
            Return String.Empty
        Else
          Return objLight.BackColor.ToString() & "@" & objLight.CSSClass & "@" & objLight.Enabled.ToString() & "@" & objLight.ForeColor.ToString()
        End If

      Catch ex As Exception
        Trace.WriteLine("Can't convert property: " & ex.ToString())
        Return Nothing
      Finally
        objLight = Nothing
      End Try
    End If

    Return MyBase.ConvertTo(context, culture, value, destinationType)
  End Function
End Class

The ConvertTo() method is where the string template is initially created. This method takes a Highlighting class object and converts its parameters into a concatenated string. The "@" character was specified as the separator for each field; this character was used because it is not common that it will appear in the properties assigned to the class. For example, if the Highlighting class were to contain an email address property, it would not be wise to use the "@" character as a separator, because it appears in the email address, and would throw off the conversion (the array in the previous method would have five parameters instead of four). Since the "@" character is a safe bet as a separator, it has been chosen for this example.

This method also verifies the destination data type, but it is very important to note the IF statement. If none of the values have been provided (set to defaults), then an empty string is returned; however, if one value is provided, the string is converted. This is to prevent a string of "@" characters to be used in the conversion. You can see the effects of this in the property window in the designer, when you mouse over the Highlighting property in the custom control. The method then returns the newly constructed string.

For the Highlighting class to appear correctly in the designer, some attributes need to be declared for the properties of the class. These properties specify how the designer will handle events and changes to the properties. All of the properties in the Highlighting class will define these attributes:

<DefaultValue(“”), NotifyParentProperty(True), RefreshProperties(RefreshProperties.Repaint)>

The NotifyParentProperty attribute declaration specifies that the parent property (Highlighting property in the custom control) will be notified when a child property is modified. Also, refresh properties denote how the designer will act when the property values are modified. The above attribute declarations are fine for the System.Drawing.Color properties; however, one additional attribute must be added for the string and Boolean properties. The TypeConverter attribute is added with the type of converter to be used (StringConverter and BooleanConverter, respectively). In addition, our class defines the type of the TypeConverter for the entire class. The new class definition looks like:

<TypeConverter(GetType(HighlightingTypeConverter))> _
Public Class Highlighting
  Private m_objBackColor As System.Drawing.Color
  <DefaultValue(""), NotifyParentProperty(True), _
  RefreshProperties(RefreshProperties.Repaint)> _
  Public Property BackColor() As System.Drawing.Color
    Get
      Return m_objBackColor
    End Get
    Set(ByVal Value As System.Drawing.Color)
      m_objBackColor = Value
    End Set
  End Property

  Private m_objForeColor As System.Drawing.Color
  <DefaultValue(""), NotifyParentProperty(True), _
  RefreshProperties(RefreshProperties.Repaint)> _
  Public Property ForeColor() As System.Drawing.Color
    Get
      Return m_objForeColor
    End Get
    Set(ByVal Value As System.Drawing.Color)
      m_objForeColor = Value
    End Set
  End Property

  Private m_strClass As String
  <DefaultValue(""), NotifyParentProperty(True), _
  RefreshProperties(RefreshProperties.Repaint), _
  TypeConverter(GetType(StringConverter))> _
  Public Property CSSClass() As String
    Get
      Return m_strClass
    End Get
    Set(ByVal Value As String)
      m_strClass = Value
    End Set
  End Property

  Private m_blnEnabled As Boolean
  <DefaultValue(""), NotifyParentProperty(True), _
  RefreshProperties(RefreshProperties.Repaint), _
  TypeConverter(GetType(BooleanConverter))> _
  Public Property Enabled() As Boolean
    Get
      Return m_blnEnabled
    End Get
    Set(ByVal Value As Boolean)
      m_blnEnabled = Value
    End Set
  End Property
End Class

We've created our custom class and the TypeConverter. This class will be an expandable/contractible class in the Visual Studio .NET property window; the only step remaining is to create a custom control that will use this property, and define additional attributes at the control level. For this article, a custom radio-button list control will be used, which inherits from the RadioButtonList control. Our Highlighting parameter is added as a property, and the private variable for the class is declared and instantiated.

Public Class MyRadioButtonList
  Inherits System.Web.UI.WebControls.RadioButtonList

  Private m_objLight As New Highlighting
  <DefaultValue(“”), Category(“Appearance”), _
  PersistenceMode(PersistenceMode.InnerProperty), _
  DesignerSerializationVisibility (DesignerSerializationVisibility.Content)> _
  Public Property Highlighting As Highlighting
    Get
      Return m_objLight
    End Get
    Set(value As Highlighting)
      m_objLight = value
    End Set
  End Property
End Class

The additional attributes added to the custom control property define how the property outputs to the screen. The PersistenceMode attribute defines where the attribute will be added to (as a property in the control, as a default property, or as an attribute. How the control renders to the screen is based on these attributes. For example, an inner property specification creates the Highlighting control as a property in the tag (at the same level as the id and runat properties). It then stores the string in the specified template within the property. However, if the mode was specified as an attribute, the Highlighting class would have it's own tag within the control (similar to the <asp:ListItem> tag).

DesignerSerializationVisibility specifies the visibility of the code and content of the control. In our example, the content is rendered to the control; whereas, other properties would allow the designer to render code, or nothing at all.

Creating a TypeConverter is easy; however, what makes it difficult to understand is the lack of documentation available on the web. Once you understand the concepts, it is pretty easy to implement for your own controls.

You may download the above, and example code here.