在 ListView 中嵌入控件






4.82/5 (104投票s)
如何使用 ListView 单元格中的任意控件。
引言
最近,我在各种新闻组中遇到了几个关于如何在 ListView
中嵌入控件的请求。
CP 上有几个所有者绘制的 ListView 控件,但我想尝试按照我的意愿来调整标准的 ListView
... ;)
方法
当您打算在ListView
中嵌入控件时,您必须确保它始终位于正确的位置。这在几种方式上可能会很困难
ListViewSubItem
的位置和大小可以通过多种方式进行修改(例如,调整ListView
的大小、滚动、调整ColumnHeader
的大小等)。ListView
的默认实现没有任何方法可以告知您ListViewSubItem
的大小和位置。- 列可以重新排序。
ListViewItem
可以排序。
确保正确位置的最简单方法是就在绘制发生的地方,所以我决定重写 ListView
的 WndProc
并侦听 WM_PAINT
作为计算控件位置的触发器。
可能还有其他更有效的方法,但那样就很难处理所有需要重新定位控件的情况了。此外,我并没有发现大量嵌入控件时存在性能问题。
获取单元格的位置和大小
这有点棘手,因为标准的 ListView
在这方面不会帮助您。它确实有一个 GetItemRect()
方法,但它只为您提供整个 ListViewItem
的信息。无法在此处检索特定 ListViewSubItem
的边界。
幸运的是,我在我之前的文章(ListView 子项的就地编辑)中也遇到了相同的问题,因此所需的函数已经可用。
基本上,我从 GetItemRect()
获取单元格的高度和垂直位置,并根据当前的 ColumnHeader
计算其水平位置和宽度。
要计算单元格的左边距,您只需将您单元格左侧(即索引小于您 ListViewSubItem
索引)的所有 ColumnHeader
的宽度相加,对吧?不幸的是,不是。用户可以重新排序列,而 ListView
的 Columns
集合不会反映这些更改:(
因此,我不得不诉诸互操作来获取列的当前显示顺序。有一个消息 LVM_GETCOLUMNORDERARRAY
,ListView
可以理解此消息,并以 int
数组的形式为您提供当前的列顺序。
/// <summary>
/// Retrieve the order in which columns appear
/// </summary>
/// <returns>Current display order of column indices</returns>
protected int[] GetColumnOrder()
{
IntPtr lPar = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)) * Columns.Count);
IntPtr res = SendMessage(Handle, LVM_GETCOLUMNORDERARRAY,
new IntPtr(Columns.Count), lPar);
if (res.ToInt32() == 0) // Something went wrong
{
Marshal.FreeHGlobal(lPar);
return null;
}
int [] order = new int[Columns.Count];
Marshal.Copy(lPar, order, 0, Columns.Count);
Marshal.FreeHGlobal(lPar);
return order;
}
一旦我有了这个数组,我就可以简单地将所讨论单元格左侧的显示列的宽度相加。
定位嵌入的控件
这是最容易的部分。一旦我有了 ListViewSubItem
的正确位置和大小,我就只需要在 ListView
的 Paint
事件中将此信息分配给嵌入控件的 Bounds
属性。
关于排序呢?
我的第一次测试没有包含对 ListView
进行排序。我的第一次测试也只将嵌入控件的行号和列号作为放置控件的参考。
当我允许用户对 ListView
进行排序时,问题出现了。所有 ListViewItem
都改变了位置,但没有一个嵌入的控件改变。发生了什么?
当 ListView
被排序时,ListViewItem
在 Items
集合中的位置会发生变化。这没关系,但排序后,它们的 Index
属性也会更改,以反映其在集合中的当前位置,而不是它们最初被添加的位置。
幸运的是,通过在我的管理结构中添加对相关 ListViewItem
的引用,可以轻松修复此行为。现在,我也可以检索 ListViewItem
的正确显示位置了。
使用新的 ListView
要在新的、扩展的 ListView
中嵌入给定控件,您有两个新方法
public void AddEmbeddedControl(Control c, int col, int row);
public void AddEmbeddedControl(Control c, int col, int row, DockStyle dock);
第二个函数允许您指定控件在其目标单元格中的定位和大小。通常,您会使用 DockStyle.Fill
来让控件使用整个 SubItem 矩形(如果您不提供停靠参数,则为默认值)。如果您不希望控件在两个方向上都被调整大小,可以指定其他 DockStyle
。如果指定 DockStyle.None
,则您的控件将根本不会被调整大小,因此可能会与其他 ListView
部分重叠。
还有用于删除给定控件或查询 ListView
在特定位置嵌入的控件的方法。
关于演示
我添加了一个小演示项目,以便您可以试用新的 ListView
及其功能。
在构建演示时,我尝试将 RichTextBox
嵌入到 ListView
中,效果很好,但令我恼火的是,我无法阻止 RichTextBox
被选中,因此我还向演示中包含了一个 ReadOnlyRichTextBox
类。
我不认为这 qualifies 为一篇独立的文章,但您也可以在您的项目中使用这个 ReadOnlyRichTextBox
,当您想要一个带有精美格式的标签时。
请随时评论这篇文章,不要忘记投票!
发布历史
- 31.12.2004 V1.0
初始发布。