Supporting Complex Types in Property Window
Introduction
Whenever you set any property of a control in the property window, the
property window needs to save this property value in the .aspx file. This
process is known as code serialization. For properties that are of simple types
(such as integer and string) this code serialization happens automatically.
However, when property data types are user defined complex types then you need
to do that work yourself. This is done via what is called as Type Converters.
This article is going to examine what type converters are and how to create one
for your custom control.
Type Converters
A type converter is a class that converts values entered in the property
window to the actual data type of the property and vice a versa. The type
converter class is inherited from TypeConverter or ExpandableObjectConverter
base class. If you inherit from TypeConverter base class then you need to supply
a delimited string in the property window where each part of the string
corresponds to a property of the underlying complex type. If you inherit from
ExpandableObjectConverter base class then Visual Studio makes your job easy by
providing an expandable tree to enter the individual property values. The
following figure shows how this expandable region looks like:

Creating an Expandable Type Converter
As an example of creating a type converter let's assume that you have a
custom control that displays full name in the web form. The Name property of the
control allows you to specify the full name to be displayed. The Name property
is of type FullName. The FullName class consists of two public properties namely
FirstName and LastName.
Creating FullName class
To begin developing this example first of all create a new Web Control
project in Visual Studio. Add a new class to the project and name it as FullName.
Code the FullName class as shown below:
[Serializable]
public class FullName
{
private string strFName;
private string strLName;
public string FirstName
{
get
{
return strFName;
}
set
{
strFName = value;
}
}
public string LastName
{
get
{
return strLName;
}
set
{
strLName = value;
}
}
public FullName()
{
}
public FullName(string fname, string lname)
{
strFName = fname;
strLName = lname;
}
}
The FullName class simply consists of two public properties namely FirstName
and LastName. Notice that the FullName class is marked as [Serializable]
Creating FullNameConverter class
Now add another class to the project and name it as FullNameConvertor.
Inherit the FullNameConvertor class from ExpandableObjectConverter base class.
As a convention the type converter class names should have name of the class
they convert attached with "Converter" at the end. Once created you need to
override certain methods of the base class. These methods are explained next.
- bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
The CanConvertFrom method tells the property window whether the source type
can be converted to the property data type. Most of the cases you will
ensure that if the source type is string then the method returns true; false
otherwise.
- bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
The CanConvertTo method tells the property window whether a property value
can be converted to the destination data type. Most of the cases you will
ensure that if the destination type is string then the method returns true;
false otherwise.
- object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
The actual task of converting a source value (string) into the destination
type (FullName) is done inside ConvertFrom method
- object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
The actual task of converting a property value (FullName) to the destination
type (string) is done inside ConvertTo method.
The following code shows all these methods for FullNameConverter class.
public override bool CanConvertFrom
(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
else
{
return base.CanConvertFrom(context, sourceType);
}
}
public override bool CanConvertTo
(ITypeDescriptorContext context,
Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
else
{
return base.CanConvertTo(context, destinationType);
}
}
public override object ConvertFrom
(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value)
{
if(value is string)
{
string[] names = ((string)value).Split(' ');
if (names.Length == 2)
{
return new FullName(names[0],names[1]);
}
else
{
return new FullName();
}
}
else
{
return base.ConvertFrom(context,culture,value);
}
}
public override object ConvertTo
(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
if (value is string)
{
FullName name = value as FullName;
return name.FirstName + " " + name.LastName;
}
else
{
return base.ConvertTo(context, culture, value,
destinationType);
}
}
The CanConvertFrom() method checks if the data type of proposed value is
string. If so it returns true otherwise base class version of CanConvertFrom()
method is called. Similar job is done inside CanConvertTo() method. Remember
that these two methods though sound similar are called at different times. The
CanConvertFrom() method is called when you enter a value in the property window
whereas CanConvertTo() method is called when property window reads previously
serialized property value.
The ConvertFrom() method converts a supplied string value into an instance of
type FullName. It does so by splitting the source string (e.g. Nancy Davalio) at
the occurrence of a white space. An instance of FullName class is returned based
on the supplied FirstName and LastName values.
The ConvertTo() method does reverse of ConvertFrom() method. It converts a
FullName instance into its string representation. This is done by simply
concatenating FirstName property, a white space and the LastName property. The
resultant string is returned from the ConvertTo() method.
Note that in the above example we inherited FullNameConverter class from
ExpandableObjectConverter base class. Even if you inherit from TypeConverter
base class the process of overriding the methods remains the same.
Attaching type converter to FullName class
Now that you have completed the FullNameConverter class it's time to attach
it to the FullName class. This is done as follows:
[TypeConverter(typeof(FullNameConvertor))]
[Serializable]
public class FullName
...
You need to decorate the FullName class with [TypeConverter] attribute. The
TypeConverter attribute accepts the type information of a class that is acting
as a type converter for this class.
Synchronizing markup and property window
Whenever you make any change in the property window immediately the new
values should be saved to the .aspx file. To enable this behavior you need to
mark the FirstName and LastName properties with the following additional
attributes.
[RefreshProperties(RefreshProperties.All)]
[NotifyParentProperty(true)]
The RefreshProperties() attribute should be familiar to you because we
discussed it in the previous
article of this series. It simply refreshes the property window by
re-querying all the property values. More important is the NotifyParentProperty()
attribute. This attribute governs whether the parent property (Name) is to be
notified when any of the child properties (FirstName and LastName) are changed.
This way the parent property can reflect the newly assigned values.
Coding the custom control
Now it's time to develop our custom control. We will call it as FullNameLabel
and it resembles as shown below:
public class FullNameLabel : WebControl
{
[DesignerSerializationVisibility
(DesignerSerializationVisibility.Visible)]
[PersistenceMode(PersistenceMode.InnerProperty)]
public FullName Name
{
get
{
return ViewState["_fullname"] as FullName;
}
set
{
ViewState["_fullname"] = value;
}
}
protected override void Render(HtmlTextWriter writer)
{
if (Name != null)
{
writer.WriteFullBeginTag("span");
writer.Write(Name.FirstName);
writer.Write(" ");
writer.Write(Name.LastName);
writer.WriteEndTag("span");
}
}
}
The code inside the FullNameLabel control is not a rocket science. It simply
declares a public property called Name that is of type FullName. It then emits
the first name and last name separated by a white space in the overridden
Render() method.
Carefully notice the declaration of FullNameLabel class. It is marked with
two special attributes viz. [DesignerSerializationVisibility] and [PersistenceMode].
The [DesignerSerializationVisibility] attribute governs whether a property will
be serialized. The DesignerSerializationVisibility enumeration has four possible
values viz. Default, Visible, Content, Hidden. The value of Content indicates
that the contents of the property will be serialized.
The [PersistenceMode] attribute governs how a property will be serialized.
The PersistenceMode enumeration has four values namely Attribute, InnerProperty,
InnerDefaultProperty and EncodedInnerDefaultProperty. The value of InnerProperty
indicates that the Name property will be serialized as a nested tag of the
custom control tag.
You can see details of other enumerated values of
DesignerSerializationVisibility and PersistenceMode enumeration in MSDN help.
If you use the FullNameLabel control on a web form you should see its Name
property in the property window as shown below:

Notice how the FirstName and LastName properties appear as sub-properties of
Name property. The serialized markup of the control after the Name property is
set looks like this:
<cc1:FullNameLabel ID="FullNameLabel1"
runat="server" EnableTheming="True">
<Name FirstName="Nancy" LastName="Davalio" />
</cc1:FullNameLabel>
Notice how the Name tag appears as a nested tag of <FullNameLabel> tag.
In the next article of this series I will show how to create custom editors
to set a property value. Stay tuned.