Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Postback-less, Designer Supported ASP.NET 2.0 Color Picker Custom Control

4.82/5 (23 votes)
11 Sep 2008CPOL6 min read 3   1.6K  
The article explains the detailed steps of creating a designer supported postback-less custom control that will help to build your own.
Image 1

Introduction

There are plenty of JavaScript color picker controls available out there, but none them are easy to distribute and fully designer supported. Moreover, integrating a JavaScript color picker to an ASP.NET app is a really awful experience. For this reason, I really felt a need for a color picker control in my toolbox that is easy to use but yet powerful. My idea of a color picker was to give the user a bundled control which he/she can easily drag and drop from the toolbox without writing a single line of code. Also, the control should be such that it doesn't postback every time a color is chosen.

This article is not all about a color picker, but I have tried to give an overview of creating and deploying a custom control in ASP.NET 2.0.

Requirements

Nothing is required in using this control, except you must have one of the Visual Studio 2005 versions that are available out there. Also, I have packaged the control as a VSI file. Therefore, if you have a management studio express installed in your PC, then the VSI package may not install properly. The reason is that it overrides some registry settings. So, you might need to go through the following steps:

  1. Under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Packages\
    {36839529-3AF3-47fa-8194-F4A3FA9F0ED7}
    , change the CodeBase value to use the Visual Studio 8.0 location, for e.g. file:///D:\Program Files\Microsoft Visual Studio 8\Common7\IDE\Microsoft.VisualStudio.ToolBoxControlInstaller.dll.
  2. Under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Packages\
    {36839529-3AF3-47fa-8194-F4A3FA9F0ED7}\SatelliteDll
    , change the Path value to point to the Common7\IDE folder, for e.g. D:\Program Files\Microsoft Visual Studio 8\Common7\IDE\.
  3. Open the Visual Studio command prompt, and run devenv /setup.

Or, you can just reference the colorpickerLib.dll from the "Add a toolbox item" option in the Tools menu of Visual Studio, to include the control in your toolbox.

Behind the Control

The first thing when building the control is to construct the color matrix, which is pretty simple. Create three variables r, g, and b. Set the initial value to five, and construct a nested loop that decreases each of the values on each iteration. Convert the value to HEX, and put the value inside an appropriate row-column position. Here, horizontally, it will have 18 boxes, and vertically, 12 boxes. The colors have been divided up into two sets, so that the lighter set fills up the first part and the darker color set fills the other half.

JavaScript
int onrow = 1;
int oncol = 1;
int count = 1;
string s = string.Empty;
string set1 = string.Empty;
string set2 = string.Empty;
for (int r = 5; r >= 0; r--)
{
    for (int g = 5; g >= 0; g--)
    {
        for (int b = 5; b >= 0; b--)
        {
            if (oncol == 1) s += "<tr>\n";
            s += "\t<td onmouseover=\"colorOver(this);\" " + 
                 "onclick=\"colorClick(this);\" style=\"cursor:pointer;" +
                 "background:#" + ReturnHex(r) + ReturnHex(g) + 
                 ReturnHex(b) + ";\" class=cc></td>\n";
            oncol++;
            if (oncol >= 19)
            {
                s += "</tr>\n";
                oncol = 1;

                if (onrow % 2 == 0)
                {
                    set2 += s;
                }
                else
                {
                    set1 += s;
                }
                s = "";
                onrow++;
            }
            count++;
        }
    }
}

Now, the challenging part. I have to render the matrix in a floating div with a possible high z-index, so that other controls do not overlap it. Also, I have to make sure that when the user chooses a color, it fills up the textbox with the appropriate HEX value without posting the page back to the server. To achieve this, I have created a JavaScript file that has a function which takes in HTML and a div ID (where it floats the matrix), which I have embedded in the resource file along with its stylesheet.

For embedding the resource and for getting the reference of the JS file, I had to do the following:

  1. Right click on the file, set the build action to embedded resource.
  2. Add a line ([assembly: WebResource("ColorPickerLib.default.js", "text/javascript", PerformSubstitution = true)]) to the assemblyInfo.cs file.

Now, ASP.NET has a cool function called GetWebResourceUrl that extracts the contents out from an assembly at runtime. By using that inside the scope of the CreateChildControls method, I have added the following lines which do the task of including the necessary JS script to the hosting page:

JavaScript
ClientScriptManager cs = Page.ClientScript;
cs.RegisterClientScriptInclude("popscript", 
   cs.GetWebResourceUrl(this.GetType(), 
   "ColorPickerLib.default.js"));

So, I have created the color matrix, and created the JS script to render it off. Now, I have to wrap in the stuff which includes creating an image button, floating the matrix div, and setting the color value from the matrix to an appropriate text box (without postback). To achieve that, I have separated the custom control into two parts, one that will hold the image button, and will render the include script and the content div within which the matrix HTML will be rendered. My target was to separate the matrix rendering logic from the main process, so that it will be easier to maintain. Although this is not necessary, I could have achieved that by creating a matrix rendering method that would have returned the same HTML. But, in that case, I won't be able to use the ASP.NET 2.0 composite control features. For example, I would manually have to write the persistence logic for the data controls.

So, first, we have to get the contents from the matrix rendering control, which is in ColorPickerPalate.cs. In each Web control, there is method called RenderContents which takes a reference to HtmlWriter as a parameter. If you create an HtmlWriter object and pass it to the RenderContents method, it will then be populated with the control's HTML output. So, I used this trick to write the control's content in a string. And then, I rendered the string inside a floating div.

JavaScript
StringWriter builder = new StringWriter();
Html32TextWriter writer = new Html32TextWriter(builder);
ColorPickerPalate details = new ColorPickerPalate(this);
details.RenderControl(writer);
builder.Close();

string popup = builder.ToString();
popup = popup.Replace("\r", string.Empty);
popup = popup.Replace("\n", string.Empty);
popup = popup.Replace("\t", string.Empty);

popup = HttpUtility.HtmlEncode(popup);

Here, you must have noticed that I encoded the HTML output. This is to avoid JavaScript errors (I get a JavaScript error on calling the showDiv function because of the HTML tags), so before passing the HTML, I encode it and then pass it through the showDiv function.

JavaScript
// Inside showDiv Function (Part of the code)

_Source = docSource;      
document.getElementById(element).innerHTML = divText;
displayFloatingDiv(element, "Color Picker", 300, 200, x, y);

Inside the displayFloatingDiv function, the content is decoded and set to the floating div.

JavaScript
originalDivHTML = document.getElementById(divId).innerHTML;
originalDivHTML = decode(originalDivHTML);
document.getElementById(divId).innerHTML =  
   addHeader + originalDivHTML; //+ originalDivHTML;

Finally, I have to link the showDiv function, and also, I have to make way so that when a user clicks on the matrix, the return value goes to the appropriate text box. And here, I did a simple trick, which is inside the CreateChildControl method. I added a function for finding the client ID of the assigned control. Actually, the function traverses the page control hierarchy to find the control for which the server side ID is given. Once the control is found, it then passes the client ID to the JavaScript function, which stores the client ID in a global variable of the control's scope (if there are multiple controls, the things won't get messed up).

Inside the FindControl, code goes like this:

JavaScript
if (Root.ID == Id)
    return Root;

foreach (Control Ctl in Root.Controls)
{
    Control FoundCtl = FindControlRecursive(Ctl, Id);

    if (FoundCtl != null)

        return FoundCtl;

}
return null;

And inside the CreateChildControl, I must have the following block of code:

JavaScript
if (!base.DesignMode)
{
    try
    {
        TextBox controlToFind = null;

        if (this.Page.Master != null)
        {
            controlToFind = FindControlRecursive(this.Page.Master, 
                            _Control.ID) as TextBox;
        }
        else
        {
            controlToFind = FindControlRecursive(this.Page, 
                            _Control.ID) as TextBox;
        }

        docSource = controlToFind.UniqueID;
        string popup = CreateColorTemlate();

        _Link.Attributes.Add("onclick", "showDiv( 'windowContent','" + 
                             docSource + "', \"" + popup + "\")");
    }
    catch
    {
        throw new Exception("ControlToSet Property can not be left blank");
    }

Also, I have to traverse separately the Page and Page.Master, as in the master page, the control hierarchy is found in Page.Master. In that case, the Page.Controls is null. Therefore, I should have a condition for that.

Deploying the Control

In ASP.NET 2.0, you can distribute your custom controls easily, by creating a VSI file. The process is pretty simple. Just create a folder, put all the contents inside, and create a manifest that holds the information of the contents. Finally, zip the folder, and change the file extension from ZIP to VSI. That's it.

Inside the manifest, in the case of a custom control, I have to write the following information:

XML
<?xml version="1.0" encoding="utf-8" ?>
<VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">

    <Content>
        <FileName>ColorPickerLib.dll</FileName>
        <DisplayName>Color Picker</DisplayName>
        <Description>Postback less easy to use color picker</Description>

        <FileContentType>Toolbox Control</FileContentType>
        <ContentVersion>2.0</ContentVersion>
    </Content>
</VSContent>

Usage

Drag and drop the control from the toolbox to the page. From the list of textboxes, choose the appropriate one in the ControlToSet property. Press F5.

Revision

  • July 11, 2006 - Initial release with full IE support
  • Nov 11, 2006 - Patch (Fix for Mozilla)
  • Aug 20, 2008 - Master page fix (Firefox and IE)[User request]
  • Sept 11, 2008 - Demo updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)