ListView 组 Header 单击 - 如何向 ListView 组 Header 添加上下文菜单?
ListView Group Header Click - How to add a context menu to ListView Group Headers?
我在我的 WinForms 应用程序中使用 ListView
,它包含很多值和一些组。组 header 仅显示组的名称,因此我想向组 header 添加一个上下文菜单,其中包含一个项目 "Show description" 以显示该组的详细摘要。
谷歌搜索一段时间后,我只找到了具有此功能的第三方控件。
如何在不使用第 3 方软件的情况下将 ContextMenu
添加到群组 header?
您可以向 ListView
发送 LVM_HITTEST
消息。当你将 -1
传递给 wParam
时,如果 return 值大于 -1
并且结果中已设置 LVHT_EX_GROUP_HEADER
,则 return SendMessage
方法的值将被点击组索引。
实施
在下面的实现中,我将 GroupHeaderClick
事件添加到 MyListView
class。您可以这样简单地处理事件:
private void myListView1_GroupHeaderClick(object sender, int e)
{
//Show ContextMenuStrip here. Or just for example:
MessageBox.Show(myListView1.Groups[e].Header);
}
这里是 MyListView
实现:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyListView : ListView
{
public event EventHandler<int> GroupHeaderClick;
protected virtual void OnGroupHeaderClick(int e)
{
var handler = GroupHeaderClick;
if (handler != null) handler(this, e);
}
private const int LVM_HITTEST = 0x1000 + 18;
private const int LVHT_EX_GROUP_HEADER = 0x10000000;
[StructLayout(LayoutKind.Sequential)]
private struct LVHITTESTINFO
{
public int pt_x;
public int pt_y;
public int flags;
public int iItem;
public int iSubItem;
public int iGroup;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg,
int wParam, ref LVHITTESTINFO ht);
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
var ht = new LVHITTESTINFO() { pt_x = e.X, pt_y = e.Y };
var value = SendMessage(this.Handle, LVM_HITTEST, -1, ref ht);
if (value != -1 && (ht.flags & LVHT_EX_GROUP_HEADER) != 0)
OnGroupHeaderClick(value);
}
}
Reza Aghaei 的解决方案只有在将组添加到项之前才有效。如果在项目已添加后添加组,则会失败,因为 SendMessage 函数 returns 错误的索引。它实际上是 returns ID 而不是索引。这是 ListView 组件中的一个已知问题。这个想法是将返回值与组 ID 进行比较。
public class WListView : ListView
{
#region [ PInvoke ]
private const int LVM_HITTEST = 0x1000 + 18;
private const int LVM_SUBITEMHITTEST = 0x1000 + 57;
private const int LVHT_EX_GROUP_HEADER = 0x10000000;
[StructLayout(LayoutKind.Sequential)]
private struct LVHITTESTINFO
{
public int pt_x;
public int pt_y;
public int flags;
public int iItem;
public int iSubItem;
public int iGroup;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref LVHITTESTINFO ht);
#endregion
/// <summary>
/// Occurs when a group is clicked.
/// </summary>
[Category("Behavior")]
[Description("Occurs when a group header is clicked.")]
public event EventHandler<ListViewGroupClickEventArgs> GroupClick;
/// <summary>
/// Raises the GroupClick event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected virtual void OnGroupHeaderClick(ListViewGroupClickEventArgs e)
{
GroupClick?.Invoke(this, e);
}
/// <summary>
/// Raises the Control.MouseDoubleClick event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
var group = TestGroupHit(e);
if (group == null)
{
return;
}
switch (e.Clicks)
{
case 1:
OnGroupHeaderClick(new ListViewGroupClickEventArgs(group));
break;
}
}
private ListViewGroup TestGroupHit(MouseEventArgs e)
{
var ht = new LVHITTESTINFO { pt_x = e.X, pt_y = e.Y };
var msg = View == System.Windows.Forms.View.Details ? LVM_SUBITEMHITTEST : LVM_HITTEST;
var value = SendMessage(Handle, msg, -1, ref ht);
if (value != -1 && (ht.flags & LVHT_EX_GROUP_HEADER) != 0)
{
return FindGroupByID(value);
}
return null;
}
private ListViewGroup FindGroupByID(int id)
{
foreach (ListViewGroup group in Groups)
{
if (group.ExtractID() == id)
{
return group;
}
}
return null;
}
}
属性 Group.ID 非public。这是提取它的扩展。
public static int ExtractID(this ListViewGroup group)
{
try
{
return (int) group
.GetType()
.GetProperty("ID", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(group, new object[0]);
}
catch
{
return -1;
}
}
请注意,根据上下文,反射可能很耗时。
我在我的 WinForms 应用程序中使用 ListView
,它包含很多值和一些组。组 header 仅显示组的名称,因此我想向组 header 添加一个上下文菜单,其中包含一个项目 "Show description" 以显示该组的详细摘要。
谷歌搜索一段时间后,我只找到了具有此功能的第三方控件。
如何在不使用第 3 方软件的情况下将 ContextMenu
添加到群组 header?
您可以向 ListView
发送 LVM_HITTEST
消息。当你将 -1
传递给 wParam
时,如果 return 值大于 -1
并且结果中已设置 LVHT_EX_GROUP_HEADER
,则 return SendMessage
方法的值将被点击组索引。
实施
在下面的实现中,我将 GroupHeaderClick
事件添加到 MyListView
class。您可以这样简单地处理事件:
private void myListView1_GroupHeaderClick(object sender, int e)
{
//Show ContextMenuStrip here. Or just for example:
MessageBox.Show(myListView1.Groups[e].Header);
}
这里是 MyListView
实现:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyListView : ListView
{
public event EventHandler<int> GroupHeaderClick;
protected virtual void OnGroupHeaderClick(int e)
{
var handler = GroupHeaderClick;
if (handler != null) handler(this, e);
}
private const int LVM_HITTEST = 0x1000 + 18;
private const int LVHT_EX_GROUP_HEADER = 0x10000000;
[StructLayout(LayoutKind.Sequential)]
private struct LVHITTESTINFO
{
public int pt_x;
public int pt_y;
public int flags;
public int iItem;
public int iSubItem;
public int iGroup;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg,
int wParam, ref LVHITTESTINFO ht);
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
var ht = new LVHITTESTINFO() { pt_x = e.X, pt_y = e.Y };
var value = SendMessage(this.Handle, LVM_HITTEST, -1, ref ht);
if (value != -1 && (ht.flags & LVHT_EX_GROUP_HEADER) != 0)
OnGroupHeaderClick(value);
}
}
Reza Aghaei 的解决方案只有在将组添加到项之前才有效。如果在项目已添加后添加组,则会失败,因为 SendMessage 函数 returns 错误的索引。它实际上是 returns ID 而不是索引。这是 ListView 组件中的一个已知问题。这个想法是将返回值与组 ID 进行比较。
public class WListView : ListView
{
#region [ PInvoke ]
private const int LVM_HITTEST = 0x1000 + 18;
private const int LVM_SUBITEMHITTEST = 0x1000 + 57;
private const int LVHT_EX_GROUP_HEADER = 0x10000000;
[StructLayout(LayoutKind.Sequential)]
private struct LVHITTESTINFO
{
public int pt_x;
public int pt_y;
public int flags;
public int iItem;
public int iSubItem;
public int iGroup;
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref LVHITTESTINFO ht);
#endregion
/// <summary>
/// Occurs when a group is clicked.
/// </summary>
[Category("Behavior")]
[Description("Occurs when a group header is clicked.")]
public event EventHandler<ListViewGroupClickEventArgs> GroupClick;
/// <summary>
/// Raises the GroupClick event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected virtual void OnGroupHeaderClick(ListViewGroupClickEventArgs e)
{
GroupClick?.Invoke(this, e);
}
/// <summary>
/// Raises the Control.MouseDoubleClick event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
var group = TestGroupHit(e);
if (group == null)
{
return;
}
switch (e.Clicks)
{
case 1:
OnGroupHeaderClick(new ListViewGroupClickEventArgs(group));
break;
}
}
private ListViewGroup TestGroupHit(MouseEventArgs e)
{
var ht = new LVHITTESTINFO { pt_x = e.X, pt_y = e.Y };
var msg = View == System.Windows.Forms.View.Details ? LVM_SUBITEMHITTEST : LVM_HITTEST;
var value = SendMessage(Handle, msg, -1, ref ht);
if (value != -1 && (ht.flags & LVHT_EX_GROUP_HEADER) != 0)
{
return FindGroupByID(value);
}
return null;
}
private ListViewGroup FindGroupByID(int id)
{
foreach (ListViewGroup group in Groups)
{
if (group.ExtractID() == id)
{
return group;
}
}
return null;
}
}
属性 Group.ID 非public。这是提取它的扩展。
public static int ExtractID(this ListViewGroup group)
{
try
{
return (int) group
.GetType()
.GetProperty("ID", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(group, new object[0]);
}
catch
{
return -1;
}
}
请注意,根据上下文,反射可能很耗时。