Wednesday, 26 March 2008

Supporting Complex Types in Property Window

ntroduction

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.
 

No comments: