| Details | ![]() |
| QuickDocId | 307 |
| Written by | Imar Spaanjaars |
| Posted | 06/24/2004 18:17 |
| Modified | 06/29/2004 14:46 |
| Reviewed | 06/06/2006 22:21 |
| Page views | 5670 |
| Listened to | Bursting by Anne Clark (Track 8 from the album: Pressure Points) |
Are you looking to hire an experienced software developer or .NET consultant? Then get in touch with me through my company's web site at devierkoeden.com
Found an interesting article on this site? Got inspired by something you read here? Then consider making a donation with PayPal.
Server controls allow a page developer to drag and drop a page together in no to time. Most of these controls come "out of the box" with ASP.NET like the TextBox, the Label, the DataGrid and many other controls. Changing the appearance and behavior of these controls is often as easy as visually setting a few properties in the properties grid for your design application, like Visual Studio .NET or the Web Matrix.. To make this process even easier, many properties can be expanded and collapsed in the property grid. By collapsing a property like the Font style, you end up with a shorter and much cleaner property list, making it easier to locate and change your properties.
Adding these collapsible properties to your own server controls is possible as well. This article will guide you through the process of creating a simple server control that exposes a collapsible property in the property grid of visual designers like Visual Studio.NET or the Web Matrix.
I'll be using Visual Studio .NET 2003 in this article, so it's handy if you have that around. Don't worry if you have Visual Studio .NET 2002; most of the concepts I explain in this article work on that version as well.
It's also handy if you have a working ASP.NET Web application to test out the control. Since we'll be dealing mostly with design-time issues, this is not really required, but of course it's useful to test out the functionality of your control in a real life application.
For the purpose of this article, I will build design-time support for the the labels of a very simple rating control that allows a user to rate an item on your Web site from 1 to 5. Below the numbers 1 and 5 there will be a short label describing the meaning of the number. The control could look like this:

Figure 1: The Rating Control
To allow a page developer to change the text for the minimum and maximum value, the control will expose an expandable ScaleLegend property. This ScaleLegend property is of type Legend, a custom class that is added to the control project. The Legend class in turns exposes MinimumValueText and a MaximumValueText properties. In the property grid the Legend property will look like this:
Figure 2: The Property Grid for the ScaleLegend Property.
Note that I won't be implementing the actual rating mechanism, nor other properties like the ID of an article the user can rate. This article will focus exclusively on expandable properties. To keep this article simple and focused, all that the control does is output a simple HTML table with two cells: one holding the MinimumValueText and one with the MaximumValueText.
Now that I briefly discussed the Legend property for the control and its designer, let's get to work and create the control.
using System;
namespace Toolkit
{
public class Legend
{
private string minimumValueText = "Sucks";
private string maximumValueText = "Rules";
public Legend() {}
public string MinimumValueText
{
get
{
return minimumValueText;
}
set
{
minimumValueText = value;
}
}
public string MaximumValueText
{
get
{
return maximumValueText;
}
set
{
maximumValueText = value;
}
}
}
}
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace Toolkit
{
[
DefaultProperty("ScaleLegend"),
ToolboxData("<{0}:Rating runat='server'></{0}:Rating>")
]
public class Rating : WebControl, INamingContainer
{
private Legend scaleLegend = new Legend();
[
Browsable(true),
Category("Appearance"),
Description("The Legend that determines the text for " +
"the Minimum and Maximum value of the Rating control.")
]
public Legend ScaleLegend
{
get
{
return scaleLegend;
}
set
{
scaleLegend = value;
}
}
protected override void Render(HtmlTextWriter writer)
{
// Make sure the control is nested in a form
if (Page != null)
{
Page.VerifyRenderingInServerForm(this);
}
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.AddAttribute(HtmlTextWriterAttribute.Align, "left");
writer.RenderBeginTag(HtmlTextWriterTag.Td);
writer.Write("\t" + scaleLegend.MinimumValueText);
writer.RenderEndTag();
writer.AddAttribute(HtmlTextWriterAttribute.Align, "right");
writer.RenderBeginTag(HtmlTextWriterTag.Td);
writer.Write(scaleLegend.MaximumValueText + "\r\n");
writer.RenderEndTag(); //td
writer.RenderEndTag(); //tr
writer.RenderEndTag(); //table
}
}
}
At this point, you have a complete and functional control. It doesn't do much, but it works as expected. The next step is to try out the control by adding it to the design surface of an ASPX page. You can do that by running the control in debug mode, and then adding an instance of it to an ASPX page. For more details how to do this, check out this FAQ.
If you add the control to a page, you'll notice that the ScaleLegend property is not available in the property grid. It's there, but it's grayed out. 
Figure 3: The Read-Only ScaleLegend property in the property grid
This is because the property grid does not know how to handle the Legend class. To tell the property grid how to deal with that class, you'll need to create a TypeConverter. A TypeConverter, as it name implies, allows you to convert types. In our scenario, we'll use it to convert a Legend to individual string properties and vice versa. For a successful TypeConverter, you'll need to override the methods CanConvertFrom, ConvertFrom and ConvertTo. The first method is used to indicate the types of conversions that are supported, the other two are responsible for the actual conversion. To implement the TypeConverter for the Legend class, follow these steps:
using System;
using System.ComponentModel;
using System.Globalization;
namespace Toolkit
{
internal class LegendConverter : ExpandableObjectConverter
{
public override bool CanConvertFrom(
ITypeDescriptorContext context, Type t)
{
if (t == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, t);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo info, object value)
{
if (value is string)
{
try
{
string s = (string) value;
// The value will have the format
// "MinimumTextValue, MaximumTextValue"
// We need to split it on the comma
int comma = s.IndexOf(',');
if (comma != -1)
{
string delimStr = ",";
char [] delimiter = delimStr.ToCharArray();
string [] splits = s.Split(delimiter);
if (splits.Length == 2)
{
string minValue = splits[0].Trim();
string maxValue = splits[1].Trim();
Legend l = new Legend();
l.MinimumValueText = minValue;
l.MaximumValueText = maxValue;
return l;
}
}
}
catch {}
// Whoops. Parsing failed. Throw an error.
throw new ArgumentException(
"Conversion from \"" + (string) value +
"\" to a Legend failed. Format must be" +
"\"MinimumTextValue, MinimumTextValue\"");
}
return base.ConvertFrom(context, info, value);
}
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture, object value, Type destType)
{
if (destType == typeof(string) && value is Legend)
{
Legend l = (Legend) value;
// Build the string as "MinimumTextValue, MinimumTextValue"
return l.MinimumValueText + ", " + l.MaximumValueText;
}
return base.ConvertTo(context, culture, value, destType);
}
}
}
namespace Toolkit
{
[
TypeConverter(typeof(LegendConverter))
]
public class Legend
{
private string minimumValueText = "Sucks";
private string maximumValueText = "Rules";
using System;
using System.ComponentModel;
namespace Toolkit
{
[
Browsable(true),
Category("Appearance"),
Description("The Legend that determines the text for " +
"the Minimum and Maximum value of the Rating control."),
PersistenceMode(PersistenceMode.Attribute),
DesignerSerializationVisibility (DesignerSerializationVisibility.Content)
] public Legend ScaleLegend

<cc1:Rating id=Rating1 runat="server" scalelegend-minimumvaluetext="Hate It!" scalelegend-maximumvaluetext="Love It!"> </cc1:Rating>
Right now, the property works as you'd expect. You can either change the parent property directly, or expand it and change its sub properties. However, the behavior is a little odd; when you change one of the sub properties, the parent is not updated correctly. Also, the designer surface is not updated. Fortunately, the fix is pretty easy. Once again, we need to apply a few attributes to get the desired behavior:
[
Description("The text that is displayed for the Minimum " +
"value of the Rating control."),
NotifyParentProperty(true),
RefreshProperties(RefreshProperties.Repaint)
]
public string MinimumValueText
{
get
Building your own server controls requires a lot of effort. However, once they are ready and thoroughly tested, they can save you and your page developers a lot of work. If you are building your own controls, don't forget to consider (and build) design time support. This article has shown you one aspect of design-time support: the implementation of expandable properties. However, there are many more design-time features to your controls to make them easier and quicker to use. Check out the References section to find out more about server controls in general, and design-time support in particular.
I have focused on expandable properties in this article. However, to make your control even easier to use, you could implement the following improvements.
Applying this attribute will cause the designer to remove the attributes from the tag if their values match the default value. It also allows you to reset the sub property to its default value by right-clicking the property in the designer and choosing Reset. It's not required, but it makes the most sense to set the default value for these properties equal to the default value of the two private variables, minimumValueText and maximumValueText.
The DefaultProperty attribute will cause the designer to put the focus on the ScaleLegend whenever you select the control in the desuigner. This allows you to quickly change the labels for the Legend.
Wonder where to go next?
You can read existing comments below
or you can post a comment yourself on this article.
Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.
I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.
When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.
If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.
For more information about the Talk Back feature, check out this news item.
| QuickDocId | 307 |
| Full URL | http://imar.spaanjaars.com/307/enhancing-design-time-support-in-your-controls |
| Short cut | http://imar.spaanjaars.com/307/ |
| Written by | Imar Spaanjaars |
| Date Posted | 06/24/2004 18:17 |
| Date Last Updated | 06/29/2004 14:46 |
| Date Last Reviewed | 06/06/2006 22:21 |
| Listened to when writing | Bursting by Anne Clark (Track 8 from the album: Pressure Points) |