Click here to Skip to main content
13,512,023 members
Click here to Skip to main content
Add your own
alternative version


51 bookmarked
Posted 31 Jan 2008

Handling Right-Click Events in ListView Column Headers

, 5 Feb 2008
Rate this:
Please Sign up or sign in to vote.
.NET WinForms doesn't provide any way to handle right-click events on column headers. This C# code shows how to determine which header was right-clicked.


If you've ever wanted to handle the right-click event on a ListView column header, you probably discovered there is no way to do it with the standard events and objects provided by the .NET Framework. This article explains how to determine if the user right-clicks a header (vs. anywhere else) in a ListView control and which header was clicked. You can then display the appropriate context menu or perform other processing specific to that header.


I ran into this issue while developing the TracerX logger/viewer for .NET. I wanted the user to be able to right-click a column header (e.g. Thread or Logger) and get a context menu with commands applicable to that column. Unfortunately, the ListView class does not have a RightClick event (nor does the ColumnHeader class). Furthermore, the following events are not even raised when the user right-clicks the header bar: Click, MouseClick, MouseDown, and ColumnClick.

Fortunately, I found that if the ListView control has a context menu, it is displayed whenever the user right-clicks anywhere on the ListView, including the headers. Therefore, the solution to this problem starts with an event handler for the context menu's Opening event. If the handler discovers that the mouse pointer is in the header bar area, it cancels the Opening event, determines which header was clicked, and displays the context menu for that header instead of the one for the ListView.


The key is getting the bounding rectangle of the header bar so we can determine if it contains the mouse pointer. Poking around with Spy++ reveals that the header bar has its own window that is the only child window of the ListView window. I used P/Invoke to call EnumChildWindows to get a handle to the header bar window. One of this function's parameters is a callback (delegate) that gets called for every child window found (the header bar is the only one). I passed a managed code method that sets a member variable to the rectangle occupied by the header bar. Here are the corresponding declarations.

// The area occupied by the ListView header. 
private Rectangle _headerRect;
// Delegate that is called for each child window of the ListView. 
private delegate bool EnumWinCallBack(IntPtr hwnd, IntPtr lParam);

// Calls EnumWinCallBack for each child window of hWndParent (i.e. the ListView).
private static extern int EnumChildWindows(
    IntPtr hWndParent, 
    EnumWinCallBack callBackFunc, 
    IntPtr lParam);

// Gets the bounding rectangle of the specified window (ListView header bar). 
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); 

private struct RECT
    public int Left;
    public int Top;
    public int Right;
    public int Bottom; 

Here's the callback method (passed to and called through EnumChildWindows) that sets _headerRect to the area of the header bar:

// This should get called with the only child window of the ListView,
// which should be the header bar.
private bool EnumWindowCallBack(IntPtr hwnd, IntPtr lParam)
    // Determine the rectangle of the ListView header bar and save it in _headerRect.
    RECT rct;
    if (!GetWindowRect(hwnd, out rct))
        _headerRect = Rectangle.Empty;
        _headerRect = new Rectangle(
        rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top);
    return false; // Stop the enum

Now we can get the current position of the mouse pointer and determine if it is in _headerRect. If so, we determine which particular header the mouse is on by adding up the width of each header until the sum exceeds the X offset of the mouse position. The only caveat is that we must add the column widths in the order they are currently displayed, which the user can change by dragging the headers around. The following method returns an array of headers in the correct order:

// This returns an array of ColumnHeaders in the order they are
// displayed by the ListView.  
private static ColumnHeader[] GetOrderedHeaders(ListView lv)
    ColumnHeader[] arr = new ColumnHeader[lv.Columns.Count];

    foreach (ColumnHeader header in lv.Columns)
        arr[header.DisplayIndex] = header;

    return arr;

Now we can write the event handler that calls all the preceding code. In the sample project attached to this article, the Form object contains two context menus: regularListViewMenu and headerMenu. The ListView's ContextMenuStrip property is set to the former. Here is the context menu's Opening event handler:

// Called when the user right-clicks anywhere in the ListView, including the
// header bar.  It displays the appropriate context menu for the ListView or
// header that was right-clicked. 
private void regularListViewMenu_Opening(object sender, CancelEventArgs e)
    // This call indirectly calls EnumWindowCallBack which sets _headerRect
    // to the area occupied by the ListView's header bar.
        listView1.Handle, new EnumWinCallBack(EnumWindowCallBack), IntPtr.Zero);

    // If the mouse position is in the header bar, cancel the display
    // of the regular context menu and display the column header context 
    // menu instead.
    if (_headerRect.Contains(Control.MousePosition))
        e.Cancel = true;

        // The xoffset is how far the mouse is from the left edge of the header.
        int xoffset = Control.MousePosition.X - _headerRect.Left;

         // Iterate through the column headers in the order they are displayed, 
         // adding up their widths as we go.  When the sum exceeds the xoffset, 
         // we know the mouse is on the current header. 
        int sum = 0;
        foreach (ColumnHeader header in GetOrderedHeaders(listView1))
            sum += header.Width;
            if (sum > xoffset)
                // This code displays the same context menu for 
                // every header, but changes the menu item
                // text based on the header. It sets the context 
                // menu tag to the header object so
                // the handler for whatever command the user 
                // clicks can know the column.
                headerMenu.Tag = header;
                headerMenu.Items[0].Text = "Command for Header " + header.Text;
        // Allow the regular context menu to be displayed.
        // We may want to update the menu here.

The following screen shots illustrate both context menus (the upper left corner of each menu is at the point where the mouse was pointing when the right mouse button was clicked).




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


About the Author

Software Developer (Senior)
United States United States
Mark Lauritsen has been a software developer for as long as he can remember.

You may also be interested in...


Comments and Discussions

Questioncapture header without context menu Pin
Member 950575621-Nov-12 20:20
memberMember 950575621-Nov-12 20:20 
AnswerRe: capture header without context menu Pin
MarkLTX23-Nov-12 4:44
memberMarkLTX23-Nov-12 4:44 
GeneralMy vote of 5 Pin
Big D16-Feb-11 10:24
memberBig D16-Feb-11 10:24 
AnswerI have a much easier solution... Pin
Darki69912-Jan-11 5:44
memberDarki69912-Jan-11 5:44 
GeneralRe: I have a much easier solution... Pin
MarkLTX13-Jan-11 14:56
memberMarkLTX13-Jan-11 14:56 
GeneralRe: I have a much easier solution... [modified] Pin
Darki69925-Jan-11 13:32
memberDarki69925-Jan-11 13:32 
GeneralRe: I have a much easier solution... [modified] Pin
Member 14945549-May-13 1:45
memberMember 14945549-May-13 1:45 
GeneralRe: I have a much easier solution... Pin
ezbUltra2-Aug-11 4:59
memberezbUltra2-Aug-11 4:59 
QuestionHow can I do this in VB.NET? Pin
VinhTien21-Dec-10 4:49
memberVinhTien21-Dec-10 4:49 
Generalslightly easier way Pin
toddsecond21-Aug-10 0:53
membertoddsecond21-Aug-10 0:53 
GeneralMy vote of 4 Pin
toddsecond21-Aug-10 0:46
membertoddsecond21-Aug-10 0:46 
GeneralAnother way Pin
grelle30-Mar-10 8:30
membergrelle30-Mar-10 8:30 
GeneralNicely Done - Get's my 5! -- One Small Improvement Suggestion Pin
wlburgess8-Mar-10 10:25
memberwlburgess8-Mar-10 10:25 
GeneralRe: Nicely Done - Get's my 5! -- One Small Improvement Suggestion Pin
MarkLTX10-Mar-10 16:48
memberMarkLTX10-Mar-10 16:48 
GeneralThank yooooooooooou :) Pin
PanzerPanz17-Aug-09 15:13
memberPanzerPanz17-Aug-09 15:13 
Questionits possible to run the code under windows mobile? Pin
Nahasapeemapetilon2213-Feb-09 4:56
memberNahasapeemapetilon2213-Feb-09 4:56 
AnswerRe: its possible to run the code under windows mobile? Pin
MarkLTX13-Feb-09 6:56
memberMarkLTX13-Feb-09 6:56 
GeneralThanks! Pin
JohnWillemse11-Feb-09 21:11
memberJohnWillemse11-Feb-09 21:11 
GeneralHorizontal scroll Pin
Pavel Kostromitinov25-Dec-08 4:48
memberPavel Kostromitinov25-Dec-08 4:48 
GeneralRe: Horizontal scroll Pin
Pavel Kostromitinov25-Dec-08 4:53
memberPavel Kostromitinov25-Dec-08 4:53 
GeneralRe: Horizontal scroll Pin
MarkLTX25-Dec-08 5:12
memberMarkLTX25-Dec-08 5:12 
QuestionUsing the code with custom ContextMenu Pin
johram16-Jun-08 1:19
memberjohram16-Jun-08 1:19 
AnswerRe: Using the code with custom ContextMenu Pin
johram16-Jun-08 1:23
memberjohram16-Jun-08 1:23 
QuestionWhy the 1 votes? Pin
MarkLTX23-Apr-08 6:37
memberMarkLTX23-Apr-08 6:37 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.180417.1 | Last Updated 5 Feb 2008
Article Copyright 2008 by MarkLTX
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid