Reputation: 3641
I use a ListView
in my WinForms application, which contains a lot of values and also a few groups. The group headers only show the name of the group, so i want to add a context menu to the group header with an item "Show description" to show a long summary of the Group.
After googling a while I only found third party controls which have this functionality.
How can I add the ContextMenu
to the Group header without using 3rd party software?
Upvotes: 0
Views: 3118
Reputation: 640
The Reza Aghaei's solution works only if the groups are added before the items. In case a group is added after the items have been added already, it fails, because the SendMessage function returns wrong indices. It actually returns the IDs not indices. That is a known problem in the ListView component. The idea is to compare the returned value to the Group 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;
}
}
The property Group.ID is non-public. Here is the extension that extracts it.
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;
}
}
Note that Reflection may be time-consuming depending on the context.
Upvotes: 1
Reputation: 125197
You can send a LVM_HITTEST
message to ListView
. When you pass -1
to wParam
, if the return value is greater than -1
and LVHT_EX_GROUP_HEADER
has been set in the result, the return value of SendMessage
method will be clicked group index.
Implementation
In below implementations, I've added GroupHeaderClick
event to MyListView
class. You can simply handle the event this way:
private void myListView1_GroupHeaderClick(object sender, int e)
{
//Show ContextMenuStrip here. Or just for example:
MessageBox.Show(myListView1.Groups[e].Header);
}
Here is MyListView
implementation:
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);
}
}
Upvotes: 4