Click here to Skip to main content
15,444,203 members
Articles / Programming Languages / C#
Posted 31 May 2003


110 bookmarked

Customizing the header control in a ListView

Rate me:
Please Sign up or sign in to vote.
4.79/5 (28 votes)
31 May 20035 min read
This article describes how to extend the default header control in a ListView in details mode.

This is completely owner-draw header

Figure 1: FullyCustomHeader

Default implementation with images

Figure 2: Default Windows implementation (with images)

Default implementation with images

Figure 3: Default Windows implementation (with images and first column set to owner-draw)


It all began when in an application I needed the column header in a ListView to display an image. I was surprised to find that .NET provides very limited implementation of the HeaderControl common control. With common controls version 4.70 and later, column header supports images from an ImageList and bitmaps. Well, .NET framework requires IE 5.0 at least, so that control version is meant to be available. Another thing not implemented in the default .NET header is the notifications sent to parent list view when the user starts dragging a column or ends dragging a column. I found that very annoying because in an application I wanted to have a hidden combo box which appears with the selected item. But to correspond properly on the column resize, I needed to resize the combo box also. But how to catch an event when there is no delegate for it... So I put all these missing features in one extended list view (it only extends the header control in detailed mode).

Basic idea

The idea is creating a new class MyColumn which has new properties and supports images and owner-draw feature. I am using the existing LVCOLUMN structure and simply specifying an ImageList, image index. The owner-draw flag is set set by HDITEM structure. To add the columns correctly I am using the LVM_INSERTCOLUMN message. The events for handling owner-draw feature and for tracking columns are achieved by overriding the WndProc of the ListView.

Using the code

To use the code you just have to add the CustomHeader.dll to your toolbox and then drag the control on the form. The Columns property is new - it is of type MyHeaderCollection and when adding a column you will see some new properties to appear:

  1. ImageIndex - this is the index from an image list associated with the header.
  2. OwnerDraw - I have added this feature so you can draw your own columns.
  3. ImageOnRight - this works only with OwnerDraw property set to true because the default Windows implementation is not drawing the image correctly.

The new ListView properties are:

  • HeaderImageList - use this to set the ImageList for the header.
  • FullyCustomHeader - use this to draw the entire header, not only the columns - in this case I am giving you the whole surface of the control for drawing.
  • DefaultCustomDraw - you can combine this with FullyCustomHeader and get a completely custom drawn header (the screenshot at the top of the page is that case).
  • HeaderHandle - gets the header control handle.
  • IncreaseHeaderHeight - use this to increase the header control default height. This is actually done by setting bigger font (I tried many other solutions like SetWindowPos and overriding HDM_LAYOUT but none worked correct). So this property simply increases the header's font height with the specified amount. This is useful only with MyColumn.OwnerDraw set to true, because it is not nice to set bigger font and let Windows do the drawing:)).
  • HeaderHeight - gets the header height in pixels.

The new events are:

  • C#
    public event DrawItemEventHandler DrawColumn

    I use the delegate of type DrawItemEventHandler as it suits my needs completely. Use this event to draw a MyColumn object with OwnerDraw property set to true.

  • C#
    public event DrawHeaderEventHandler DrawHeader

    This is a custom delegate type and it is declared like that:

    public delegate void DrawHeaderEventHandler(DrawHeaderEventArgs e)

    Use this event when setting the MyListView.FullyCustomHeader to draw the entire surface of the header.

  • C#
    public event HeaderEventHandler BeginDragHeaderDivider;
    public event HeaderEventHandler DragHeaderDivider;
    public event HeaderEventHandler EndDragHeaderDivider;

    The names of the events are self-explanatory. They are of type:

    public delegate void 
        HeaderEventHandler(object sender, HeaderEventArgs e);


The DrawHeaderEventArgs class inherits from EventArgs class and it is declared like that:

public class DrawHeaderEventArgs : EventArgs
    Graphics graphics;
    Rectangle bounds;
    int height;
    public DrawHeaderEventArgs(Graphics dc, Rectangle rect, int h)
        graphics = dc;
        bounds = rect;
        height = h;
    public Graphics Graphics
        get{return graphics;}
    public Rectangle Bounds
        get{return bounds;}
    public int HeaderHeight
        get{return height;}


I am providing to you the bounds, height and the graphics object to draw on with this DrawHeaderEventArgs class. The HeaderEventArgs class also inherits from EventArgs and is declared like that:

public class HeaderEventArgs : EventArgs
    int columnIndex;
    int mouseButton;
    public HeaderEventArgs(int index, int button)
        columnIndex = index;
        mouseButton = button;
    public int ColumnIndex
        get{return columnIndex;}
    public int MouseButton
        get{return mouseButton;}

InsertColumns method

The most significant part of this event is the index of the column that fires the event - this is provided by the ColumnIndex property.

How the MyColumn objects are set correctly:

void InsertColumns()
    int counter = 0;
    foreach(MyColumn m in myColumns)
        Win32.LVCOLUMN lvc = new Win32.LVCOLUMN();
        lvc.mask = 0x0001|0x0008|0x0002|0x0004; = m.Width;
        lvc.subItem = counter;
        lvc.text = m.Text;
            case HorizontalAlignment.Left:
                lvc.fmt = 0x0000;
            case HorizontalAlignment.Center:
                lvc.fmt = 0x0002;
            case HorizontalAlignment.Right:
                lvc.fmt = 0x0001;
        if(headerImages != null && m.ImageIndex != -1)
            lvc.mask |= 0x0010;//LVCF_IMAGE
            lvc.iImage = m.ImageIndex;
            lvc.fmt |= 0x0800;//LVCFMT_IMAGE
                lvc.fmt |= 0x1000;
        //Send message LVM_INSERTCOLUMN
        Win32.SendMessage(this.Handle,0x1000+97,counter,ref lvc);
        //Check if column is set to owner-draw
        //If so - send message HDM_SETITEM with HDF_OWNERDRAW flag set
            Win32.HDITEM hdi = new Win32.HDITEM();
            hdi.mask = (int)Win32.HDI.HDI_FORMAT;
            hdi.fmt = (int)Win32.HDF.HDF_OWNERDRAW;
            Win32.SendMessage(header.Handle,0x1200+12,counter,ref hdi);

According to MyColumn's properties I am filling a LVCOLUMN structure and then send message LVM_INSERTCOLUMN. We need to check if we have OwnerDraw property set to true - if so, modify the existing HDITEM structure with the new flag - owner-draw.

HeaderControl class

The HeaderControl class has an important role. It inherits from NativeWindow and its only purpose is to receive messages sent to the header control.

internal class HeaderControl : NativeWindow
    MyListView parent;
    bool mouseDown;
    public HeaderControl(MyListView m)
        parent = m;
        //Get the header control handle
        IntPtr header = Win32.SendMessage(parent.Handle, 
                    (0x1000+31), IntPtr.Zero, IntPtr.Zero);
    protected override void WndProc(ref Message m)

The header handle is received via the LVM_GETHEADER message. After getting this handle assign it to the HeaderControl class, so we can override its WndProc.

HeaderControl overriden WndProc

protected override void WndProc(ref Message m)
        case 0x000F://WM_PAINT
                Win32.RECT update = new Win32.RECT();
                if(Win32.GetUpdateRect(m.HWnd,ref update, 
                //Fill the paintstruct
                Win32.PAINTSTRUCT ps = new 
                IntPtr hdc = Win32.BeginPaint(m.HWnd, ref ps);
                //Create graphics object from the hdc
                Graphics g = Graphics.FromHdc(hdc);
                //Get the non-item rectangle
                int left = 0;
                Win32.RECT itemRect = new Win32.RECT();
                for(int i=0; i<parent.Columns.Count; i++)
                    Win32.SendMessage(m.HWnd, 0x1200+7, i, 
                                             ref itemRect);
                    left += itemRect.right-itemRect.left;
                parent.headerHeight = 
                if(left >= ps.rcPaint.left)
                    left = ps.rcPaint.left;
                Rectangle r = new Rectangle(left, 
                Rectangle r1 = new Rectangle(ps.rcPaint.left, 
                //If we have a valid event handler - call it
                if(parent.DrawHeader != null && 
                //Now we have to check if we have 
                //owner-draw columns and fill
                //the DRAWITEMSTRUCT appropriately
                int counter = 0;
                foreach(MyColumn mm in parent.Columns)
                        Win32.DRAWITEMSTRUCT dis = 
                              new Win32.DRAWITEMSTRUCT();
                        dis.ctrlType = 100;//ODT_HEADER
                        dis.hwnd = m.HWnd;
                        dis.hdc = hdc;
                        dis.itemAction = 0x0001;//ODA_DRAWENTIRE
                        dis.itemID = counter;
                        //Must find if some item is pressed
                        Win32.HDHITTESTINFO hi = new 
                        int hotItem = Win32.SendMessage(m.HWnd, 
                                            0x1200+6, 0, ref hi);
                        //If clicked on a divider - 
                        //we don't have hot item
                        if(hi.flags == 0x0004 || hotItem != counter)
                            hotItem = -1;
                        if(hotItem != -1 && mouseDown)
                            dis.itemState = 0x0001;//ODS_SELECTED
                            dis.itemState = 0x0020;
                        Win32.SendMessage(m.HWnd, 0x1200+7, 
                                        counter, ref itemRect);
                        dis.rcItem = itemRect;
                        //Send message WM_DRAWITEM
                                          0x002B,0,ref dis);
                Win32.EndPaint(m.HWnd, ref ps);
                base.WndProc(ref m);
        case 0x0014://WM_ERASEBKGND
            //We don't need to do anything here 
            //in order to reduce flicker
                base.WndProc(ref m);
        case 0x0201://WM_LBUTTONDOWN
            mouseDown = true;
            base.WndProc(ref m);
        case 0x0202://WM_LBUTTONUP
            mouseDown = false;
            base.WndProc(ref m);
        case 0x1200+5://HDM_LAYOUT
            base.WndProc(ref m);
        case 0x0030://WM_SETFONT                    
            if(parent.IncreaseHeaderHeight > 0)
                System.Drawing.Font f = 
                        new System.Drawing.Font(parent.Font.Name,
                        parent.Font.SizeInPoints + 
                m.WParam = f.ToHfont();
            base.WndProc(ref m);
            base.WndProc(ref m);

Here I am checking if we have MyListView.FullyCustomHeader property set to true. If so - fire the DrawHeader event and then check for owner-draw columns - if we have ones, fill a DRAWITEMSTRUCT appropriately and send WM_DRAWITEM message to MyListView. If we haven't FullyCustomHeader - call base.WndProc.

Firing the DrawColumn event through the MyListView WndProc

We have to override the MyListView's WndProc and catch the WM_DRAWITEM message, then fill the DrawItemEventArgs appropriately and if we have a valid event handler - call it!

case 0x002B://WM_DRAWITEM
    //Get the DRAWITEMSTRUCT from the LParam of the message
    Win32.DRAWITEMSTRUCT dis = (Win32.DRAWITEMSTRUCT)Marshal.PtrToStructure(
    //Check if this message comes from the header
    if(dis.ctrlType == 100)//ODT_HEADER - it do comes from the header
        //Get the graphics from the hdc field of the DRAWITEMSTRUCT
        Graphics g = Graphics.FromHdc(dis.hdc);
        //Create a rectangle from the RECT struct
        Rectangle r = new Rectangle(dis.rcItem.left, 
  , dis.rcItem.right -
            dis.rcItem.left, dis.rcItem.bottom -;
        //Create new DrawItemState in its default state                    
        DrawItemState d = DrawItemState.Default;
        //Set the correct state for drawing
        if(dis.itemState == 0x0001)
            d = DrawItemState.Selected;
        //Create the DrawItemEventArgs object
        DrawItemEventArgs e = new 
        //If we have a handler attached call 
        //it and we don't want the default drawing
        if(DrawColumn != null && !defaultCustomDraw)
            DrawColumn(this.Columns[dis.itemID], e);
        else if(defaultCustomDraw)
        //Release the graphics object                    

Firing the BeginDragHeaderDivider, DragHeaderDivider and EndDragHeaderDivider events

These events comes to us as notifications from the header control. All we have to do is make sure we have a valid handler to call it:

case 0x004E://WM_NOTIFY
        base.WndProc(ref m);
        Win32.NMHDR nmhdr = (Win32.NMHDR)m.GetLParam(typeof(Win32.NMHDR));
        case (0-300-26)://HDN_BEGINTRACK
            if(BeginDragHeaderDivider != null)
                    new HeaderEventArgs(nm.iItem, nm.iButton));
        case (0-300-20)://HDN_ITEMCHANGING
            //Adjust the column width
            Win32.RECT rect = new Win32.RECT();
            Win32.SendMessage(header.Handle, 0x1200+7, nm.iItem, ref rect);
            //Get the item height which is actually header's height
            this.headerHeight =;
            this.Columns[nm.iItem].Width = rect.right - rect.left;
            if(DragHeaderDivider != null)
                    new HeaderEventArgs(nm.iItem, nm.iButton));
        case (0-300-27)://HDN_ENDTRACK
            if(EndDragHeaderDivider != null)
                    new HeaderEventArgs(nm.iItem, nm.iButton));

Points of interest

Well, I was thinking also of extending MyListView, so it will support custom drawing and user-defined selection color, border color and column color, but as I found in the CodeProject's articles section that, some people have already made that, so I worked only on the header control. I haven't played with the bitmap field of the LVCOLUMN structure. But to my great surprise if we have the default Windows implementation of the LVCFMT_BITMAP_ON_RIGHT flag set to the column, the image is not drawn correctly!!! Do you see:

Default Windows implementation for LVCFMT_BITMAP_ON_RIGHT:

Default implementation with images

May be it works correctly only when a bitmap is specified...


  • CustomHeader v. 1.0.0


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
Team Leader Telerik Corp.
Bulgaria Bulgaria
.NET & C# addicted. Win8 & WinRT enthusiast and researcher @Telerik.

Comments and Discussions

Questioncheck in the first column Pin
Lander1326-Dec-19 3:03
MemberLander1326-Dec-19 3:03 
GeneralIncreaseHeaderHeight doesn't work outside CustomHeaderTest app Pin
DLChambers7-Jan-10 4:42
MemberDLChambers7-Jan-10 4:42 
GeneralJust want to resize header height Pin
aloplop30-Jun-09 22:27
Memberaloplop30-Jun-09 22:27 
GeneralRe: Just want to resize header height Pin
ketan d patel8-Nov-11 19:09
Memberketan d patel8-Nov-11 19:09 
GeneralRe: Just want to resize header height Pin
aloplop2-Jan-12 1:17
Memberaloplop2-Jan-12 1:17 
GeneralRe: Just want to resize header height Pin
L.Botello8-Jun-14 3:15
MemberL.Botello8-Jun-14 3:15 
GeneralExceptions thrown Pin
H. Tony16-Oct-07 7:16
MemberH. Tony16-Oct-07 7:16 
GeneralRe: Exceptions thrown Pin
legcsabi18-Jul-08 8:37
Memberlegcsabi18-Jul-08 8:37 
GeneralRe: Exceptions thrown Pin
DLChambers7-Jan-10 4:33
MemberDLChambers7-Jan-10 4:33 
GeneralIt doesn't work Pin
SK_30-Apr-07 0:52
MemberSK_30-Apr-07 0:52 
GeneralRe: It doesn't work Pin
Harri Gunther29-Nov-09 2:43
MemberHarri Gunther29-Nov-09 2:43 
GeneralChanging color of the Items and SubItems in the list View Pin
ashwini_m30-Nov-06 9:56
Memberashwini_m30-Nov-06 9:56 
GeneralRe: Changing color of the Items and SubItems in the list View Pin
崔瑞明3-Aug-16 22:23
Member崔瑞明3-Aug-16 22:23 
QuestionHow do you clear columns? Pin
JRQ14-Nov-06 22:16
MemberJRQ14-Nov-06 22:16 
GeneralAdding columns after the handle is created. Pin
Collin Parker9-Aug-06 6:12
MemberCollin Parker9-Aug-06 6:12 
QuestionCan I use this Custom ColumnHeader, on a Smart Device Pin
Trystano30-Apr-06 1:57
MemberTrystano30-Apr-06 1:57 
GeneralHeader text aligning Pin
Antei916-Feb-06 10:29
MemberAntei916-Feb-06 10:29 
QuestionErm... Is it me? Pin
Gavin Roberts5-Oct-05 2:29
MemberGavin Roberts5-Oct-05 2:29 
AnswerRe: Erm... Is it me? Pin
nkarasev24-Oct-05 6:59
Membernkarasev24-Oct-05 6:59 
GeneralDouble Buffering Pin
giiiiif22-Sep-05 7:16
Membergiiiiif22-Sep-05 7:16 
GeneralRe: Double Buffering Pin
giiiiif22-Sep-05 8:59
Membergiiiiif22-Sep-05 8:59 
GeneralRe: Double Buffering Pin
Michael Janulaitis29-Sep-06 10:55
MemberMichael Janulaitis29-Sep-06 10:55 
Generaladding columns at run-time Pin
fenixz29-Aug-05 3:23
Memberfenixz29-Aug-05 3:23 
GeneralA small bug in drawing the header. Pin
iliyang14-Jun-05 11:41
Memberiliyang14-Jun-05 11:41 
GeneralCode Serialization Pin
TigerChan27-Apr-05 16:09
MemberTigerChan27-Apr-05 16:09 

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.