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).
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:
public ARBOScrollableListBox()
public List Items
public bool AdaptControlWidth
public bool AdaptControlHeight
public Control CurrentSelected
public IComparer Comparer
public OrientationEnum Orientation
public void BeginUpdate()
public void EndUpdate()
public void Add( Control Control )
public void Remove( Control Control )
public void RemoveAt( int Index )
public void Clear()
public void Rebuild()
public void SelectControl( Control C )
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