Introduction
I was developing a math-related application and required to display complex math formulae on the screen. Ideally a ListView
was good for this, except that native Windows ListView
doesn't support complex cell's structure (i.e. CheckBox
es, images) and worse, it doesn't allow varying-height items, which was a must in my application.
After looking around CodeProject and Google articles, I found that the latter issue can't be fixed by modifying the existing ListView
. It was designed so that all items are of equal height. The workaround is modifying fonts... but this is really nasty for me. So I decided to write my own ListView
, based on simpler, but in some aspects, more general, ListBox
control. I named it GListView
, which was based on the facts that it is a ListView
, and that it uses C# generics as a mechasism for improving object-orientation.
Two main practical benefits you can find from this control are:
- It supports varying-height items which is not supported by native
ListView
. - It supports complex data types: images, check boxes. And more important, these data types can be extended.
Have a look at the screenshot below:
Using the Code
The article comes with the source code and a demo.
If you need an out-of-the-box ListView
which supports varying item heights, complex cell structures and probably more object-oriented use, then the demo project should do the job. Read the demo's source and get on how to use.
Note that the GListView
is an abstract
generic class. Basically, using it includes three steps:
- Determining what is your underlying data structure. Deriving a non-generic class from it, supplying appropriate type parameter.
class MathProblem { ... }
class MathListView : GListView<MathProblem> { ... }
- Determining what columns will be displayed on your
ListView
and setup it using the SetColumns(GListViewColumnSchema schema)
method. Usually the method resides inside the constructor of the derived class.
class MathListView : GListView<MathProblem> {
...
public MathListView() {
this.SetColumns(new GListViewColumnSchema() {
new GCheckBoxColumn(),
new GTextColumn("ID", GCellAlignment.Center),
new GImageColumn("Question", GCellAlignment.Left),
new GImageColumn("Answer", GCellAlignment.Left),
new GTextColumn("Type", GCellAlignment.Left),
new GTextColumn("Difficulty", GCellAlignment.Center)
});
}
}
- Determining how an object (of underlying data structure) is translated to be visual cues on the screen. This is done through overriding the
abstract
method Translate(T obj, object[] fields)
whose responsibility is to use data from obj
to fill the fields
array, which are later used for displaying.
class MathListView : GListView<MathProblem> {
...
protected override void Translate(MathProblem obj, object[] fields) {
fields[1] = obj.Index.ToString();
fields[2] = obj.Question;
fields[3] = obj.Answer;
fields[4] = obj.Type;
fields[5] = obj.Difficulty;
}
}
Besides, the DLL contains another control, ProperListBox
, which is an ordinary ListBox
but without flickering and has more complete control over its horizontal scroll bar. If you are developing a ListView
-style control, you may find this supplementation useful. It supports only OwnerDraw
mode.
One important point: if you want to use Visual Studio drag-n-drop Winforms design, you may have a problem since GListView
bases on generics and Visual Studio's IDE doesn't like this. To be specific, if your control derives from a generic type, and you put it on a Form
, some error messages will appear. One work-around for this is to create a container (e.g. a Panel
control) to hold the GListView
and then, instead of positioning the GListView
, position its container. Finally setting the GListView
-derived control's Dock
property to DockStyle.Fill
and setup things for the GListView
(columns, event handlers, populating data) by hand.
Lastly, you may want to look at the GListView
's source code if you are curious about how the code works, or you want to extend it. I have formatted and refactored it carefully, but it is a little bit long. The most realistic scenario I could imagine is to extend it to support new type of columns, and the control is designed to do that.
Points of Interest
This section explains difficulties I encountered during the development of the control and the solution I found. I rather enjoy developing GUI components. GListView
was a very interesting control I have developed.
ListView
control is used widely throughout my applications, and perhaps most of you find it useful for displaying large amount of records. The usual way is to attach data to items in the Tag
property, or sometimes inherit the ListViewItem
class. However I found this is clumsy, not type-safe and not very object-oriented. That was why I derived a solution using generic. A subclass of GListView
is completely aware of its underlying data. And the internal working (i.e. translation from underlying object to visual data) is invisible outside the GListView
. All accesses and manipulations are done with objects, rather than with ListViewItem
s. Designing the classes so that the control is extensible and easy-to-use was also a challenging task and took quite a lot of refactoring.
Headers is a functionality that took me considerable time to implement. ListBox
doesn't natively support headers. My solution is to use a panel, and the headers are implemented completely by hand: handling mouse events, drawing splitters and header texts. The behaviour of GListView
's headers is a copy of Windows Explorer in details mode.
Flickering of the ListBox
is also an issue. This is solved with Les Potter's solution.
The final problem is rendering checkboxes and images. This is done with ListBox
's owner draw facility. It also took me a while learning what kind of drawing facilities .NET already offered.
Future Developments
As I said above, the GListView
is used within one of my personal projects and it is designed to fit my needs, which it does. Thus there are certainly aspects that it will lack. Some of such I can think of are sorting abilities and column reordering. I am not sure if I can add these functionalities in the future, as these take serious efforts. Perhaps this depends on how the control can benefit the community.
Bug reports and comments are warmly welcome. But I am not sure if can respond or fix them well.
History
- 24 June, 2010: Article was submitted to CodeProject (my first article :))