Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Locking ListView Column Size

0.00/5 (No votes)
20 Sep 2005 1  
A custom ListView that allows the column size to be locked.

Sample image

Introduction

It happens to every CPian at some point in time. They actually have to submit an article. For the first time in my .NET development career, I couldn't find an answer to my problem. So, here it is, my first article. It is a fairly simple control, but I found several other questions on this topic without an answer.

Background

The main purpose for this control is to make an easier to use system for non-technical people. The ListView seems like a good way of displaying information, without having to go with a full blown Grid control. The problem is there is no way to lock the size of the column headers. I can hear the support calls now, like "I can't see the totals anymore". Issues like this are difficult to troubleshoot and cause frustration on the user's part. Plus this application which I was developing would be touch screen driven, so it would be easy to move the columns, but somewhat difficult to get them back to the right spot.

So the search began for a C# solution. But nothing seemed to be available. I did find a C++ article on this topic by Paul DiLascia from the June 2003 MSDN Magazine - C++ Q & A article. Now I was armed with the basic knowledge, but this had to be translated to C#. Being new to .NET, I headed to CodeProject and found Georgi Atanasov's article on Customizing the Header Control in a ListView. This filled in the details needed to make the control function as I wanted.

Using the code

The control is quite simple once all of the details are available. First we will subclass the standard ListView control. The mouse tracking messages need to be captured and discarded to prevent sizing of the column headers.

public class myListView : System.Windows.Forms.ListView
{
    /// <summary>

    /// Capture messages for the list view control.

    /// </summary>

    protected override void WndProc( ref Message message )
    {
        const int WM_NOTIFY = 0x004E;
        const int HDN_FIRST = (0-300);
        const int HDN_BEGINTRACKA = (HDN_FIRST-6);
        const int HDN_BEGINTRACKW = (HDN_FIRST-26);
        bool callBase = true;

        switch ( message.Msg )
        {
        case WM_NOTIFY:
            NMHDR nmhdr = (NMHDR)message.GetLParam(typeof(NMHDR));
            switch(nmhdr.code)
            {
            case HDN_BEGINTRACKA:  //Process both ANSI and

            case HDN_BEGINTRACKW:  //UNICODE versions of the message.

                if(locked)
                {
                    //Discard the begin tracking to prevent dragging of the 

                    //column headers.

                    message.Result = (IntPtr)1;
                    callBase = false;
                } //if

                break ;
            } //switch

            break;
        } //switch


        if(callBase)
        {
            // pass messages on to the base control for processing

            base.WndProc( ref message ) ;
        } //if

    } //WndProc()

}

The begin tracking notification is part of the WM_NOTIFY message. Notice that both the ANSI and UNICODE versions of the message are captured.

This works great. The user can no longer size the column headers, but it doesn't look quite right. The cursor still changes to the sizing cursor on mouse over. To handle this, the messages going to the header of the ListView must be captured. When this is done, the WM_SETCURSOR can be discarded. Now the cursor does not change and everything looks correct.

/// <summary>

/// Class used to capture window messages for the header of the list view

/// control.

/// </summary>


private class HeaderControl : NativeWindow
{
    private myListView parentListView = null;

    [DllImport("User32.dll",CharSet = CharSet.Auto,SetLastError=true)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int msg, 
                                   IntPtr wParam, IntPtr lParam);

    public HeaderControl(myListView m)
    {
        parentListView = m;
        //Get the header control handle

        IntPtr header = SendMessage(m.Handle, 
            (0x1000+31), IntPtr.Zero, IntPtr.Zero);
        this.AssignHandle(header);                
    } //constructor HeaderControl()


    protected override void WndProc(ref Message message)
    {
        const int WM_LBUTTONDBLCLK = 0x0203;
        const int WM_SETCURSOR = 0x0020;
        bool callBase = true;

        switch ( message.Msg )
        {
       case WM_SETCURSOR:
            if(parentListView.LockColumnSize)
            {
                //Don't change cursor to sizing cursor.

                message.Result = (IntPtr)1; //Return TRUE from message handler

                callBase = false;           //Don't call the base class.

             } //if

             break;
        } //switch


        if(callBase)
        {
            // pass messages on to the base control for processing

            base.WndProc( ref message ) ;
        } //if

    } //WndProc()

} //class HeaderControl

The control is looking good now. However, there is still a side effect. The user can double click on the header and the column auto-sizes. This turns out to be easy, we just capture and discard the double click for the header. Here is the updated WndProc() to correct this problem.

/// <summary>


/// Class used to capture window messages for the header of the list view

/// control.

/// </summary>

private class HeaderControl : NativeWindow
{
    //...

    
    protected override void WndProc(ref Message message)
    {
        const int WM_LBUTTONDBLCLK = 0x0203;
        const int WM_SETCURSOR = 0x0020;
        bool callBase = true;

        switch ( message.Msg )
        {
        case WM_LBUTTONDBLCLK:                    
        case WM_SETCURSOR:
            if(parentListView.LockColumnSize)
            {
                //Don't change cursor to sizing cursor. Also ignore

                //double click, which sizes the column to fit the data.

                message.Result = (IntPtr)1;    //Return TRUE from message handler

                callBase = false;        //Don't call the base class.

             } //if

             break;
        } //switch


        if(callBase)
        {
            // pass messages on to the base control for processing

            base.WndProc( ref message ) ;
        } //if

    } //WndProc()

} //class HeaderControl

Thanks to the team on Code Project, yet another side effect was discovered. Geert Baeyaert pointed out that CTRL+ was not being handled. The ListView resizes all columns on CTRL+. So after a bit of digging, I found how to capture and discard the CTRL+ key stroke. Here is the bit of code to handle CTRL+:

/// <summary>

/// Capture CTRL+ to prevent resize of all columns.

/// </summary>

protected override void OnKeyDown(KeyEventArgs e)
{
    if(e.KeyValue==107 && e.Modifiers==Keys.Control && locked)
    {
        e.Handled = true;
    }
    else
    {
        base.OnKeyDown (e);
    } //if

} //OnKeyDown()

The control now functions as desired. The option to enable and disable the size of the columns is wrapped in a property called "LockColumnSize", which is shown under the properties in the Behavior section. The property is active at design time, so the column width will need to be set before locking the columns.

Points of Interest

Programming .NET is a whole new way of thinking. My background is programming C for embedded systems. Sometimes the embedded world is easier. You can look at the schematics and datasheets and they clearly tell you what is possible and how to do it. In .NET, there is an ocean of information and it is somewhat difficult to find the solution you are looking for.

I hope you find the control useful and I look forward to hearing comments. I need all the help I can get in learning how to do .NET code properly.

History

  • 2005-09-20: Modified to capture CTRL+ to prevent resizing of all columns.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here