Introduction

This article shows how you can implement a smooth list box that allows intuitive, friction affected, scrolling of the list items. By friction based, I mean that the user can apply a dragging force in one direction and the list will keep scrolling after the mouse or stylus is released and then slow down by itself. I've looked at how lists behave on the iPhone and tried to mimic that to a certain extent, but I'm nowhere near anything as cool as what the iPhone presents.

The properties of the iPhone list box that I think are the neatest are the fact that you do not scroll it using a scrollbar but rather just grab the list items and drag them, much neater.

Also, on the iPhone, one is allowed to scroll beyond the bounds of the list box and the items just snap back into place. Very cool.

Note: This article and download has now been updated to include a version where you can use the keys to scroll the list as well. I didn't have access to Visual Studio 9 when implementing this, so there's been a few changes to revert back to the .NET 2.0 level of compliance.

Thanks to Mixxer, mbrucedogs, and Daniel Bass who suggested tips and implementations and pushed me to implement the key support. Unfortunately, I haven't had time to implement everything they asked for.

Background

As the standard ListBox in the .NET Compact Framework is kind of limited, I wanted to create one which fulfilled three requirements;

The list must scroll smoothly, and rely on "friction" and "springs" to give it an intuitive feel.

The list must be able to have list items that are more than just text and an icon, any Control must be able to act as a list item.

The list must be fast enough to run on a PocketPC 2003 device.

All in all, not terribly complicated requirements, and after implementing it first for the Desktop and then porting it to the .NET Compact Framework, the requirement that gave me the most grief was actually #3.

Using the Code

The downloadable Visual Studio solution contains three C# projects;

Extended ListItems; This is a class library project that has two predefined list item controls that can be used to show information about music albums or Formula One teams.

Smooth ListBox (Test); This is a test project that creates a sandbox application to try the SmoothListBox in three different ways.

I've implemented this solution using .NET 3.5, but it should be trivial to get it working for .NET 2.0 as well.

Creating a Custom ListBox

As my SmoothListBox was going to be completely different in both appearance and behaviour from the list box found in the System.Windows.Forms namespace, I decided to build it from "scratch" and not inherit from ListBox. As the list box should behave like any other component in the designer, it inherits from UserControl.

The SmoothListBox showing Formula One team information.

List Item Storage

The first thing to solve is the storage of the list items. I was first planning on using a List<Control> for this, but found that I could just as well use the Controls property provided by UserControl. This also makes sense from the point of view that the list items are really nothing other than Controls owned and displayed by the SmoothListBox. For future purposes, such as adding a scrollbar (the current implementation does not support a scrollbar as I didn't really see the need for it), list items are not added directly to the SmoothListBox's Controls. To accommodate more than just the list items being owned by the SmoothListBox, the control holds an internal Panel that directly owns the list items. This, in turn, means that doing mySmoothListBox.Controls.Add(new MyListItem()) won't work as it will not add the list items to the correct container. Therefore, two new methods, AddItem and RemoveItem, need to be implemented. This is actually quite good as the semantics makes more sense that way (adding items to a list is different from manipulating controls in a container).

Selection State Handling

In order to keep track of which items are currently selected (as the SmoothListBox supports multiple selection), a look-up dictionary is used. This maps list items to a selected state like so:

MultiSelectEnabled: when true, the selection model allows several items to be selected at the same time. If set to false, an already selected list item is automatically de-selected when a new one is selected.

UnselectEnabled: when true, the user can explicitly de-select a selected value; if set to false, the only way to do this is to select another value.

And, as this is a .NET 3.5 project, these are declared as automatic properties:

///<spanclass="code-SummaryComment"><summary></span>/// If set to <spanclass="code-SummaryComment"><c>True</c> multiple items can be selected at the same</span>/// time, otherwise a selected item is automatically de-selected when
/// a new item is selected.
///<spanclass="code-SummaryComment"></summary></span>publicbool MultiSelectEnabled
{
get;
set;
}
///<spanclass="code-SummaryComment"><summary></span>/// If set to <spanclass="code-SummaryComment"><c>True</c> then the user can explicitly unselect a</span>/// selected item.
///<spanclass="code-SummaryComment"></summary></span>publicbool UnselectEnabled
{
get;
set;
}

List Event Handling

A custom event that fires whenever a list item is clicked is also defined:

Making it Smooth

OK, so storing some information and firing an event doesn't really make the list box scroll smoothly in any way. To get that part working, quite a lot of mouse handling and animation code has to be added to the implementation.

Mouse Handling

When scrolling, I do not want to have to grab a handle on a scroll bar and drag that around, I want to be able to "grab" anywhere in the list item and drag to scroll (with certain limitations that will be explained later). On the iPhone, the user just "flicks" through the list using his/her finger anywhere in the list. This is a very intuitive way of scrolling, and I want my list box to behave the same way.

According to requirement #2, the list box must support any list item that inherits from Control. This means that a list item can have any number of nested child controls, and regardless of whether the "base" list item control is the control that receives the mouse events or if it is one of the list item's child control, the events need to be handled in a uniform way. To accommodate this, list box global mouse event listeners are added recursively to any list item being added using the AddItem method. In WPF, this could have been handled using bubbling up or filtering down of events, but in WinForms, we have to do it ourselves. The SmoothListBox has three members, all MouseEventHandlers, that listens to mouse down, up, and move events. The recursive adding of the listeners is done by a static helper class called Utils:

So, in the AddItem method, Utils.SetHandlers is called, and after that, the list box is aware of every mouse related thing that happens to a list item or the list item's children. We can now go ahead and implement the dragging.

Dragging

MouseDown

The first thing, naturally, to handle when processing mouse dragging is the mouse down event. As far as the smooth scrolling goes, nothing really happens in the mouse down event except the storing of some information that'll later prove to be vital:

///<spanclass="code-SummaryComment"><summary></span>/// Handles mouse down events by storing a set of <c/>Point</c/>s that
/// will be used to determine animation velocity.
///<spanclass="code-SummaryComment"></summary></span>privatevoid MouseDownHandler(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
mouseIsDown = true;
// Since list items move when scrolled all locations are
// in absolute values (meaning local to "this" rather than to "sender".
mouseDownPoint = Utils.GetAbsolute(new Point(e.X, e.Y), sender as Control, this);
previousPoint = mouseDownPoint;
}
}

The Utils.GetAbsolute method is a helper method that converts a point in child-local coordinates to parent-local coordinates; this is important as we're going to move list items around during dragging, and if we're not looking at the absolute coordinates, they won't behave as expected.

MouseMove

The next natural thing to handle is the mouse move event. Again, not very much is required to handle this event (at least, not conceptually), but the implementation calls for some weird things that might require further explanation. What the mouse move handler needs to do is to measure the vertical distance that the mouse has moved and then make sure that the list items are scrolled by that amount. Easy enough, right? Well, here's where I ran into some weird thing.

As the list has to be animated to give the impression of obeying friction and spring laws, it has to be periodically re-drawn whenever a specific timer event fires. But, the list also has to be re-drawn when a user mouse drag has invalidated the list items current positions. This initially caused the list to re-draw too often, causing the Mobile Device to hang or run extremely slow. One way to fix this would have been to turn off the animation timer when the list is being dragged by the mouse and enable it again when the mouse is released.

I tried that approach, but it resulted in a too long delay between when the stylus was lifted and when the list scrolled "on its own", making it look a bit unnatural. I ended up solving it using a much simpler approach. By keeping a boolean member that is set and unset by the timer event handler method and the mouse move method, I can discard updates that occur too often causing the list movement to "stall".

As seen from this snippet, the mouse move event is only handled if the renderLockFlag is set to false. This means that if a mouse move event occurs before the previous one was handled, it is discarded. The flag is reset by the animation tick handler method:

MouseUp

The last mouse event to handle is the mouse up event; here, the velocity that the list should continue to scroll with is calculated. Also, handling of list item selection/de-selection is taken care of when the mouse up event occurs.

To calculate the velocity the list should have is quite easy; it's simply the dragged distance (distance between the last two mouse move events) multiplied with a constant factor.

To handle the selection or de-selection of list items, a few checks have to be made as the SmoothListBox allows different selection modes; multiple items selection can be disabled or enabled as can explicit de-select of items. Further, as it is not required for a list item to implement IExtendedListItem (see a later chapter for details on this) checks have to be performed on the type of the affected list item to see if methods need to be called on that list item. The benefit of implementing IExtendedListItem is that the list items can dictate their behaviour when being selected or de-selected, as shown in my example application where they change size and or content when selected.

privatevoid MouseUpHandler(object sender, MouseEventArgs e)
{
// Only calculate a animation velocity and start animating if the mouse
// up event occurs directly after the mouse move.
if (renderLockFlag)
{
velocity = Math.Min(Math.Max(dragDistanceFactor *
draggedDistance, -maxVelocity), maxVelocity);
draggedDistance = 0;
DoAutomaticMotion();
}
if (e.Button == MouseButtons.Left)
{
// If the mouse was lifted from the same location it was pressed down on
// then this is not a drag but a click, do item selection logic instead
// of dragging logic.
if (Utils.GetAbsolute(new Point(e.X, e.Y), sender as Control,
this).Equals(mouseDownPoint))
{
// Get the list item (regardless if it was a child Control that was clicked).
Control item = GetListItemFromEvent(sender as Control);
if (item != null)
{
bool newState = UnselectEnabled ? !selectedItemsMap[item] : true;
if (newState != selectedItemsMap[item])
{
selectedItemsMap[item] = newState;
FireListItemClicked(item);
if (!MultiSelectEnabled && selectedItemsMap[item])
{
foreach (Control listItem in itemsPanel.Controls)
{
if (listItem != item)
selectedItemsMap[listItem] = false;
}
}
// After "normal" selection rules have been applied,
// check if the list items affected are IExtendedListItems
// and call the appropriate methods if it is so.
foreach (Control listItem in itemsPanel.Controls)
{
if (listItem is IExtendedListItem)
(listItem as IExtendedListItem).SelectedChanged(
selectedItemsMap[listItem]);
}
// Force a re-layout of all items
LayoutItems();
}
}
}
}
mouseIsDown = false;
}

Animating

As seen in some of the above snippets, a method called DoAutomaticMovement is used. This method animates the list motion when the mouse or stylus is not affecting it.

One thing to handle is to calculate the current speed of the list animation and update the position of the list items accordingly. Friction is applied during this process as the current speed, or velocity, is multiplied with the de-acceleration factor:

The elapsed time is estimated, for convenience (read laziness), to the timer interval. This probably should have been changed to an actual measured delta time between updates for an even smoother animation. A requested difference in distance, deltaDistance, is calculated because we only want to move the items and re-layout the list box if the actual distance moved is greater than one pixel.

// If the velocity induced by the user dragging the list
// results in a deltaDistance greater than 1.0f pixels
// then scroll the items that distance.
if (Math.Abs(deltaDistance) >= 1.0f)
ScrollItems((int)deltaDistance);
else
{
...
}

The ScrollItems method is the method that actually re-positions the list items by a certain distance.

The else statement is necessary because if the de-acceleration has caused the list item to no longer have any velocity, we need to check whether the list has been scrolled "out of bounds". I would like the user to be able to apply enough velocity for the list items to scroll beyond the visible area of the list box, and when they've slowed down to zero velocity, the list box will make sure they "snap" back into view.

The items are moved by moving the entire itemsPanel in the ScrollItems method:

privatevoid ScrollItems(int offset)
{
// Do not waste time if this is a pointless scroll...
if (offset == 0)
return;
SuspendLayout();
itemsPanel.Top += offset;
ResumeLayout(true);
}

Key Handling

The easiest way to get key handling working was not to try to hook key listeners to all relevant elements as I did for the mouse handling. The reason for this is that (according to Daniel Bass) the key events didn't fire as expected.

I decided instead to use an approach where I simply read the key states just prior to animating the list movement. This way, I could add velocity in either the upwards or downwards direction, and this would give the list an accelerating change in velocity.

The keyAcceleration is a poorly named member that defines how much additional velocity should be added. It is accessible using the KeyAcceleration property.

Custom List Items

As I want the list box to have items that can react to being selected or de-selected, an interface to be implemented by the list item classes is required. But, as I also want this list box to be able to handle just about anything (anything that is a Control at least), the list items must also work even if they do not implement the IExtendedListItem interface.

The example application provides three different usages of the SmoothListBox, and also provides a way of comparing them:

Album Information

The album information list item, the Album class, implements IExtendedListItem to allow the list items to display different information when selected. It also renders different background colors depending on whether the item is on an even or odd row in the list box.

This list item changes its size when selected, but the SmoothListBox is smart enough to re-layout its list items when a selection or de-selection occurs so it is handled neatly and automatically for the user.

BigPanel

In order to show the versatility of the SmoothListBox, one example scenario is using a class called BigPanel as a single list item. BigPanel is just a panel containing a lot of Controls, and by adding such a panel to a SmoothListBox, we get a new way of scrolling UIs that are bigger than the screen area. A way that is more intuitive than the scroll bar one gets when AutoScroll is set to true.

Conclusion

All three requirements were implemented, but I would have liked the performance to be a bit better, offering even smoother scrolling. As always, any comments on the article or the code are most welcome.

Points of Interest

It's quite difficult to get a list to scroll smoothly; this is largely because of the limited CPU power of the Mobile Devices. But, also because I wanted a list box that is really easy to use, I couldn't make use of any native drawing methods, such as GDI.

Share

About the Author

21 Feb 2014: Best VB.NET Article of January 2014 - Second Prize18 Oct 2013: Best VB.NET article of September 201323 Jun 2012: Best C++ article of May 201220 Apr 2012: Best VB.NET article of March 201222 Feb 2010: Best overall article of January 201022 Feb 2010: Best C# article of January 2010

When I chose "saved as" to download the source codes file, there will prompt one .htm link in the "saved as" dialog.So I only got one invalid shell of the file, i don't know why?Please help! Thanks previously!

at first, many thx for developing this great control. Its fully satisfying all my needs regarding a special problem on my WM6.5 device (displaying dynamic content of xml-files with textboxes/comboboxes/checkboxes to change some parameters). Especially the customization due to all the parameters for scrolling is a nice feature.that brings me to my problem: ive created several smoothlistbox-controls in my application. all instances work fine. just a single one is driving me nuts, due to its not firing any events at all. ive reimplemented the parent usercontrol twice, changed the content of the listbox, copied one of the working listboxes, with the same result... this one isnt firing events at all... not even events set with the designer, like focus and stuff... due to not firing events, its not possible to scroll trough the list... besides that any parent and child-controls behave well...Any idea what went wrong or how to fix this?

No. It aint working with any controls in it. Ive even tried to put the child controls in another instance, its working in there. Also tried with child controls of another instance (a working one) in the buggy one - it isnt working then. So i think it isnt a problem of the child controls.

In fact i havent changed ur code, except the timer, for handling the disposed-exception. Im using ur control with the add-method, handing a custom control like:

sLb_Beleg.AddItem(new Ctrl_sLb_TextBox("TestText1: ", "TestText1"));

this creates a custom control which fills a label and a textbox with the provided content.

after that of course i use:

sLb_Beleg.LayoutItems();

the usage is the same in all instances ive created. all content is displayed correct.

is there any limitation on events or something like that, which could limit the usage of the control? in fact, it seems like i cant create any additional working one in my project, while other controls like buttons, listviews, etc. behave correct.

Hello, First of all,thank you for your great control, it improved the user friendly on WM device.And I'm sorry for my poor English,But I have found a problem when using your great list control. The problem is that that when I touch and move the list by finger , and then list scrolling, such as iPhone,good.And when I lift my finger, the list will continue scrolling for a distance by DoAutomaticMotion().Then I continue to touch and move the list before it stopped, and I've found sometimes the list didn't respone the opearation until it stopped.You know on iPhone we can continue touch when the list is scrolling or moving.So how I can solove this issue, or any body else has met this problem ,too.I hope for your reply.Thank you.

I implement your usercontrol on an old windows CE platefrom and animations are very slow, because of my old machine I think.Do you have any option or shortcut to avoid animation and slide effet.I would like to move the Item.Panel from top to bottom just by moving my finger from the top of the screen to the bottom.

I already have implemented this snippet to avoid the snapback effet but may be there is another way...

private void ScrollItems(int offset) { // Do not waste time if this is a pointless scroll... if (offset == 0) return;

I have a project, which starts with a frmStart screen, then the user clicks on a button and appears a frmLocations screen. on this frame there is a smoothlistbox, which shows a list of locations. the user clicking on a listitem, the listitem pops out and three button appears. the first button works fine. the other buttons doesn't. if I click on them, the debugger crashes with a TypeLoadExeption, in the frmStart's buttonLocation_Click eventhandler, on the line frmLocations.ShowDialog();

please if anybody can tell me, what could be the problem I would appreciate it very much.

1st. I made a version that doesn't resize the control to its client width. -- which now I could:---- 1. Move the controls around (runtime)---- 2. Resize the controls (runtime)---- 3. I can set a background image so I can make a transparent (alpha blending) effect control (runtime)---- 4. send an ItemRemoved event---- 5. send an LayoutChanged event---- 6. adjusted the dragging sensitivity so it wont move when I tap on the control.---- 7. I guess that's all.2nd. I made a horizontal version to produce a WP7 horizontal panning effect.

Hi, firstly: great work! I have been trying to do something similiar some time ago, but gave up quite soon. Your control works perfectly. I only had a problem selecting a list item on my Motorola Symbol, as "clicking" (mousedown, mouseup) has to occure on identical location. Therefore, here is my suggestion for making the usage more comfortable:In the Utils class, I added the following method:

///<summary>/// Determinates which distance between the mouse down and mouse up points
/// may be to handle it as a click.
///</summary>privatedouble selectionRadius = 5.0;
publicdouble SelectionRadius
{
get
{
returnthis.selectionRadius;
}
set
{
this.selectionRadius = value;
}
}

2. ... and modified the MouseUpHandler (the 3rd if-condition):

privatevoid MouseUpHandler(object sender, MouseEventArgs e)
{
// Only calculate a animation velocity and start animating if the mouse
// up event occurs directly after the mouse move.
if (renderLockFlag)
{
velocity = Math.Min(Math.Max(dragDistanceFactor * draggedDistance, -maxVelocity), maxVelocity);
draggedDistance = 0;
DoAutomaticMotion();
}
if (e.Button == MouseButtons.Left)
{
// If the mouse was lifted from the same location (in the radius given by
// the PelectionRadius property) it was pressed down on then this is not
// a drag but a click, do item selection logic instead of dragging logic.
if (Utils.GetEuclideanPointDistance(Utils.GetAbsolute(new Point(e.X, e.Y), sender as Control, this), mouseDownPoint) < this.selectionRadius)
{
.....

Now, if the mouse is released within the radius given by the new property value, it is handled as a click rather than a scroll.I found out that a radius of 5 works quite well for me.I hope this helps anyone else having same problems as me

First of all thanks for sharing this neat ListBox with us.If you allow me a suggestion, please do update your code with the corrections others posted.

Second I have a question. I use your class as a dll within my VB solution. I'd like to find a way to "unselect" a list item without clicking it. E.g. having a Save and Cancel button on it and after doing some stuff the item gets unselected.

The other problem I have is your list is very sensitive in a way clicking an item is very hard. What do I need to adjust to accept clicks even if it's a small move?