Introduction

Like Evyatar Ben-Shitrit says in the introduction to his article The ScrollableListBox Custom Control for ASP.NET 2.0, I too started out searching for an ASP.NET ListBox control that supports horizontal scrollbars. Like him, I too found several solutions and code snippets (including his) which, as helpful as they were, did not quite suit my requirements.

At first I thought I just wanted a ListBox that could scroll horizontally. Then I wanted a ListBox that would also adjust its scroll position when the up and down arrow keys were used to cycle through the items. Then I wanted a ListBox that would adjust its scroll position when someone used other keys (like letters and numbers) to cycle through the items. Once I had those requirements down, I wanted a ListBox that would remember its vertical and horizontal scroll position and readjust itself after a postback (asynchronous or otherwise). Finally, I wanted this ListBox to be able to exist as a normal Microsoft System.Web.UI.WebControls.ListBox by default, so that the control itself would determine its own rendering strategy using its Width, BorderXyz, and Style properties. The resulting control is what this article is about.

Background

I started out searching and immediately found lintom's article Finally a Horizontal Scroll Bar List Box in ASP.NET! Through that article I learned about the strategy of wrapping the ListBox inside a DIV and setting its OVERFLOW style to AUTO. This made it possible to get the horizontal scrollbars needed. However, when using the keyboard to traverse the items in the ListBox, the DIV did not automatically scroll. When a user selected an item that was not in the scroll window, they were out of luck. My next search yielded Shiby Chacko's article Horizontal scroll bar in a combo box or a listbox with up/down arrow key press functionality. Shiby was helpful in showing how client script can be used to overcome the problem in lintom's article. The best part of Shiby's article though was a comment left at the bottom of it by a user named zivros. It contained client script that could be used to make the DIV adjust its scroll position after any keypress event (not just the up & down arrow keys), even if the DIV element received the keypress event in lieu of the ListBox. That was a great contribution zivros, thanks for posting it. Finally, Evyatar's article addressed the idea of passing Width, BorderXyz, and other attributes from the ListBox to its containing DIV by overriding rendering methods in a custom server control.

What I can bring to this series of improvements upon a horizontally-scrollable ListBox is my familiarity with using the ASP.NET AJAX Extensions to add client functionality to server controls. If you've never implemented the System.Web.UI.IScriptControl interface or created client script prototypes for ASP.NET 2.0 server controls, don't worry, we're going to cover that. If you have a hard time following along, these 2 articles helped me get a good grasp on the core concepts:

A Comprehensive Solution

The first thing we need to do is create a new server control that extends Microsoft's System.Web.UI.WebControls.ListBox control and implements the System.Web.UI.IScriptControl interface. This is simple enough:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace DanLudwig.Controls.Web
{
publicclass ListBox : System.Web.UI.WebControls.ListBox,
System.Web.UI.IScriptControl
{
// all the server code we need will go right here
}
}

As for configurable properties, our ListBox control will have three: HorizontalScrollEnabled, ScrollTop, and ScrollLeft. Notice that the default value of the HorizontalScrollEnabled property is false, meaning it must be explicitly enabled in an ASPX page in order to take advantage of the new features we will create. The ScrollTop and ScrollLeft properties are here to provide simplified wiring between the server control and the corresponding client script so that the ListBox will remember its scroll position between postbacks. Also note that if EnableViewState is false on the control or one of its parents, the scroll properties won't be persisted between postbacks. I've also added a get accessor to provide uniform access to the ScriptManager, which will be needed later.

At this point, most other AJAX Extensions primer articles I've read discuss how to implement the System.Web.UI.IScriptControl interface in the extended control. This article will instead discuss the client script file first. If you're following along, you've already created a ListBox.cs file in your App_Code directory. In the root of your web site project, create a JScript file called ListBox.js. This file will automatically contain all the client script we need for the control when the ASPX page is rendered, as long as it contains a ScriptManager. There will be no need to manually include any script tags in the ASPX page or write any ASPX codebehind whatsoever. We do have to carefully follow Microsoft's specification though, which entails the following major steps:

Register the client control's namespace (1 line of code).

Define the client control's class.

Define the class prototype.

Override / implement the initialize method of Sys.UI.Control.

Override / implement the dispose method of Sys.UI.Control.

Define the event handlers.

Define the property get and set methods.

Optionally enable JSON Serialization (not required).

Register the client control as a type that inherits from Sys.UI.Control (1 line of code).

This file doesn't give us any real functionality yet, but it defines the necessary parts so that the client control (the client JavaScript code specific only to our ListBox control) can interface with the ScriptManager. Now, if we implement the System.Web.UI.IScriptControl methods on the server control, and override both its OnPreRender and Render methods, we can finally wire up our server control properties to their corresponding client control properties.

This should get rid of those pesky compiler errors telling you to implement the interface you declared for the class earlier. If you're following along with our own web site project, the code discussed so far should compile and render fine at this point (though there isn't any real functionality yet). The server control will set properties on the client control, but we still have to put them to use. Be warned though, from here forward, testing the control in an ASPX page may produce funky results until we reach the end of the article.

Render a DIV around the ListBox... or not

So far we've done the common things you'll need to do every time you create a new AJAX-enabled server control with client events. The client events are what will be specific to your control. In the case of our ListBox, the client-specific events we want to handle and respond to are when users scroll the DIV element that will contain the ListBox, or when they use keystrokes to navigate the items. But where is this DIV container? Do we need to include a DIV element or an ASP Panel control around our ListBox in the ASPX page? Ideally, the control should use its HorizontalScrollEnabled property to determine whether or not it should render a DIV around itself. We also need to render a hidden Form field to pass client state data back to the server control on postbacks (to maintain scroll state).

Another thing we should do while we're rendering is pass certain characteristics of the ListBox to the DIV container. For example, if width or border styles are applied to our ListBox from the ASPX page, we want to pass those up to the DIV element. This can be a little tricky, and I might not have it exactly right, but the following code gets the job done for all of the styles I apply to this ListBox in my applications. Read the code comments to see exactly what's going on while we're rendering the control.

This is almost all of the code we need in the server control. The only thing that's missing is what the server control does when it receives new scroll position information from the client during a postback. As it turns out, all we need to do is extract the data passed by our hidden field and apply its values to our respective ScrollTop and ScrollLeft properties in the server control. Then, when IScriptControl.GetScriptDescriptors() is executed, it will pass these values back to the client control when it's rendered again after the postback. I handled this by overriding the server control's OnLoad event, since it happens at a point in the lifecycle where there is access to the Request.Form collection, but before the IScriptControl.GetScriptDescriptors() is called.

Handle Client Events to Add ListBox Functionality... or not

The rest of the code we need to write is all in the ListBox.js file. All of the user interaction with the ListBox will be handled in the client. This is where the code zivros posted comes in handy. The 3 client events that his code handled were the DIV container's onkeydown event, and the ListBox's onkeydown and onchange events. We're going to handle these 3 events, but we're also going to handle the container's onscoll event in order to update the hidden field we rendered.

In order to handle these events, we first have to add some code to our overridden initialize and dispose functions. (Note that from within the prototype definition, all functions are separated by commas.)

In the initialize method, we created and added 4 new event handlers. Because the ListBox is the server control that is directly connected to this client control, we access it using this.get_element(). I've created a helper method called get_elementContainer() that conveniently retrieves the DIV client element for us as well. After creating the handlers, we add them respectively to the ListBox and the DIV using the $addHandlers shortcut. The rest of the code follows zivros' pattern of initializing the client-side elements, with a few of my own tweaks. After the page is loaded, when any of our 4 client events are triggered, the client will execute the event-specific code defined below:

If you've been trying to get these functions to work before coming this far in the article, you're probably pretty frustrated. There's one more client-side function we need to add, and it's probably the most important of all. Again, thanks to zivros for the final piece that ties everything together and actually updates the DIV element's scroll position. Place the updateListBoxScrollPosition function just before the final line of code where Sys.Application.notifyScriptLoaded() is called:

Voila

There is a lot of overlapping code here, so if you're copying and pasting it straight from this article into your own files, you may be running into the dreaded "DanLudwig is undefined" client error message. That error message, like so many others, can be misleading. I assure you, I am very well defined. However, the slightest syntax error in the ListBox.js file will cause the DanLudwig namespace to not be successfully registered. Sometimes it's because there aren't the required commas separating the different parts of the prototype declaration, and other times it's because of more obscure errors in the client script file. If you're having trouble copying and pasting, use the source files provided along with this article (but remember to change the reference in the GetScriptReferences() method if you'll be testing it in a web site project).

Perfectionism is a Disease

Now we have a fairly intuitive ListBox that can accommodate text too wide for our available screen real estate. As an added feature, it will also remember its last vertical and horizontal scroll positions and restore them after postbacks to preserve client state. But there are still more features to be added! For example, if your ListBox's SelectionMode is set to Multiple, users should be able to use the CONTROL and SHIFT keys to select multiple items simultaneously. Try this: select an item, then scroll away so that it isn't visible anymore. now, try to use either the CONTROL or SHIFT key to select more than one item. As soon as you hit the key, the ListBox scrolls back to the original item you selected. I'm not a user, but if I was, that would probably annoy me.

Another shortcoming of this control can be demonstrated by viewing it in IE7 and FireFox 1.5. In FireFox, when the mouse is positioned over the ListBox, the mouse's wheel can be used to scroll through the items. In IE7 though, this only works if the mouse is hovered over one of the scrollbars. Again, were I a user, I might complain about this too. What's even more interesting is what happens when you view the control in IE6 and FireFox 1.0... you get the opposite behavior! In the older versions of these browsers, IE scrolls by default whereas FireFox does not. Ideally, we should code for the mouse wheel such that we can turn this type of behavior on and off from the ASPX page. Some users might prefer using the mouse wheel whereas others might think it interferes too much with page scrolling. If we make it configurable, we can at least provide ourselves with the possibility of letting users turn this feature on and off for themselves using ProfileCommon.

You might also have noticed that our ListBox doesn't remember scroll position if HorizontalScrollingEnabled is set to false (or not set at all). The two should be configured separately and operate independently from one another. That, and we can also remodel how we respond to events to use fewer CPU cycles on the client machine. This will be especially useful since FireFox 1.0 doesn't respond to the onscroll event we attached to the DIV container. There are probably more things that I haven't even thought of yet, but this article is already way too long. In my next, much shorter article, I'll begin discussing how to accommodate these additional requirements.

Points of Interest

This is only the second code article I've ever written. The first, now quite obsolete More Scrolling Function in Flash 5, I wrote way back in 2000. Some of the same mathematics needed to calculate the scroll position is similar, but even I find it odd that the only two articles I've ever written have to do with this rather mundane, yet UI-critical necessity. Perhaps this ListBox control will also become obsolete if browsers ever evolve to the level of maturity where they can scroll ListBoxes sideways natively.

Exercises for You

I know this control isn't perfect, though I do think it's highly reusable when packaged correctly, and we're going to make it even better in the next article. Here are some considerations for you to tackle if you find this control falling short of your requirements:

Even on asynchronous postbacks, there is sometimes screen flicker on the ListBox between the time the browser renders the ListBox and the time the client code initializes the saved scroll position. Because its height is determined by its "size" attribute (which corresponds to the "Rows" attribute / property in the server control) by default, it's difficult to set the DIV's scroll position before the ListBox's size is programmatically set by the JavaScript. For my requirements the screen flicker is not a 100% reproducible problem, is barely noticeable, and for the most part, can be ignored. But can you figure out how to lose it altogether?

Although this control is compatible with FireFox, it always renders a vertical scrollbar even when the ListBox is bigger than the number of items it contains. Can you figure out how to get rid of this?

Create server properties to add CSS styles to the DIV scrollbars.

Create other properties to pass more CSS styling control from the ListBox to its DIV container.

Clean up my C# code (I'm actually certified in Java and J2EE, not ASP.NET or C#

I can't think of anything else. What could I be missing?

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.

Comments and Discussions

Thank you so much for your Advanced AJAX ListBox Component series, they are exactly what I am looking for my current application. I am quite interested in your other postings, do you happen to have a blog? website?
Again, thanks for your harework, they really make my job(and many of others', I believe) - A LOT EASIER!

Thanks Rick. I don't really blog, but I recently contributed a new control to the AjaxControlToolkit -- look out for the new ComboBox control coming soon in the next release. It's actually downloadable right now if you know how to download changesets from codeplex.

As for this article series and my web site, I've actually been developing a new set of controls based on this same framework. I'm working right now on developing a site to showcase a lot of that stuff. I've turned this ListBox scrolling code into an extender control (one that extends Sys.UI.Behavior instead of Sys.UI.Control). I've also developed separate controls for ListBox teleportation, ListBox item reordering, asynchronous file uploads (actually an imitation async file upload), a calculator that can be attached to a textbox (like a calendar extender), and I'm working on a few others. Thanks for asking.

I have abandoned waiting for each article to be approved before positing the next in the sequence. I have gone ahead and posted all 6 articles in this series. In case codeproject changes the URL's, you should be able to view them all from the following page:

Is there an UpdatePanel wrapped around the ListBoxes and their related buttons? Without an UpdatePanel, there will be considerable flicker. Otherwise, if you do use an UpdatePanel, what browser are you seeing with flicker?

I am using Two UpdatePanels,one for each Listbox and Buttons to move items from not included in any of them, I tested output in IE 6.0 gives flicker in Listbox not to complete page, but in Firefox 2.0.0.9 it did not flicker.

What does the flicker look like when you have one UpdatePanel wrapping both ListBoxes (and their buttons)?

It sounds to me like IE6 might be flickering due to the JavaScript redraws. I don't think IE7 does this (and obviously, FF2 either). There may be nothing that can be done about this, but let me know if you solve it.

Is this possible with Dropdownlist? I mean to make a DIV take the size of the list at runtime and yet provide a horizontal scroll like for a listbox. I am not finding this anywhere and now have a feeling it cannot be done. Is there any other alternative like displaying tooltip for items which are longer than the dropdownlist width?

It's funny you asked this question, because after finishing this control, I started work on a web ComboBox (combination of a drop down list and a textbox). It's almost ready for beta release.

The short answer is, no, I couldn't get horizontal scrolling to work CORRECTLY. I say correctly because I could get it to work, but there was one significant problem.

In a drop down list, you want to be able to highlight / select items by changing their background color. This is not hard to accomplish, but the problem was that when horizontal scrolling was used, only the immediately visible portion of the selected item was properly styled by css. This meant that, when the user scrolled to the right, there was a "cutoff" where list items were not styled properly. I can't remember which browsers this happened in, but my results were significant enough for me to abandon supporting that feature.

The good news is that with a dropdownlist, you can make the popup as wide as the user's browser window. Since it is shown and hidden with the click of a button, there is no huge screen real estate issue. If a user can't determine whether a selection is the correct one in the first 50 to 75 characters, you might want to reexamine the data.

I like your idea about the popup tooltips though. Why don't you check out the ajax tutorials and try to implement that?

This article is great and helped me understand easily.
I have one question. I implemented this code exactly, but now i need to save all that is in the second listbox. Is there any AJAX way of doing this.
The traditional way i know is call a method, get an array and do the split and save to DB.
Thanks for any help/suggestions

S,
The way I did in production was by making a db call during a postback. There was no need to get an array or split it, since my ListBox inherited from the ASP.NET ListBox. On the server, you can get all of the items in the second ListBox (or any ListBox for that matter) from the ListBox's Items collection. I used DataSets and TableAdapters, so my db code looked something like this:

If you don't use tableadapters, then your db code will look different. Also, since both the Value and Text properties of a ListItem are strings, you may need to do some manual type conversions if your method accepts different types like ints, nullable ints, etc. However, iterating through the ListBox's items using the above foreach block is the best way I know of to do it.

Firstly, great article, very indepth and helpful, just what i was looking for.

What would be the best way to approach the reordering of the "selected" items, for instance if that was a list of publish news items and you wanted to change the order that they were displayed on the front page of your website, using a database reference of course to insert the index that it is in that list.

The ordering of the selected items in the demo project is entirely application-specific. That's why I kept this code outside of the control. In the case of the demo, they're always alphabetically ordered. However, you can support many different types of ordering if you're moving items from one ListBox into another. How you implement this depends on how the ListBox is bound to data (i.e. manually, via a DataSource, via a DataSourceID, etc).

I have actually implemented the behavior you're talking about, however I haven't encapsulated it into a Sys.UI.Control. What you probably want is UP & DOWN buttons, so that users can select items, then use the buttons to reorder them. This can easily be handled from ASPX codebehind, but I suppose the best approach would be an Extender control like Evyatar posted about. That way you could link TargetListBoxID, MoveUpButtonID, and MoveDownButtonID properties to the extender control and have all of the reordering happen on the client before postback.

Actually Ben, now that I've thought about it, you should check out the ReorderList control in the AjaxControlToolkit. That control gives you drag-and-drop functionality for reordering items, which is easier for a user than having to perform multiple clicks to reorder things.

I am a newbie, so I really appreciate the way this article is written and the pointer to the other articles that can get a newbie going. My first thought when I read these is, "I wonder what I need on my system to be able to follow this project". I think the core concept articles you pointed to will guide me into this project.

Firstly, it is very nice to see that pepole using and extending my work thank you for that.

Actually I did plan to publish a second part article soon for The ScrollableListBox Custom Control for ASP.NET 2.0[^]. The second part is using AJAX's ExtenderControl which extend a ListBox control and add the horizontal scroll support as plugin.
The code looks like:

Yeah Evyatar, that looks pretty in tune. I opted to extend Sys.UI.Control rather than Sys.UI.Behavior, just because I have actually developed somewhat of a suite of controls on that basis. I have an Image control that does client-side swapping and can be rendered inside a doPostBack anchor (or have a doPostBack onclick), a clickable Panel, LinkButton / LoginStatus that hide cryptic window.status text, etc. Nevertheless, I don't see why an extender can't be used just as easily. For me, it just means adding more XML to the ASPX page.

You should definitely still publish your planned update though. People should see there are a few different ways these things can be accomplished. I've almost finished the second installment in this series of articles, where I create a new property called ScrollStateEnabled. This separates the client-side scroll state preservation from the HorizontalScrollEnabled property, so that last scroll positions can be preserved even if HorizontalScrollEnabled is false. Look for it soon!

Didn't do this with atlas, I used pure js and a webservice. (In this case the webservice is php but it could have been .net) I am finding that many things are much easier if you don't use the ms ajax toolkit.

Case in point this app, while very nice could have been written in js/webservice in a fraction of the code used to coax the toolkit into behaving properly.

However I am undecided, because using the toolkit gives you some really powerful ready made events that would be a nightmare to create by hand.

So, I can't decide what I should use, so for now i'll continue using both. Oh btw, heres the link to the app I made (Its not much just a visualy sortable list that when updated calls a webservice).

http://beta.devclarity.com/sort/sortable.html

Matthew Hazlett

Sometimes I miss the simpler DOS days of Borland Turbo Pascal (but not very often).

I'm not sure I follow. I visited the link you provided, and received 2 JavaScript errors on lines #60 and #7. Besides that, there is no ListBox, only a BulletedList (aka Unordered List). This article was about adding client functionality to a element with options and a size attribute. You can always use a web service to populate the elements inside of any ListBox, but how exactly does a web service help add client functionality like horizontal scrolling and persistent scroll state across postbacks?

All I was saying is that it seems to me that a lot can be accomplished without the need for atlas + the toolkit. And a ton of things that are very complicated with atlas are really easy if you use normal js + services.

I'd be interested to find out where you got the errors in my application and what browser you were using. It has been tested with IE7, FF and the early beta of safari for the pc.

Matthew Hazlett

Sometimes I miss the simpler DOS days of Borland Turbo Pascal (but not very often).

Yeah, FF is always more helpful when it comes to markup and client script. I like to debug in IE7 first though because it's the only way I know how to link up the VS2005 javascript debugger to a browser :P

As far as the framework & toolkit, you're definitely right. I might act like I'm a control developer in this article, but I'm actually an application specialist. What I like about this approach is that it's easier to plug into an existing UI. Once the control's ready, I can go into other ASPX files and replace "asp:ListBox" with "my:ListBox" without having to make sure the javascript is included, the correct ClientID's are passed, etc. It's also easy to reverse, which makes it easier to manage large, complex apps with a lot of forms.