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

The ScrollableListBox Custom Control for ASP.NET 2.0

, 13 Jun 2006
Rate this:
Please Sign up or sign in to vote.
A ListBox control with horizontal scrolling capability.

ScrollableListbox

Introduction

I have been searching the web looking for a list box control which supports horizontal scroll and behaves exactly like the ASP.NET ListBox. I found lintom's article which causes us to handle each ListBox separately without providing reuse capabilities. I also found the XList control, which is a reusable control but it doesn't behave exactly like the ordinary ASP.NET ListBox control. After a while, I decided to develop my own control. In this article, I'll present the ScrollableListBox custom control I've created using C# and ASP.NET 2.0 technologies. The ScrollableListBox, which derives from ListBox, supports a horizontal scroll bar, and yet behaves like the ASP.NET ListBox control.

[ToolboxBitmap(typeof(Evyatar.Web.Controls.ScrollableListBox), 
               "Evyatar.Web.Controls.ScrollableListBox.bmp")]
public class ScrollableListBox : ListBox
{
    ...
}

Adding a horizontal scroll bar

The main idea behind adding a horizontal scroll bar is to wrap the <SELECT> HTML element (that represent the ListBox) with a <DIV> HTML element and let the <DIV> to deal with the scroll bars. This approach leads us to put the Height and the Width attributes of the control on the <DIV> element. To keep it behave like the ASP.NET ListBox, we need to drill down and ask ourselves how browsers define the measurements of the ListBox or the <SELECT> HTML element? In the case of Internet Explorer, the answer to this question is: the width is defined by the longest ListItem or <OPTION> HTML element, unless there is an explicit definition of width. The height is defined by the Rows property of the ListBox which renders to the size attribute of the <SELECT> HTML element, unless there is an explicit definition of height.

In order to eliminate the Height and the Width attributes from the <SELECT> HTML element, we should override the Height and the Width properties as follows:

public override Unit Width
{
  get
  {
     object o = ViewState["Width"];
     if (null == o)
        return Unit.Empty;
     return (Unit)o;
  }
  set
  {
     if (value.Value < 0)
     {
        throw new ArgumentOutOfRangeException("value");
     }
     ViewState["Width"] = value;
  }
}

public override Unit Height
{
  get
  {
     object o = ViewState["Height"];
     if (null == o)
        return Unit.Empty;
     return (Unit)o;
  }
  set
  {
     if (value.Value < 0)
     {
        throw new ArgumentOutOfRangeException("value");
     }
     ViewState["Height"] = value;
  }
}

In order to wrap the <SELECT> HTML element with the <DIV> HTML element, we should override the Render method as follows:

protected override void Render(HtmlTextWriter writer)
{
   writer.Write(string.Format("<div style='OVERFLOW-X:" + 
                " auto;OVERFLOW-Y: auto; {0}' id='{1}'>", 
                AddStyleAttributesToRender(), ClientID + "_div"));
   base.Render(writer);
   writer.Write("</div>");
}

private string AddStyleAttributesToRender()
{
  StringBuilder sb = new StringBuilder("", 16);

  if (!Width.IsEmpty)
     sb.Append(string.Format("WIDTH: {0};", 
               Width.ToString(CultureInfo.InvariantCulture)));
  if (!Height.IsEmpty)
     sb.Append(string.Format("HEIGHT: {0};", 
               Height.ToString(CultureInfo.InvariantCulture)));

  return sb.ToString();
}

The Render method firstly renders the start tag of the <DIV> HTML element, indicating the height and the width of the <DIV> and its horizontal and vertical scrollbars behavior when the <DIV> content overflows. Then, we render the <SELECT> HTML element by calling the Render method of the ListBox control (which is the base class). Here, we should be aware that the ListBox's Render method will not render the Height and the Width attributes. The reason for that is while we are setting a value to the Height or the Width properties, we aren't passing this value to the base class. Finally, we are rendering the end tag of the <DIV> HTML element.

We are almost done, but we still need to deal with the browser definition of the control measurements:

Scenario Current behavior Expected behavior
There is no explicit definition of the control Width. The <SELECT> width is determined by the longest <OPTION> element. But, the <DIV> width is determined to be 100% (the default behavior of Internet Explorer). The <DIV> width should be determined like the <SELECT> HTML element.
There is explicit definition of the control Width. The <SELECT> width is determined by the longest <OPTION> element. The <DIV> width is determined according to the control Width definition. In case the longest <OPTION> element is wider than the explicit definition of the control Width, a horizontal scroll bar will appear. In case the longest <OPTION> element is wider than the <DIV> element, the current behavior is also the expected behavior. Otherwise, the <SELECT> element should be stretched to fill the <DIV> area.
There is no explicit definition of the control Height. The <SELECT> height is determined by the Rows property of the control. The <DIV> height is determined by its content. In case the Rows property is less than the number of list items, a vertical scroll bar will appear on the <SELECT> HTML element. In case the amount of list items is less than the Rows definition, no vertical scroll bar should appear. Otherwise, a vertical scroll bar should appear on the <DIV> element instead of the <SELECT> element. The amount of visible items should be according to the Rows property even though there is a horizontal scroll bar (that may appear and consume area).
There is explicit definition of the control Height. The <SELECT> height is determined by the Rows property of the control. The <DIV> height is determined by the Height property of the control. In case the Rows property is less than the number of list items, a vertical scroll bar will appear on the <SELECT> HTML element. In case the <DIV> height is exceeding the <SELECT> height, there is a gap between the <DIV> and its contents. The <SELECT> element shouldn't contain vertical scroll bars. In addition, no gap should be there between the <DIV> element and its contents.

To solve the above issues, we need to know what the client width and the client height of the rendered <SELECT> HTML element is. That information is only available in the client side after the control is rendered. So, in order to solve this issue, we are going to inject a JavaScript code that is invoked right after the control is rendered.

First of all, let's look at the JavaScript code that will invoked. The __ScrollableListBoxRefineHeightAndWidth function tests the above scenarios, and makes sure the the control will behave as expected.

function __ScrollableListBoxRefineHeightAndWidth(list_id)
{
    var list = document.getElementById(list_id);
    var div = document.getElementById(list_id + '_div');
    if (div.style.height) 
    {
        // div has height we will expand the list to fit the div's height.
        // In addition we will make sure the vertical
        // scroll bar of the list will not
        // displayed
        list.size = list.options.length;
        while (list.clientHeight < div.clientHeight)
            list.size = list.size + 1;
    }
    if (div.style.width)
    {   
        if (div.clientWidth > list.clientWidth)
        {
            // div is wider than list. We will
            // wide the list to fill the div area
            list.style.width = div.clientWidth;
        }
        if (!div.style.height)
        {
            // div have no height this is mean the height is defined by list rows.
            // We will set the div height to be like the list height.
            div.style.height = list.offsetHeight + 
                     (list.offsetHeight - div.clientHeight);
             
            // now after we set the div height,
            // we need to remove the vertical scroll bar of the list 
            // (it will exists when the size of the list is less than the number 
            // the amount of list's items).
            // We are doing it by set the list rows 
            if (list.size < list.options.length)
            {
                list.size = list.options.length;
            }
            else
            {
                // here we will not need the vertical
                // scroll bar. therefore we will hide it.
                div.style.overflowY = 'hidden';
            }
        }
    }
    else 
    {
        // div has no width. This is mean the width will be set 
        // by the longest list's item. Therefore we will
        // set the div width like the list offset.
        div.style.width = list.offsetWidth;
        // here we will not need the horizontal scroll bar.
        // therefor we will hide it.
        div.style.overflowX = 'hidden';
     }
}

Now, we should inject the above code into the client browser, and inject the call to the __ScrollableListBoxRefineHeightAndWidth function right after the control rendering. In order to keep a clean separation between the server side code and the client side code, I put the __ScrollableListBoxRefineHeightAndWidth on a separate file called ScrollableListBox.js. That file is compiled as an embedded resource in my control library project. Now, all we have to do is to register the include script to the ScrollableListBox.js file, and to register the startup script that calls to the __ScrollableListBoxRefineHeightAndWidth function:

protected override void OnInit(EventArgs e)
{
  ClientScriptManager cs = Page.ClientScript;
  Type rsType = this.GetType();
  if (!cs.IsClientScriptIncludeRegistered("ScrollableListBox"))
  {
     cs.RegisterClientScriptInclude("ScrollableListBox", 
        cs.GetWebResourceUrl(rsType, 
          "Evyatar.Web.Controls.JavaScript.ScrollableListBox.js"));
  }
  cs.RegisterStartupScript(rsType, "ScrollableListBoxStartup" + this.UniqueID,
     string.Format("__ScrollableListBoxRefineHeightAndWidth('{0}');", 
                   this.ClientID), true);

  base.OnInit(e);
}

Resuscitation of appearance properties

When I examined the ListBox and its rendering on Internet Explorer, I noticed that the BorderColor, BorderStyle, and BorderWidth properties have no effect on the appearance of the ListBox control, even though they still render within the STYLE attribute of the <SELECT> HTML element. This is the reason why those properties are not supported at design time.

Because the ScrollableListBox is rendered to the <SELECT> HTML element wrapped by the <DIV> HTML element, we want to render the appearance properties within the STYLE attribute of the <DIV> HTML element. In addition, we want to add design time support to those properties.

[Browsable(true)]
[Category("Appearance")]
[Description("Color of the border around the control"), 
             DefaultValue(typeof(Color),"")]
[TypeConverter(typeof(WebColorConverter))]
public override Color BorderColor
{
  get
  {
     object o = ViewState["BorderColor"];
     if (null == o)
        return Color.Empty;
     return (Color)o;
  }
  set
  {
     ViewState["BorderColor"] = value;
  }
}

[Browsable(true)]
[DefaultValue(BorderStyle.NotSet)]
[Category("Appearance")]
[Description("Style of the border around the control")]
public override BorderStyle BorderStyle
{
  get
  {
     object o = ViewState["BorderStyle"];
     if (null == o)
        return System.Web.UI.WebControls.BorderStyle.NotSet;
     return (BorderStyle)o;
  }
  set
  {
     ViewState["BorderStyle"] = value;
  }
}

[Browsable(true)]
[Description("Width of the border around the control")]
[Category("Appearance")]
[DefaultValue(typeof(Unit), "")]
public override Unit BorderWidth
{
  get
  {
     object o = ViewState["BorderWidth"];
     if (null == o)
        return Unit.Empty;
     return (Unit)o;
  }
  set
  {
     if (value.Value < 0)
     {
        throw new ArgumentOutOfRangeException("value");
     }
     ViewState["BorderWidth"] = value;
  }
}

Now, let's fix the AddStyleAttributesToRender method in order to support the appearance properties as well:

private string AddStyleAttributesToRender()
{
  StringBuilder sb = new StringBuilder("", 16);

  if (!Width.IsEmpty)
     sb.Append(string.Format("WIDTH: {0};", 
               Width.ToString(CultureInfo.InvariantCulture)));
  if (!Height.IsEmpty)
     sb.Append(string.Format("HEIGHT: {0};", 
               Height.ToString(CultureInfo.InvariantCulture)));
      if (! BorderColor.IsEmpty) 
     sb.Append(string.Format("BORDER-COLOR: {0};", 
               ColorTranslator.ToHtml(BorderColor)));
  if (!BorderWidth.IsEmpty)
     sb.Append(string.Format("BORDER-WIDTH: {0};", 
               BorderWidth.ToString(CultureInfo.InvariantCulture)));
  if (BorderStyle != BorderStyle.NotSet)
     sb.Append(string.Format("BORDER-STYLE: {0};", BorderStyle.ToString()));

  return sb.ToString();
}

Using the code

In case you are an ASP.NET programmer, you should be familiar with the ListBox control. You will probably be happy to notice that the ScrollableListBox control is derived from the ListBox control. Therefore, all the capabilities of ListBox, such as data binding, ViewState, event notifications, etc., will remain as is when you are using the ScrollableListBox control. All you need to do in order to use the ScrollableListBox control is to add it into the toolbox and drag it into the page. From now on, you can use it the same way you use the ListBox control.

About the demo project

The demo project lets you examine and compare the behavior of the ListBox control and the ScrolllableListBox control. Run the project, and use the demo project page to define the control properties such as Height, Width, Rows, BorderColor, BorderStyle, and BorderWidth. In addition, try to add long items to the lists when you are defining an explicit Height and when you are not doing that.

Summary

The ScrollableListBox, which derives from ListBox, supports a horizontal scroll bar, and yet behave like the ASP.NET ListBox. In addition, it resuscitates a few appearance properties which are not supported by the ASP.NET ListBox control.

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

Share

About the Author

Evyatar Ben-Shitrit
Web Developer
Israel Israel
I am a 28 years old, happily married and live in peace in Jeruslem, Israel. I have been study at computer science depratment of Hadassah college Jerusalem.

I am employed as an application develoder on Verint corporation.

Comments and Discussions

 
Suggestionscroll bar code without DLL Pinmemberkrishna_comm14-Mar-12 19:06 
GeneralProblem with Atlas Ajax PinmemberMember 4037947-Feb-09 21:30 
QuestionProblem with Postback with List PinmemberLeela_MD22-Jul-08 21:19 
GeneralProblem with Postback PinmemberLeela_MD22-Jul-08 21:17 
GeneralAdvanced ListBox Component Pinmemberdanludwig21-Jun-07 4:36 
Generalproblem PinmemberCurtis15624-Apr-07 9:22 
QuestionRe: problem PinmemberEvyatar Ben-Shitrit26-Apr-07 20:58 
AnswerRe: problem Pinmemberigon_ghost26-Sep-07 3:54 
Questionhow to get Multi-Column with feature? PinmemberRenugopal19-Apr-07 3:55 
AnswerRe: how to get Multi-Column with feature? PinmemberEvyatar Ben-Shitrit23-Apr-07 4:14 
Hi Renu,
 
I think it will be useless to extend existing .net web control to implement multi-column list. In order to implement that I am recomending to create composite control that composite the repeater control. yet you'll need to implement all client side logic while the rendering layout will come from the repeater.
 
Evyatar Ben-Shitrit

GeneralIssue in firefox Pinmembersp3c1311-Jan-07 12:57 
GeneralRe: Issue in firefox PinmemberEvyatar Ben-Shitrit12-Jan-07 2:43 
QuestionCompiling for ASP.NET 1.1 Pinmemberserioga3-Dec-06 21:27 
QuestionWhy cannot find function __ScrollableListBoxRefineHeightAndWidth? PinmemberChunShahab11-Sep-06 8:44 
AnswerRe: Why cannot find function __ScrollableListBoxRefineHeightAndWidth? PinmemberChunShahab11-Sep-06 10:53 
GeneralRe: Why cannot find function __ScrollableListBoxRefineHeightAndWidth? PinmemberEvyatar Ben-Shitrit12-Sep-06 8:18 
GeneralRe: Why cannot find function __ScrollableListBoxRefineHeightAndWidth? PinmemberChunShahab13-Sep-06 4:35 
GeneralError message poping up Pinmemberbazn8r10-Sep-06 21:07 
GeneralRe: Error message poping up PinmemberChunShahab11-Sep-06 8:56 
GeneralRe: Error message poping up PinmemberEvyatar Ben-Shitrit12-Sep-06 7:43 
GeneralRe: Error message poping up [modified] Pinmemberbazn8r12-Sep-06 18:36 
GeneralRe: Error message poping up Pinmemberbazn8r13-Sep-06 15:31 
GeneralRe: Error message poping up Pinmemberbazn8r13-Sep-06 18:12 
GeneralRe: Error message poping up Pinmemberbazn8r13-Sep-06 18:58 
GeneralRe: Error message poping up PinmemberEvyatar Ben-Shitrit13-Sep-06 22:29 
GeneralRe: Error message poping up Pinmemberbazn8r14-Sep-06 13:40 
GeneralJavascript Injection PinmemberEran Nachum20-Aug-06 5:21 
GeneralRe: Javascript Injection PinmemberEvyatar Ben-Shitrit20-Aug-06 7:31 
QuestionError in parsing value for property 'width'. Declaration dropped. PinmemberK32111-Aug-06 2:30 
GeneralVery nice! Pinmembereliramd27-Jul-06 1:33 
GeneralRe: Very nice! [modified] PinmemberEvyatar Ben-Shitrit28-Jul-06 4:18 
GeneralVery nice article PinmemberCoder of the life24-Jun-06 7:49 
GeneralRe: Very nice article PinmemberEvyatar Ben-Shitrit25-Jun-06 3:29 
QuestionNicely Written Article Pinmembergraemea10022-Jun-06 10:52 
AnswerRe: Nicely Written Article PinmemberEvyatar Ben-Shitrit23-Jun-06 3:12 
Questiontool tip? PinmemberXapp20-Jun-06 3:55 
AnswerRe: tool tip? PinmemberEvyatar Ben-Shitrit21-Jun-06 3:31 
GeneralRe: tool tip? PinmemberXapp21-Jun-06 4:55 
AnswerRe: tool tip? PinmemberEvyatar Ben-Shitrit21-Jun-06 5:09 
GeneralcssClass attribute + elimination of inner border PinmemberRan Davidovitz15-Jun-06 17:37 
AnswerRe: cssClass attribute + elimination of inner border PinmemberEvyatar Ben-Shitrit15-Jun-06 20:02 

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
Web03 | 2.8.140821.2 | Last Updated 13 Jun 2006
Article Copyright 2006 by Evyatar Ben-Shitrit
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid