65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的 ListView HitTest

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.70/5 (9投票s)

2004年1月23日

2分钟阅读

viewsIcon

163543

downloadIcon

856

一篇关于为 ListView 控件添加点击检测方法的文章。

引言

对于那些从 C++ 迁移到 C# 的开发者来说,您可能正在寻找一种类似于在网上常见的 C++ 的 HitTest 方法。HitTest 方法是一种用于 C# 的 ListView 控件(C++/MFC 中的 CListCtrl)的方法,当鼠标在控件上点击时,它会向程序员提供鼠标点击的行号和列号。最初,这个方法非常基础。但是,随着我收到一些关于某些情况下无法正常工作的反馈,代码变得稍微复杂一些。无论如何,这是最新的版本。

使用代码

只需将一个 ListView 控件添加到 Form 中,添加一些列,并确保...

  • ListView 的“View”属性设置为 Details(否则点击检测没有意义)。
  • ListView 的“Full Row Select”属性设置为 true(如果不是,点击检测在第一列以外的所有列都会失败)。
  • 向控件添加一些行(如果点击不存在的行,点击检测将返回 false)。
  • 您可以选择启用“Allow Column Reordering”或不启用。代码对两者都有效。

为了使 HitTest 正常工作,我们必须在您的类中添加一些其他内容。首先,我们必须在我们的类中添加一个结构体,以便能够创建类似于 C++ 的 RECT 对象。

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

接下来,我们必须添加一些消息传递方法。我们必须添加一个 SendMessage 方法,它将允许我们获取每个子项的边界矩形。必须添加另一个带参数的 SendMessage 方法,以允许我们获取列顺序数组,以防 ListView 中的列已被用户重新排序。

[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);

最后,HitTest 方法如下...

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;
}

历史

HitTest 测试应用程序 1.0.0。

  • 原始版本。

HitTest 测试应用程序 1.0.1。

  • 使用“out”参数代替“ref”。更合适。

HitTest 测试应用程序 2.0.0。

  • 现在,如果 ListView 控件水平滚动,则会给出正确的列号。
  • 现在允许程序员“Allow Column Reordering”,它仍然可以工作并给出正确的初始列号。
© . All rights reserved.