Click here to Skip to main content
Click here to Skip to main content

ASP.NET Color DropDown Control

, 27 Jan 2005
Rate this:
Please Sign up or sign in to vote.
An example of persisting and parsing a custom collection in an ASP.NET server control.
Prize winner in Competition "ASP.NET Jun 2004"

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Mike Ellison

United States United States
I work for the University of Nevada, Las Vegas in the Office of Institutional Analysis and Planning. Among other things, our office is charged with the mission of deriving useful information in support of administrative decision-making from institutional data. Within the context of that mission, my office mates and I apply technology in the form of custom data processing applications, data extraction and analysis tools, reporting tools, relational databases, OLAP solutions, data warehousing, and data mining.
 
Visit my blog at MishaInTheCloud.com


Comments and Discussions

 
QuestionAwesome PinmemberRob_Jurado16-Jan-14 12:29 
Questioncolor palette Pinmemberjyotianand14-Apr-13 8:19 
GeneralMy vote of 5 Pinmembermanoj kumar choubey3-Feb-12 19:34 
GeneralMy vote of 5 PinmemberSunasara Imdadhusen2-Jul-11 0:22 
QuestionHow to create nested TAG component PinmemberSunasara Imdadhusen2-Jul-11 0:22 
QuestionHow do you set a custom name for a user defined color? PinmemberMember 454777024-Sep-09 2:26 
QuestionFlat DropDownList Pinmemberpriyaar10-Apr-09 22:17 
Questioncan not work in listview of VS2008 Pinmemberjxnchk26-Feb-08 21:01 
AnswerRe: can not work in listview of VS2008 PinmemberMike Ellison28-Nov-08 4:45 
GeneralAttaching to an asp.net validation control... PinmemberMember 45342466-Feb-08 6:34 
GeneralRe: Attaching to an asp.net validation control... PinmemberMike Ellison28-Nov-08 4:43 
QuestionHow Set Color For HtmlColorDropDown in Server code? Pinmemberzohreh_r10-Dec-07 19:48 
AnswerRe: How Set Color For HtmlColorDropDown in Server code? PinmemberMike Ellison28-Nov-08 4:42 
QuestionApplying a collection property Pinmemberdekelc27-Sep-07 16:54 
AnswerRe: Applying a collection property PinmemberMike Ellison23-Oct-07 8:07 
QuestionBackground color changes when mouse over Pinmemberpalulu29-Aug-07 8:13 
AnswerRe: Background color changes when mouse over PinmemberMike Ellison23-Oct-07 8:09 
General4-pixel margin around checkboxes Pinmemberstdtime25-Apr-07 15:58 
GeneralRe: 4-pixel margin around checkboxes PinmemberMike Ellison26-Apr-07 5:51 
QuestionNeed idea for parsing PinmemberRamesh.A19-Jul-06 4:35 
AnswerRe: Need idea for parsing PinmemberMike Ellison19-Jul-06 6:02 
GeneralRe: Need idea for parsing PinmemberRamesh.A19-Jul-06 20:15 
GeneralRe: Need idea for parsing PinmemberMike Ellison20-Jul-06 6:02 
GeneralRe: Need idea for parsing PinmemberRamesh.A21-Jul-06 22:24 
GeneralRe: Need idea for parsing PinmemberMike Ellison22-Jul-06 7:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140821.2 | Last Updated 27 Jan 2005
Article Copyright 2004 by Mike Ellison
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid