Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Color DropDown Control

0.00/5 (No votes)
27 Jan 2005 2  
An example of persisting and parsing a custom collection in an ASP.NET server control.

Introduction

When developing controls for a designer environment like Visual Studio or WebMatrix, the complexities of persisting control properties as serialized HTML can come into play. In many cases, the built-in serialization of control properties as attributes or nested child tags is sufficient. There are also several code attributes available to a control developer to apply a degree of customization for this process. In some cases however, a developer requires a greater degree of control over how control properties are persisted as HTML.

In this article, I present a custom ASP.NET server control for displaying color choices in a dropdown list. Following a description of the control, I will focus on the problem of persisting a custom collection of items in the context of the Visual Studio Web Form designer, and offer a solution using custom ControlDesigner and ControlBuilder objects.

HtmlColorDropDown Control

The HtmlColorDropDown web control encapsulates an HTML dropdown list (i.e., <select> tag) with colors for items (<option> tags). The control can render the items themselves as foreground or background colors within the <option> tags. The choice of colors offered is determined by the value of the Palette property, which can be one of the following:

AllNamedColors

All named colors (not including System colors) available in the System.Drawing.KnownColor enumeration.

Simple

A small subset of common named colors.

WebSafe

The 216 colors considered "web-safe"; these use only hex values #FF, #CC, #99, #66, #33, and #00 for each of the red, green, and blue components.

UserDefined

Colors identified by the control user through the UserDefinedColors property.

The UserDefinedColors property is a ColorCollection object. ColorCollection is a strongly-typed collection class inheriting from CollectionBase, using standard System.Drawing.Color objects for items.

The DisplayColorMode property governs how colors will be displayed in each item, if at all:

ColorAsBackground

Display each item using its color for the background; foreground text is displayed in either black or white, depending on which offers a better contrast.

ColorAsForeground

Display each item using its color for the foreground text.

Plain

Do not display colors in the items.

The selected item is available as a Color object through the SelectedColor property. The properties SelectedColorName and SelectedColorHexString return the name of the selected color (for named colors) and the HTML hex value (e.g., "#FFC309") as strings respectively. These are all read/write properties; the setting of one affects the value of all three. If an attempt is made to set the SelectedColor to a color that doesn't exist in the current Palette, a value of Color.Empty is assigned instead. Likewise, if the Palette is changed, SelectedColor may be set to Color.Empty if its existing value isn't among the new list of choices.

Note that the proper display of foreground or background colors rendered in <option> tags is dependent on the browser. Most modern browsers interpret item colors identified as CSS properties through a style attribute, without problems. Interestingly however, not all render the colors of the selected item when the dropdown is collapsed. To work around this, the AutoColorChangeSupport property is provided. When true (its default value), a small JavaScript onChange handler is rendered with the control, causing the colors of the selected item to be assigned to the parent <select> tag. The property may be set to false to prevent this behavior.

The default event for the control is ColorChanged. This event fires whenever the selection in the dropdown list has changed between postbacks to the server. The following is an example of an .aspx page where the ColorChanged event is handled:

<%@ Page language="c#" %>
<%@ Register TagPrefix="cc1" Namespace="UNLV.IAP.WebControls" 
             Assembly="UNLV.IAP.WebControls.HtmlColorDropDown" %>
<script runat="server">
   private void HtmlColorDropDown1_ColorChanged(object sender, 
                                           System.EventArgs e)
   {
     Label1.Text = HtmlColorDropDown1.SelectedColorName;
   }
</script>
<html>
  <head>
    <title>Example</title>
  </head>
  <body>
    <form runat="server">
      <cc1:HtmlColorDropDown id="HtmlColorDropDown1" runat="server" 
                  DisplaySelectColorItemText="--Select a Color--"
                  DisplaySelectColorItem="True" 
                  AutoPostBack="True"
                  OnColorChanged="HtmlColorDropDown1_ColorChanged">
      </cc1:HtmlColorDropDown>
      <P>The selected color is: <asp:Label id="Label1" runat="server"/></P>
    </form>
  </body>
</html>

See the downloadable control documentation for a complete description of all HtmlColorDropDown properties and events.

The Issue: Persisting the Collection of UserDefinedColors

This control was designed with both visual designer (e.g., Visual Studio) and text editor (e.g., Notepad) environments in mind. For designer environments, we inherit much functionality without any extra code. Because we're using regular System.Drawing.Color objects for the SelectedColor property for example, the standard ColorBuilder becomes available within the Visual Studio web form designer.

Likewise, as we have standard Color objects within the UserDefinedColors collection, the Visual Studio collection builder is available at no charge:

A problem occurs however when it comes time to serialize this color collection into the server tag HTML. Typically, we would set the PersistenceMode and DesignerSerializationVisibility attributes on the collection property to have the collection items persisted as nested tags, like so:

    [
        PersistenceMode(PersistenceMode.InnerDefaultProperty)
        ,DesignerSerializationVisibility(
             DesignerSerializationVisibility.Content)
    ]
    public ColorCollection UserDefinedColors
    {
        get {...}
        set {...}
    }

With these attributes set, we can get the UserDefinedColors collection persisted. The markup, however, appears as verbose <System.Drawing.Color> tags, like the following:

<cc1:htmlcolordropdown id="HtmlColorDropDown1" runat="server"  
                       Palette="UserDefined">
    <System.Drawing.Color IsEmpty="False" A="255" B="0" IsNamedColor="True" 
        IsKnownColor="True" Name="Red" G="0" R="255" IsSystemColor="False">
    </System.Drawing.Color>
    <System.Drawing.Color IsEmpty="False" A="255" B="0" IsNamedColor="True" 
        IsKnownColor="True" Name="Green" G="128" R="0" IsSystemColor="False">
    </System.Drawing.Color>
    <System.Drawing.Color IsEmpty="False" A="255" B="255" IsNamedColor="True" 
        IsKnownColor="True" Name="Blue" G="0" R="0" IsSystemColor="False">
    </System.Drawing.Color>
</cc1:htmlcolordropdown>

The problem isn't only in the fact that there is much more information about the colors persisted than we need or want. As the attributes represent read-only properties of the Color object, an exception will be thrown upon parsing. It would be preferable to persist these inner items differently, with minimal information necessary, like the following:

<cc1:htmlcolordropdown id="HtmlColorDropDown1" runat="server"  
                       Palette="UserDefined">
    <Color value="Red"/>
    <Color value="Green"/>
    <Color value="Blue"/>
    </cc1:htmlcolordropdown>

This also makes the control easier to work with for those using plain text editors. A custom ControlDesigner can help us here in persisting the inner markup in this simplified manner, while a custom ControlBuilder will help deserialize the <Color value="xxx"/> tags upon parsing.

Custom Persistence through a ControlDesigner

In order to persist the UserDefinedColors collection, we'll define a custom subclass of System.Web.UI.Design.ControlDesigner. A ControlDesigner's purpose is to provide design-time support for a web control, and it can do so in a number of ways. For example, a ControlDesigner can be used to customize the HTML that is used to represent a control in a visual designer like Visual Studio. A ControlDesigner may also be used to add support for the visual editing of control templates.

For the HtmlColorDropDown control, we need the ability to customize the inner HTML that is serialized when a visual designer persists the object's properties to a web page. For this, we can subclass ControlDesigner and override the GetPersistInnerHtml method. The complete code for the subclassed HtmlColorDropDownDesigner is as follows:

public class HtmlColorDropDownDesigner : ControlDesigner
{
    public override string GetPersistInnerHtml()
    {
        StringWriter sw = new StringWriter();
        HtmlTextWriter html = new HtmlTextWriter(sw);

        HtmlColorDropDown dd 
            = this.Component as HtmlColorDropDown;
        if (dd != null)
        {
            // for each color in the collection, output its

            // html known name (if it is a known color)

            // or its html hex string representation

            // in the format:

            //   <Color value='xxx' />

            foreach(Color c in dd.UserDefinedColors)
            {
                string s = 
                    (c.IsKnownColor ? c.Name 
                      : ColorUtility.ColorToHexString(c) );

                html.WriteBeginTag("Color");
                html.WriteAttribute("value", s);
                html.WriteLine(HtmlTextWriter.SelfClosingTagEnd);
            }

        }

        return sw.ToString();

    }
}

In the overridden GetPersistInnerHtml method, we iterate through each System.Drawing.Color item in the UserDefinedColors property. For each color, we output a child tag in the form:

<Color value="xxx">

where xxx is either the name of the color (for known color items such as "Red") or its HTML hex string (e.g., "#FF0000"). Through this custom persistence, we can represent the full UserDefinedColors collection without the problems and verbosity of serializing full Color objects.

It is worthwhile to note that other methods are available for customizing how a control's properties are to be persisted. A TypeConverter, for example, may be defined for custom types, which can then play a role in serialization. For that matter, a custom CodeDomSerializer object can persist object properties as programming code rather than HTML. In the case of the HtmlColorDropDown control, I chose a custom ControlDesigner overriding GetPersistInnerHtml as it offered a simple and direct solution for persisting the contents of the ColorCollection.

The custom designer is associated with the main HtmlColorDropDown control through the Designer attribute. Other attributes are also important in this context. The PersistChildren(false) attribute is applied to the control to ensure that properties are not otherwise persisted as nested server control tags. The PersistenceMode and DesignerSerializationVisibility attributes are still applied to the UserDefinedColors property to ensure that our designer's GetPersistInnerHtml method in fact has a reason to be called.

[
    //...(other attributes)...//

    ,PersistChildren(false)
    ,Designer(typeof(HtmlColorDropDownDesigner))
]
public class HtmlColorDropDown : Control, IPostBackDataHandler 
                     , IPostBackEventHandler, IAttributeAccessor
{
    ...
    [
        //...(other attributes)...//

        ,PersistenceMode(PersistenceMode.InnerDefaultProperty)
        ,DesignerSerializationVisibility(
             DesignerSerializationVisibility.Content)
    ]
    public ColorCollection UserDefinedColors
    {
        get {...}
        set {...}
    }
    ...
}

Custom Parsing through a ControlBuilder

The custom HtmlColorDropDownDesigner takes care of persisting the UserDefinedColors collection as inner HTML content of the server tag. When the page is parsed, three separate classes work together to deserialize this inner content back into the UserDefinedColors collection: a custom ControlBuilder, a helper class representing a <Color value='xxx'/> tag, and the main HtmlColorDropDown control itself.

The first of the three is a subclass of System.Web.UI.ControlBuilder. A ControlBuilder object assists the page parser when building a server control from markup text. The HtmlColorDropDownBuilder is defined as follows with only one overridden method:

public class HtmlColorDropDownBuilder : ControlBuilder
{
    public override Type GetChildControlType(string tagName, 
                                          IDictionary attribs)
    {
        if (string.Compare(tagName,"color", true) == 0)
        {
            return typeof(ColorItemHelper);
        }

        return base.GetChildControlType (tagName, attribs);
    }
}

The custom builder's GetChildControlType method returns the type ColorItemHelper for each <Color value='xxx'> child tag nested within the server control markup. The ColorItemHelper class is defined very simply:

public class ColorItemHelper
{
    private string _value;
    public string Value
    {
        get {return _value;}
        set {_value = value;}
    }
}

The custom builder is assigned to the HtmlColorDropDown control through the ControlBuilder attribute. The main HtmlColorDropDown control then uses the ColorItemHelper object passed to it from the parser to add the color to its UserDefinedColors collection. This is done by overriding the AddParsedSubObject method of the Control class:

[
    //...(other attributes)...//

    , ParseChildren(false)
    , ControlBuilder(typeof(HtmlColorDropDownBuilder))
]
public class HtmlColorDropDown : Control, IPostBackDataHandler 
                     , IPostBackEventHandler, IAttributeAccessor
{
    ...

    protected override void AddParsedSubObject(object obj)
    {
        if (obj is ColorItemHelper)
        {
            ColorItemHelper h = obj as ColorItemHelper;
            this.UserDefinedColors.Add(h.Value);
        }
        else
            base.AddParsedSubObject (obj);
    }

    ...
}

Note that the ParseChildren(false) attribute is applied to the main HtmlColorDropDown class. This ensures that the nested inner HTML is not parsed as properties of the control, allowing our custom parsing to function.

Summary

The HtmlColorDropDown web control renders a standard HTML <select> tag with <option> items representing color choices. The colors themselves may be rendered as the background or foreground color of each item. Three built-in palettes are offered; a fourth, UserDefined, allows a control user to specify which colors to provide as choices in the list. To support the persistence of user defined colors within a visual designer, I used a custom ControlDesigner, overriding the GetPersistInnerHtml method to render colors as simple <Color value='xxx'/> child tags. I also used a custom ControlBuilder to assist in parsing the nested tags for the deserialization of the UserDefinedColors collection. While techniques for custom property persistence and parsing may be tricky, such techniques offer important benefits for control authors and are worth consideration.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here