C# listview columnheader 右键事件

来源:互联网 发布:休闲网游 知乎 编辑:程序博客网 时间:2024/04/27 15:03

Handling Right-Click Events in ListView Column Headers

By MarkLTX5 Feb 2008
 
Sign Up to vote
vote 1vote 2vote 3vote 4vote 5
 
  • Download source - 16.91 KB

Introduction

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.

Background

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,MouseClickMouseDown, 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.

Details

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 theListView 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).[DllImport("user32.Dll")]private static extern int EnumChildWindows(    IntPtr hWndParent,     EnumWinCallBack callBackFunc,     IntPtr lParam);// Gets the bounding rectangle of the specified window (ListView header bar). [DllImport("user32.dll")]private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [StructLayout(LayoutKind.Sequential)]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;    }    else    {        _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'sContextMenuStrip 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.    EnumChildWindows(        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;                headerMenu.Show(Control.MousePosition);                break;            }        }    }    else    {        // 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).

ListViewMenu.PNG 

HeaderMenu.PNG
原创粉丝点击