Click here to Skip to main content
15,886,137 members
Articles / Programming Languages / C#
Article

A ListView HitTest for C#

Rate me:
Please Sign up or sign in to vote.
3.70/5 (9 votes)
27 Jan 20042 min read 161.6K   852   32   16
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.

C#
[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.

C#
[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...

C#
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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhat is the license governing the source code of this article? Pin
SGUS11-May-16 22:07
SGUS11-May-16 22:07 
GeneralVB 2008 Express Listview detection cell clicked Pin
Raju Kadam15-Oct-09 12:04
Raju Kadam15-Oct-09 12:04 
QuestionRequest permission to use the ideas or codes. Pin
wilim21-Jan-07 16:17
wilim21-Jan-07 16:17 
General.NET already has this Pin
MrPolite26-Aug-05 7:43
MrPolite26-Aug-05 7:43 
GeneralNET 1.1 does not already have this Pin
Andrew Phillips13-Nov-07 19:25
Andrew Phillips13-Nov-07 19:25 
GeneralLove this article Pin
tot2ivn18-Feb-05 1:09
tot2ivn18-Feb-05 1:09 
GeneralProblem with reorder Pin
besobeso21-Oct-04 14:37
besobeso21-Oct-04 14:37 
GeneralUsing managed-only code Pin
Chopper5-Jun-04 8:13
Chopper5-Jun-04 8:13 
QuestionWhy not use LVM_SUBITEMHITTEST? Pin
Glen Walker9-Feb-04 14:13
Glen Walker9-Feb-04 14:13 
AnswerRe: Why not use LVM_SUBITEMHITTEST? Pin
Djof23-Feb-04 14:19
Djof23-Feb-04 14:19 
GeneralColumn reordering problem Pin
David R. Woods23-Jan-04 12:13
David R. Woods23-Jan-04 12:13 
GeneralHorizontal scrolling Pin
Marc Clifton23-Jan-04 4:21
mvaMarc Clifton23-Jan-04 4:21 
GeneralRe: Horizontal scrolling Pin
row4utc23-Jan-04 10:03
row4utc23-Jan-04 10:03 
GeneralRe: Horizontal scrolling Pin
Bill Menees3-Feb-04 16:33
Bill Menees3-Feb-04 16:33 
GeneralNice. Pin
Marc Clifton23-Jan-04 4:14
mvaMarc Clifton23-Jan-04 4:14 
GeneralRe: Nice. Pin
row4utc23-Jan-04 10:01
row4utc23-Jan-04 10:01 

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

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