Click here to Skip to main content
Click here to Skip to main content

A ListView HitTest for C#

By , 27 Jan 2004
Rate this:
Please Sign up or sign in to vote.

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

About the Author

row4utc

United States United States
No Biography provided

Comments and Discussions

 
GeneralVB 2008 Express Listview detection cell clicked PinmemberRaju Kadam15-Oct-09 12:04 
QuestionRequest permission to use the ideas or codes. Pinmemberwilim21-Jan-07 16:17 
General.NET already has this PinmemberMrPolite (Kourosh Derakshan)26-Aug-05 7:43 
GeneralNET 1.1 does not already have this PinmemberAndrew Phillips13-Nov-07 19:25 
GeneralLove this article Pinsusstot2ivn18-Feb-05 1:09 
GeneralProblem with reorder Pinmemberbesobeso21-Oct-04 14:37 
GeneralUsing managed-only code PinmemberChopper5-Jun-04 8:13 
QuestionWhy not use LVM_SUBITEMHITTEST? PinmemberGlen Walker9-Feb-04 14:13 
AnswerRe: Why not use LVM_SUBITEMHITTEST? PinmemberDjof23-Feb-04 14:19 
GeneralColumn reordering problem PinmemberDavid R. Woods23-Jan-04 12:13 
GeneralHorizontal scrolling PineditorMarc Clifton23-Jan-04 4:21 
GeneralRe: Horizontal scrolling Pinmemberrow4utc23-Jan-04 10:03 
GeneralRe: Horizontal scrolling PinmemberBill Menees3-Feb-04 16:33 
GeneralNice. PineditorMarc Clifton23-Jan-04 4:14 
GeneralRe: Nice. Pinmemberrow4utc23-Jan-04 10:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 28 Jan 2004
Article Copyright 2004 by row4utc
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid