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

在 ListView 中嵌入控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (104投票s)

2004 年 12 月 31 日

CPOL

4分钟阅读

viewsIcon

601785

downloadIcon

19312

如何使用 ListView 单元格中的任意控件。

Sample Image - ListViewEmbeddedControls.jpg

引言

最近,我在各种新闻组中遇到了几个关于如何在 ListView 中嵌入控件的请求。

CP 上有几个所有者绘制的 ListView 控件,但我想尝试按照我的意愿来调整标准的 ListView... ;)

方法

当您打算在 ListView 中嵌入控件时,您必须确保它始终位于正确的位置。这在几种方式上可能会很困难

  • ListViewSubItem 的位置和大小可以通过多种方式进行修改(例如,调整 ListView 的大小、滚动、调整 ColumnHeader 的大小等)。
  • ListView 的默认实现没有任何方法可以告知您 ListViewSubItem 的大小和位置。
  • 列可以重新排序。
  • ListViewItem 可以排序。

确保正确位置的最简单方法是就在绘制发生的地方,所以我决定重写 ListViewWndProc 并侦听 WM_PAINT 作为计算控件位置的触发器。

可能还有其他更有效的方法,但那样就很难处理所有需要重新定位控件的情况了。此外,我并没有发现大量嵌入控件时存在性能问题。

获取单元格的位置和大小

这有点棘手,因为标准的 ListView 在这方面不会帮助您。它确实有一个 GetItemRect() 方法,但它只为您提供整个 ListViewItem 的信息。无法在此处检索特定 ListViewSubItem 的边界。

幸运的是,我在我之前的文章(ListView 子项的就地编辑)中也遇到了相同的问题,因此所需的函数已经可用。

基本上,我从 GetItemRect() 获取单元格的高度和垂直位置,并根据当前的 ColumnHeader 计算其水平位置和宽度。

要计算单元格的左边距,您只需将您单元格左侧(即索引小于您 ListViewSubItem 索引)的所有 ColumnHeader 的宽度相加,对吧?不幸的是,不是。用户可以重新排序列,而 ListViewColumns 集合不会反映这些更改:(

因此,我不得不诉诸互操作来获取列的当前显示顺序。有一个消息 LVM_GETCOLUMNORDERARRAYListView 可以理解此消息,并以 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 的正确位置和大小,我就只需要在 ListViewPaint 事件中将此信息分配给嵌入控件的 Bounds 属性。

关于排序呢?

我的第一次测试没有包含对 ListView 进行排序。我的第一次测试也只将嵌入控件的行号和列号作为放置控件的参考。

当我允许用户对 ListView 进行排序时,问题出现了。所有 ListViewItem 都改变了位置,但没有一个嵌入的控件改变。发生了什么?

ListView 被排序时,ListViewItemItems 集合中的位置会发生变化。这没关系,但排序后,它们的 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

    初始发布。

© . All rights reserved.