Click here to Skip to main content
15,861,125 members
Articles / Programming Languages / C#
Article

Locking ListView Column Size

Rate me:
Please Sign up or sign in to vote.
4.77/5 (21 votes)
20 Sep 20053 min read 170.9K   1.5K   38   30
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.

C#
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.

C#
/// <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.

C#
/// <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+:

C#
/// <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


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThe answers was not working for me. The bellow code worked. Pin
Aginjith GJ8-Nov-23 18:13
Aginjith GJ8-Nov-23 18:13 
QuestionHow to disable individual columns? Pin
szanni19-Sep-20 10:29
szanni19-Sep-20 10:29 
GeneralGreat article / control, but seems to be x86 only Pin
Walter Wittel5-Oct-11 6:01
Walter Wittel5-Oct-11 6:01 
AnswerRe: Great article / control, but seems to be x86 only Pin
Member 122078764-Jul-17 22:02
Member 122078764-Jul-17 22:02 
GeneralAwesome control Pin
Vivek_Minhas30-Mar-09 23:10
Vivek_Minhas30-Mar-09 23:10 
GeneralC++ WTL style Pin
T800G26-Nov-08 10:51
T800G26-Nov-08 10:51 
QuestionItem Selection Pin
1stFalloutBoy13-May-08 2:35
1stFalloutBoy13-May-08 2:35 
GeneralMuch simpler way Pin
spintz15-Nov-07 4:10
spintz15-Nov-07 4:10 
GeneralRe: Much simpler way Pin
Rayan Isran27-Nov-11 7:05
Rayan Isran27-Nov-11 7:05 
Generallast item removal Pin
numbers1thru928-Dec-06 7:24
numbers1thru928-Dec-06 7:24 
GeneralRe: last item removal Pin
Chris Morgan15-Jan-07 7:48
Chris Morgan15-Jan-07 7:48 
GeneralRe: last item removal Pin
numbers1thru915-Jan-07 11:25
numbers1thru915-Jan-07 11:25 
GeneralI have translated into VB.NET, but i met a problem. Pin
Lukecao14-Sep-06 0:01
Lukecao14-Sep-06 0:01 
GeneralRe: I have translated into VB.NET, but i met a problem. Pin
Chris Morgan14-Sep-06 4:28
Chris Morgan14-Sep-06 4:28 
GeneralRe: I have translated into VB.NET, but i met a problem. Pin
Lukecao14-Sep-06 22:02
Lukecao14-Sep-06 22:02 
GeneralRe: I have translated into VB.NET, but i met a problem. Pin
Chris Morgan15-Sep-06 2:30
Chris Morgan15-Sep-06 2:30 
GeneralAn easier solution in .NET 2.0 Pin
F2denmark5-Jun-06 1:16
F2denmark5-Jun-06 1:16 
GeneralRe: An easier solution in .NET 2.0 [modified] Pin
garyjohn_200016-Sep-07 11:56
garyjohn_200016-Sep-07 11:56 
GeneralRe: An easier solution in .NET 2.0 Pin
Andrew Phillips16-Oct-07 18:49
Andrew Phillips16-Oct-07 18:49 
GeneralRe: An easier solution in .NET 2.0 Pin
shuami3-Jan-08 17:44
shuami3-Jan-08 17:44 
GeneralRe: An easier solution in .NET 2.0 Pin
Member 182304426-May-10 4:41
Member 182304426-May-10 4:41 
Just use e.ColumnIndex:

if (e.ColumnIndex == ColumnIndexToLock)
{
e.NewWidth = LV.Columns[e.ColumnIndex].Width;
e.Cancel = true;
}
GeneralRe: An easier solution in .NET 2.0 Pin
Renfield7811-Feb-11 2:39
Renfield7811-Feb-11 2:39 
GeneralRe: An easier solution in .NET 2.0 Pin
Magina Ogre21-Mar-12 22:27
Magina Ogre21-Mar-12 22:27 
GeneralMinimum Column Width Pin
ANIL KUMAR SHARMA (INDIA)25-May-06 22:26
ANIL KUMAR SHARMA (INDIA)25-May-06 22:26 
Generallistview sorting Pin
portia vandemere9-Jan-06 10:05
portia vandemere9-Jan-06 10:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.