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

在 .NET 中实现无闪烁 ListView - 第 2 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (16投票s)

2003年2月4日

6分钟阅读

viewsIcon

246895

downloadIcon

2448

本文讨论了两种减少 .NET ListView 闪烁的方法。

引言

这是另一篇关于减少 ListView 闪烁的文章。我最初写的文章只适用于 Windows XP(需要清单文件来使用 Common Controls 版本 6)。您可以通过点击此链接查看:ListViewXP

本文将重点介绍另外两种技术。这两种技术都不需要 Windows XP。请注意,本文的重点是在不闪烁的情况下,使用尺寸合适的图像列表动态添加多个项目。

WndProc 技术

第一种技术涉及捕获和操作消息。正如我在上一篇文章中提到的,每次向 ListView 添加项目时,整个控件都会失效。在循环中添加多个项目时,除非您在循环结束时添加 Update()Refresh() (或其它各种方法),否则控件只会在循环结束时绘制。使用 Update()Refresh() 等会导致闪烁,因为控件会不断失效并重新绘制自身。观察这种情况的一种方法是重写 WndProc 方法,并将所有消息存储在 ArrayList 或其他结构中。然后当您查看 ArrayList 时,您会看到与 WM_ERASEBKGNDWM_PAINT 对应的数字。我们可以利用这些知识来帮助我们。

我们这个新类设计的一部分是允许程序员指定他/她正在添加新项目。我们创建了一个名为 UpdateItem(int iIndex) 的新方法。程序员可以调用此方法,它将只绘制新创建的项目。

public void UpdateItem(int iIndex)
{
    updating = true;
    itemnumber = iIndex;
    this.Update();
    updating = false;
}

首先,我们有一个名为 bool 的私有成员变量 updating。这将在下面的 WndProc 方法中使用。当它设置为 true 时,WndProc 方法将捕获消息。当 false 时,WndProc 除了调用 base.WndProc() 外,不执行任何操作。itemnumber 是另一个新添加的私有成员。它保存新添加项目的索引。Update() 用于重新绘制控件中失效的区域(我们在 WndProc 中操作的),最后它将 updating 设置为 false,以便我们的代码不再捕获消息。现在我们重写 WndProc 以进行调整。

protected override void WndProc(ref Message messg)
{
    if (updating)  
    {
        // We do not want to erase the background, 
        // turn this message into a null-message
        if ((int)WM.WM_ERASEBKGND == messg.Msg)
            messg.Msg = (int) WM.WM_NULL;
        else if ((int)WM.WM_PAINT == messg.Msg)
        {
            RECT vrect = this.GetWindowRECT();
            // validate the entire window                
            ValidateRect(this.Handle, ref vrect);

            //Invalidate only the new item
            Invalidate(this.Items[itemnumber].Bounds);
        }
    }
    base.WndProc(ref messg);
}

如您所见,我们捕获了 WM_ERASEBKGND 消息,并将其转换为 NULL 消息。这将阻止 ListView 被擦除。WM_PAINT 用于重新绘制失效区域。由于整个控件都失效了,我们需要做一些工作。首先,我们使整个可见区域 有效。这样做会导致 WM_PAINT 不执行任何操作,因此我们紧接着只使新项目的边界失效。现在,当发生绘制时,它将只针对新项目发生,因为控件的其余部分已经有效。

WM_ERASEBKGNDWM_PAINT 是枚举类型,表示窗口消息的 int 值。您可以在代码中查看这些内容。ValidateRect 是一个 Win32 函数,允许我们验证某个区域。由于 .NET 中没有等效项,我们从 user32.dll 导入此函数(请参阅代码)。RECT 是一个类似于 Rectangle 的结构,但 ValidateRect 函数需要它。当我们调用 ValidateRect 时,它会验证 ListView 的整个窗口区域。然后我们使用控件的 Invalidate 方法使新项目的边界失效。现在只有新项目的边界失效。这意味着只会绘制新项目。由于我们捕获了 WM_ERASEBKGND 并验证了控件的其余部分,因此每个项目在添加时都会被绘制(前提是您使用了新添加的 UpdateItem(index) 函数)。如果您不使用 UpdateItem()WndProc 将不执行任何操作,因为“updating”变量将始终为 false。

注意 - 我想感谢 Carlos H Perez。他在他的 ListViewEx 控件中做了类似的事情,这个想法的一部分是基于那项工作。

现在我们可以在主应用程序中这样做

for (int i=0; i < 500; i++)
{
   ListViewItem lvi = 
      new ListViewItem("Item #" + i.ToString(),  indexOfImage);
   listViewFF.Items.Add(lvi);
   listViewFF.UpdateItem(i);
}

现在,项目将在添加到列表视图时被绘制,而不会闪烁。

纯 .NET 技术

下一项技术是纯 .NET,您不需要扩展 ListView 类。这项技术不会在项目添加时绘制它们,但描述了一种替代方法来防止用户长时间等待。

如果我们不将 ImageIndex 与 ListView 项目关联(将 ImageIndex 用作 -1),我们可以非常快速地将项目添加到 ListView

for (int i=0; i < 500; i++)
{
   ListViewItem lvi = 
      new ListViewItem("Item #" + i.ToString(), -1);
   this.listView.Items.Add(lvi);
}

由于我们没有调用 Update()Refresh(),我们不会在项目添加时看到它们,但这会很快完成,因为没有图像与 ListView 关联。

现在所有项目都已添加,因此我们需要将图像与它们关联。

for (int i=0; i < 500; i++)
{
     ListViewItem lvi = this.listView.Items[i] ; 
        // get the already existing item

     lvi.ImageIndex = someNumber ; 
        // Now we associate the item with an image in the imagelist;

     this.listView.Invalidate(lvi.Bounds); 
        // Invalidate the already-existing item
     
    this.Update();  
        // Will force the invalidated region to be redrawn
}

在循环开始之前,所有项目都将存在,没有图像。现在我们只需关联图像,并使项目的区域失效。调用 Update() 将只重新绘制失效区域。在这种情况下,只有项目的边界将失效,因此图像将绘制到屏幕上,而不会影响其他 ListView 项目。

这对于缩略图程序很有用。您可以在第一个循环中添加缩略图的名称,在第二个循环中,您可以将位图加载到图像列表中,将已创建的项目与新添加图像的图像索引关联,现在每个图像都将在加载时被绘制。更好的是,您可以将这个第二个循环放入一个线程中。这将允许用户在绘制图像时滚动列表视图。

技术的优缺点

您应该根据需要使用每种技术。WndProc 技术允许您在项目添加时看到它们。如果您添加数千个项目,整体可能会更慢,但至少用户可以看到正在发生的事情。如果第一个循环中尝试添加(比如说)5000个项目,纯 .NET 技术将导致列表视图空白几秒钟,但如果第二个循环在线程中,则在绘制图像时不会阻止用户做其他事情。

关于 WndProc 技术有一点需要注意。由于某种原因,如果您使用清单文件来使用新的 XP 主题,它不能很好地工作。我不确定为什么,但有些项目在绘制其他项目时被擦除了。我确实找到了一个解决方案(它用于源代码中)。在循环开始之前,将列表视图的 AutoArrange = false。当循环结束时,您可以将其设置回 true。当然,如果您只使用 Windows XP,您可以使用我在上一篇文章中描述的双缓冲技术(请参阅介绍中的链接)。如果您不打算支持 XP 主题,则不需要更改 AutoArrange 属性。

关于编译源代码的说明

您需要 MS Visual Studio .NET 来查看此项目。解压所有文件后,将有两个目录:ListViewXTestListViewFF。打开 ListViewX 文件夹中的解决方案文件,加载后,将 TestListViewFF 项目设置为启动项目。然后编译应该没有问题。

© . All rights reserved.