Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A ListBox Displaying (User-)Controls

4.55/5 (10 votes)
1 Jul 2008CPOL4 min read 1   2.1K  
Use this Control to line up controls in a ListBox
Sample Image - ARBOScrollableListBox.png

Introduction

From time to time, I was looking for an easy way to show a collection of information in my programs. Searching around, I always ended up displaying information in a ListView. One day I found an article here on The Code Project about a multiline ListBox, but that does not really solve the problem of displaying data collections like addresses. Thus I started to write a usercontrol which should be easy to use and solve my needs. Maybe it is helpful for others, too...

Background

My first thought was: easy... take a usercontrol, make it scrollable and add each sub-control with DockStyle.Top. The rest is done by the usercontrol itself and the framework.
Ok, so 5 minutes later the first version was on start. Adding 100 controls (I created an address control displaying name, street, zip, city and phone) took about 17 seconds. no good idea... Some tests later (disabling DockStyle etc.), I started from scratch. The only thing that is equal to the first approach - it's still a usercontrol.

Speed...

The next step was to think about a method that still displays items, but won't take seconds to build the list. The solution is something like a "virtual" visible list. That means, the usercontrol holds a list of controls to be displayed and rebuilds the items to be shown each time the control scrolls.

That also means that I have to control the scrollbar on my own. To do so, I need to remember the first element on the screen and the number of elements. On each scroll event, I need to rebuild the items that are in the visible area of my usercontrol.

... More Speed

The new version of the usercontrol was nearly what I wanted it to be like. It is (nearly) fast and it can scroll items, even if the items do not have the same height.
But if I add a lot of elements (let's say...1000) it still gets a little bit slow and flickers horribly. So the next step was to add an "update" mechanism. Each time I call Update(), a member update is increased, and on each EndUpdate() it is decreased. While update is > 0, no visual things are done. And when update is 0 again, the visual things are done.

Item Selection

Now we are fast enough for a lot of items, we can add/remove them and they can be scrolled. But no one gets informed as to which item is selected.

The first thing on my mind was "let's add a click event handler". But that's not really all, because if the child control itself contains other child controls, we will never be informed about the click. so we need to register to the click handler of all child controls recursively (and deregister on deletion). So adding a control leads to calling AttachClickEvents(ChildControl).

C#
private void AttachClickEvents( Control Control ) {
    Control.Click += new EventHandler( Control_Click );
    if( Control.Controls != null ) {
        foreach( Control C in Control.Controls ) {
            AttachClickEvents( C );
        }
    }
}

Now, if any child control is clicked, we are informed about that event. we need to compare, if the clicked control is in our list of controls. If not, we need to take the parent control and do that compare, again. If we find the control in our list, we find the control for which we need to fire the ControlSelected event.

Sorting Items

The last thing that is outstanding is sorting. That is really easy. We need to have a public property for an IComparer which can be set from outside the usercontrol. Now, if that comparer is set, adding/removing a control to/from our item list needs a call of items.Sort(comparer).

Using the Code

Though we have a property to get the list of controls our usercontrol is displaying, we may not use this list to change elements, because the click event handler is not properly set. They are only set in Add(Control) and they are only removed in Remove(Control) or RemoveAt(Index).

The public methods and properties are:

C#
//constructor
    public ARBOScrollableListBox()

//properties
    public List Items                       // list of controls to display
    public bool AdaptControlWidth           // make controls width fitting
    public bool AdaptControlHeight          // make controls heights fitting
    public Control CurrentSelected          // the selected control
    public IComparer Comparer               // the comparer to use
    public OrientationEnum Orientation      // orientation of the items

//methods
    public void BeginUpdate()               // stop screen updating
    public void EndUpdate()                 // redo updating
    public void Add( Control Control )      // add a control
    public void Remove( Control Control )   // remove a control
    public void RemoveAt( int Index )       // remove a control at position "index"
    public void Clear()                     // clear list of controls
    public void Rebuild()                   // rebuild the display
    public void SelectControl( Control C )  // manually select a control

Points of Interest

Writing this control showed up some interesting points explained in the background section, like click event or speed up.

Extension 1: Orientation

By now, you can set the orientation of the items within the ListBox. That means, if you set Orientation to OrientationEnum.Vertical, you will get the list of items sorted from top to bottom (in connection with the item comparer you use). Otherwise you will get them in order from left to right.

If you set the AdaptControlWidth property to false, you will get as many columns (rows), as fit on the control (see screenshot).

Extension 2: Invisibility

Thanks to my readers, I was able to change the behaviour when the control is invisible. Some of the calculations for the scrollbars depend on the number of items visible within the control. When invisible, this leads to a "division by zero" error (corrected).

Thanks...

... to Marc Clifton, who read the article and gave me some hints, before I published it...
If you want to get good information - read his articles!

History

  • 2008-05-21 : Initial release
  • 2008-06-25 : Orientation and multi cols/rows added
  • 2008-06-29 : Article updated

License

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