是否有允许用户在 Windows 窗体容器中滚动数百或数千个项目的通用设计?示例:编写一个电子邮件客户端,用户会获得平滑滚动超过 10,000 条消息“行”的印象,每封电子邮件一个 - 但这些肯定直到它们显示之前才真正呈现。类似地,滚动一个巨大的图像必须需要将它平铺成更小的部分,但是呈现它的代码是如何组织的呢?
2 回答
它是 Windows 设计方式与生俱来的,不需要 3 个字母或特殊模式。
一个 GUI 程序,不管它的风格如何,只要它需要执行一个操作,它就会从 Windows 获取消息。消息不会按照它们生成的顺序进行处理。有三个基本的“优先事项”:
- 重要消息直接投递,winapi底层函数是SendMessage()
- 用户输入首先存储在队列中,底层的winapi函数是PostMessage()
- 某些消息是从窗口状态合成的,仅在上述两种消息类型都不需要处理时才生成。
消息按上述顺序处理。发送的消息首先发送,如果没有待处理的消息,则程序开始清空消息队列。如果它为空,则发送合成消息。绘画属于第三类。一个程序只有在不需要做任何其他事情时才会收到 WM_PAINT 消息。
因此,基本的事件链是控件从消息队列中检索鼠标消息并检测到它是用于滚动条的。它计算滚动条拇指的新位置并调用 InvalidateRect() winapi 函数来指示需要绘制窗口。与 Winforms 中的 Invalidate() 方法相同的功能。更新内部窗口状态以标记需要重新绘制该矩形。这一切都非常快,没有实际的绘画发生。
当程序检索下一条消息时,现在会发生两件基本的事情。当用户不断滚动时,它可能又是一条鼠标消息。处理方式与上面完全相同,除了更改拇指位置之外,窗口没有任何变化。
或者没有新消息要处理,用户停止滚动,现在类别#3合成消息轮到了。Windows 注意到需要重新绘制窗口并传递 WM_PAINT 消息。
除此之外还有一些实现细节,更高版本的 Windows 默认启用“拖动时显示窗口内容”系统选项。这使得用户在拖动拇指时更容易看到他在做什么,它有意生成额外的窗口绘制。总而言之,操作系统和您的程序都很好地支持在 ListView 中包含数以万计的项目。当然不是用户。
如前所述, VirtualMode可能是最好的解决方案,CodeProject 上有一个使用示例。
在创建表单时初始化虚拟模式:
private void Form1_Load(object sender, EventArgs e)
{
listView1.VirtualMode = true; // switching virtual mode on
listView1.VirtualListSize = 1000000000; // give it 1 million lines
}
然后分配和处理RetrieveVirtualItem
事件:
private void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
ListViewItem lvi = new ListViewItem(); // create a listviewitem object
lvi.Text = nt.MakeText(e.ItemIndex); // assign the text to the item
ListViewItem.ListViewSubItem lvsi = new ListViewItem.ListViewSubItem(); // subitem
NumberFormatInfo nfi = new CultureInfo("de-DE").NumberFormat;
nfi.NumberDecimalDigits = 0;
lvsi.Text = e.ItemIndex.ToString("n", nfi); // the subitem text
lvi.SubItems.Add(lvsi); // assign subitem to item
e.Item = lvi; // assign item to event argument's item-property
}