C# 中的 ListView HitTest






3.70/5 (9投票s)
2004年1月23日
2分钟阅读

163543

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”,它仍然可以工作并给出正确的初始列号。