Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A ListView HitTest for C#

0.00/5 (No votes)
27 Jan 2004 1  
An article on adding a HitTest method for a ListView control.

Introduction

For those of you who are making the move from C++ to C#, you may be looking for a HitTest method such as the one commonly found around the web for C++. A HitTest method is a method used on C#'s ListView control (CListCtrl in C++/MFC) that when the mouse is clicked on the control, gives the programmer the row and column numbers where the mouse was clicked. At first, this was very basic. But, as I got responses from it for certain cases that would not work, the code became a littler more involved. Anyway, here is the latest.

Using the code

Simply add a ListView control to a Form, add some columns, and be sure to...

  • Set ListView "View" property to Details (HitTest pointless otherwise).
  • Set ListView "Full Row Select" property to true (If not, HitTest fails on all but first column).
  • Add some rows to the control (HitTest returns false if clicked on non-existent row).
  • You may choose to enable "Allow Column Reordering" or not. The code works for both.

For the HitTest to work properly, we must add a few other things to your class. First, we must add a structure to our class. Somewhere inside your class definition, add the code below to be able to create "C++ - like" RECT objects.

[StructLayout(LayoutKind.Sequential)]
struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

Next, we must add some messaging methods. We must add a SendMessage method that will allow us to get each subitem's bounding rectangle. A different parameterized SendMessage method must be added to allow us to get the column order array for the case that the columns in the ListView have been reordered by the user.

[DllImport("User32.dll")]
private static extern int SendMessage(IntPtr hWnd, 
  int msg , int wParam , ref RECT lParam);

[DllImport("User32.dll")]
private static extern int SendMessage(IntPtr hWnd, 
  int msg , int wParam , int[] lParam);

Lastly, the HitTest method is as follows...

private bool HitTest(Point hitPoint, out int row, out int column)
{
    const int LVM_GETSUBITEMRECT  = 0x1038;    //Is LVM_FIRST (0x1000) + 56

    const int LVM_COLUMNORDERARRAY  = 0x103B;  //Is LVM_FIRST (0x1000) + 59

    const int LVIR_BOUNDS = 0;
    bool retval = false;
    RECT subItemRect;
    row = column = -1;
    ListViewItem item = m_lvListView.GetItemAt(hitPoint.X, hitPoint.Y);

    if(item != null && m_lvListView.Columns.Count > 1)
    {
        if(m_lvListView.AllowColumnReorder)
        {
            int[] columnOrder = new int[m_lvListView.Columns.Count];
            // Get the order of columns in case

            // they've changed from the user.

            if(SendMessage(m_lvListView.Handle, 
                LVM_COLUMNORDERARRAY, m_lvListView.Columns.Count,
                columnOrder) != 0)
            {
                int i;
                // Get the subitem rectangles (except column 0), 

                // but get them in the proper order.

                RECT[] subItemRects = new RECT[m_lvListView.Columns.Count];
                for(i = 1; i < m_lvListView.Columns.Count; i++)
                {
                    subItemRects[columnOrder[i]].top = i;
                    subItemRects[columnOrder[i]].left = LVIR_BOUNDS;
                    SendMessage(m_lvListView.Handle, 
                       LVM_GETSUBITEMRECT, item.Index, 
                       ref subItemRects[columnOrder[i]]);              
                }

                // Find where column 0 is.

                for(i = 0; i < columnOrder.Length; i++)
                    if(columnOrder[i] == 0)
                        break;
                
                // Fix column 0 since we can't get 

                // the rectangle bounds of it using above.

                if(i > 0)
                {
                    // If column 0 not at index 0, set using the previous.

                    subItemRects[i].left = subItemRects[i-1].right;
                    subItemRects[i].right = subItemRects[i].left 
                        + m_lvListView.Columns[0].Width;
                }
                else
                {
                    // Else, column 0 is at index 0, so use the next.

                    subItemRects[0].left = subItemRects[1].left - 
                         m_lvListView.Columns[0].Width;
                    subItemRects[0].right = subItemRects[1].left;
                }

                // Go through the subitem rectangle bounds and 

                // see where our point is.

                for(int index = 0; index < subItemRects.Length; index++)
                {
                    if(hitPoint.X >= subItemRects[index].left & 
                         hitPoint.X <= subItemRects[index].right)
                    {
                        row = item.Index;
                        column = columnOrder[index];
                        retval = true;
                        break;
                    }
                }
            }
        }
        // No column reordering...much simpler.

        else
        {
            for(int index = 1; index <= m_lvListView.Columns.Count-1; 
                   index++)
            {
                subItemRect = new RECT();
                subItemRect.top = index;
                subItemRect.left = LVIR_BOUNDS;
                if(SendMessage(m_lvListView.Handle, 
                     LVM_GETSUBITEMRECT, item.Index, ref subItemRect) != 0)
                {
                    if(hitPoint.X < subItemRect.left)
                    {
                        row = item.Index;
                        column = 0;
                        retval = true;
                        break;
                    }
                    if(hitPoint.X >= subItemRect.left & hitPoint.X <= 
                          subItemRect.right)
                    {
                        row = item.Index;
                        column = index;
                        retval = true;
                        break;
                    }
                }
            }
        }
    }
    return retval;
}

History

HitTest test application 1.0.0.

  • Original revision.

HitTest test application 1.0.1.

  • Use "out" parameters instead of "ref". More suitable.

HitTest test application 2.0.0.

  • Now gives the correct column number if the ListView control is horizontally scrolled.
  • Now allows the programmer to "Allow Column Reordering" and it will still work and give the correct initial column number.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here