简体   繁体   中英

How to know which header column is right clicked in a ListView?

I found a way to handler right click in a ListView Header column.

I needed to define a new ListView item and override WndProc .

public class ListViewUpdated : ListView
{
    public ContextMenuStrip HeaderContextMenu { get; set; }
    private frmMain mainForm;


    public ListViewUpdated()
    {
        mainForm = null;
    }

    public void setAttributes(frmMain frm, ContextMenuStrip menu)
    {
        mainForm = frm;
        HeaderContextMenu = menu;
    }

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == 0x7b)
        {  //WM_CONTEXTMENU
            if (m.WParam != this.Handle)
            {
                mainForm.Invoke(mainForm.delegateDeleteColumn,
                                  new Object[] { HeaderContextMenu });
            }
        }
    }

    private void InitializeComponent()
    {
        this.SuspendLayout();
        this.ResumeLayout(false);

    }
}

This does work fine. I used a delegate so the event trigger could be a function within the form.

The event so far within the form is this:

    if (tabControl1.SelectedIndex == 0)
    {
        System.Drawing.Point mousePos = lsvRequestsByParameters.PointToClient(Control.MousePosition);
        hitTest = lsvRequestsByParameters.HitTest(mousePos);
    }
    else
    {
        System.Drawing.Point mousePos = lsvRequestsByParametersToSave.PointToClient(Control.MousePosition);
        hitTest = lsvRequestsByParametersToSave.HitTest(mousePos);
    }
    HeaderContextMenu.Show(Control.MousePosition);

It is called properly.

It does allow me to popup the context menu on the header.

The problem is one of the option is delete column:

private void deleteColumnToolStripMenuItem2_Click(object sender, EventArgs e)
{
    int columnIndex = hitTest.Item.SubItems.IndexOf(hitTest.SubItem);
    lsvRequestsByParameters.Columns.RemoveAt(columnIndex);
}

Problem is the following line:

int columnIndex = hitTest.Item.SubItems.IndexOf(hitTest.SubItem);

It seems that when I set the hitTest variable, if the mouse is over a header, it doesn't return a content usable. Note that using hitTest this way would return me the columnIndex properly if I click on a empty cell.

So what can I do instead to obtain the columnIndex ?

As long as there are Items in the ListView you could fool the HitTest by feeding in a location that is below the header..:

Point eLoc = listView1.PointToClient(Control.MousePosition);
ListViewHitTestInfo hit = null;
int i = 0;
do
{
    Point vp = new Point(eLoc.X, listView1.Items[i].Bounds.Top);
    hit = listView1.HitTest(vp);
    i++;
}
while( hit.SubItem == null && i < listView1.Items.Count);
var ColumnIndex = hit.Item.SubItems.IndexOf(hit.SubItem);

Note that the loop is only necessary if your SubItems are jagged. Otherwise you can simply always go for Items[0] .


Update:

If for reason you want to allow clicking the headers of an empty ListView , you can use this workaround:

Declare a variable that holds all the bounding rectangles of the headers:

 private List<Rectangle> lvHeaderBounds = new List<Rectangle>();

In the above code that processes the ColumnClick use this:

 Point eLoc = listView1.PointToClient(Control.MousePosition);
 var ColumnIndex = -1;
 if (listView1.Items.Count == 0)
 {
      getHeaders(listView1);
      for (int i = 0; i < lvHeaderBounds.Count; i++)
           if (lvHeaderBounds[i].X + lvHeaderBounds[i].Width > eLoc.X)
           {
                ColumnIndex =  i;
                break;
           }
 }
 else...

To collect the bounding rectangles we need to loop over the ColumHeader collection. Here is a function that achieves this:

void getHeaders(ListView lv)
{
    lvHeaderBounds.Clear();
    for (int i = 0; i < lv.Columns.Count; i++)
    {
        ColumnHeader ch = lv.Columns[i];
        int left = i == 0 ? 0 : lvHeaderBounds[i-1].X + ch.Width;
        Rectangle r = new Rectangle(left, 0, ch.Width, 0);
        lvHeaderBounds.Add(r);
    }
}

We also need to refresh our collection when the user or code modifies the header layout:

    private void listView1_ColumnReordered(object sender, ColumnReorderedEventArgs e)
    {
        if (lvHeaderBounds != null) lvHeaderBounds.Clear();
        getHeaders(sender as ListView);
    }

    private void listView1_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
    {
        if (lvHeaderBounds != null) lvHeaderBounds.Clear();
        getHeaders(sender as ListView);
    }

Note that the Height values in our list are all 0 !

Also note that in my tests the code works even after resizing the headers and/or scrolling them to the right..

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM