Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Another Flexible ListView Control

4.59/5 (12 votes)
17 Jul 2010CPOL5 min read 38.3K   2.3K  
A highly object-oriented ListView control with varying-height items and support of complex data types

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. CheckBoxes, 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:

  1. It supports varying-height items which is not supported by native ListView.
  2. It supports complex data types: images, check boxes. And more important, these data types can be extended.

Have a look at the screenshot below:

GListView.png

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:

  1. Determining what is your underlying data structure. Deriving a non-generic class from it, supplying appropriate type parameter.
    C#
    class MathProblem { ... } // Our data structure
    class MathListView : GListView<MathProblem> { ... } // Our non-generic ListView
  2. 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.
    C#
    class MathListView : GListView<MathProblem> {
    
        ...
    
        public MathListView() {
            this.SetColumns(new GListViewColumnSchema() {
                new GCheckBoxColumn(),  // A CheckBox column, with no text on    header
                new GTextColumn("ID", GCellAlignment.Center), // A text column
                new GImageColumn("Question", GCellAlignment.Left), 	// A column 
    							// displaying images
                new GImageColumn("Answer", GCellAlignment.Left),    // Another column 
    							// with images
                new GTextColumn("Type", GCellAlignment.Left),
                new GTextColumn("Difficulty", GCellAlignment.Center)
            });
        }
    }
  3. 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.
    C#
    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 ListViewItems. 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 :))

License

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