Windows窗体DataGridView是否实现了真正的虚拟模式?

我有一个SQL表,其中包含当前将随时间增长的100万行。

特定用户要求提供可排序网格,该网格显示所有行而不进行分页。 用户希望能够通过使用滚动条快速地从一行跳到另一行以及从上到下跳转。

我熟悉“虚拟模式”网格,它只显示整个数据的可见子集。 它们可以提供出色的UI性能和最小的内存要求(我甚至多年前使用这种技术实现了一个应用程序)。

Windows窗体DataGridView提供了一个看起来应该是答案的虚拟模式。 然而,与我遇到的其他虚拟模式不同,它仍然为每一行分配内存(在ProcessExplorer中确认)。 显然,这会导致整体内存使用量不必要地大大增加,并且在分配这些行时,会有明显的延迟。 滚动性能也会受到100万+行的影响。

真正的虚拟模式不需要为未显示的行分配任何内存。 您只需给它总行数(例如1,000,000),所有网格都会相应地缩放滚动条。 当它首次显示时,网格只询问数据前n个(比如30个)可见行,即时显示。

当用户滚动网格时,提供简单的行偏移和可见行的数量,并且可用于从数据存储中检索数据。

这是我正在使用的DataGridView代码的示例:

public void AddVirtualRows(int rowCount) { dataGridList.ColumnCount = 4; dataGridList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; dataGridList.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; dataGridList.VirtualMode = true; dataGridList.RowCount = rowCount; dataGridList.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridList_CellValueNeeded); } void dataGridList_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) { e.Value = e.RowIndex; } 

我在这里遗漏了什么,或者DataGridView的“虚拟”模式根本不是虚拟的?

[更新]

看起来好的旧ListView实现了我正在寻找的那种虚拟模式。 但遗憾的是ListView没有DataGridView的单元格格式化function,所以我无法使用它。

对于其他可能的人,我使用四列ListView(详细模式),VirtualMode = True和VirtualListSize = 100,000,000行测试它。

该列表立即显示,前30行可见。 然后我可以毫不拖延地快速滚动到列表的底部。 内存使用量始终为10 MB。

我们只是有类似的要求,能够使用库存DataGridView在我们的应用程序中以“非常好”的性能显示任意的,未编制索引的1M +行表。 起初我认为这是不可能的,但是有足够的头部刮擦,我们想出了一些在花费数天时间倾注于Reflector和.NET Profiler之后运行良好的东西。 这很难做到,但结果非常值得。

我们解决这个问题的方法是创建一个实现ITypedListIBindingList的类(例如,您可以将其LargeTableView )来管理从数据库中异步检索和缓存信息。 我们还创建了一个PropertyDescriptorinheritance类(例如LargeTableColumnDescriptor )来从每列中检索数据。

DataGridView.DataSource属性设置为实现IBindingList的类时,它进入与常规VirtualMode不同的伪虚拟模式 ,其中当绘制每一行时(例如当用户滚动时),DataGridView访问索引器[ ]在IBindingList和每个列的PropertyDescriptor上的相应GetValue方法上,根据需要检索值。 不会引发CellValueNeeded事件。 在我们的例子中,我们在访问索引器时访问数据库,然后缓存该值,以便后续的重新绘制不会访问数据库。

我执行了类似的测试:内存使用情况。 DataGridView确实分配了一个列表大小的数组(即1M行),但是数组中的每个项最初都引用一个DataGridViewRow,因此内存使用是可以接受的。 当VirtualMode为true时,我不确定行为是否相同。 如果没有缓存行, 我们可以通过在GetValue方法中立即返回String.Empty 来消除滚动延迟 ,然后异步执行数据库查询。 当异步请求完成时,您可以引发IBindingList.ListChanged事件以向DataGridView发出信号,告知它应该重新绘制单元格,除非这次从缓存中读取现有的内容。 这样,UI永远不会被阻塞,等待数据库调用。

我们注意到的一件事是,如果将DataGridView添加到表单之前设置DataSource或虚拟行数,性能会明显提高 – 它会将初始化时间缩短一半。 此外,请确保将“行”和“列”自动调整设置为“ None ,否则您将遇到其他性能问题。

旁注:我们在.NET应用程序中完成“加载”这样一个大表的方式是在SQL服务器上创建一个临时表,该表以所需的排序顺序列出主键以及IDENTITY(行号),然后持久保存后续行请求的连接。 这自然需要时间来初始化(在相当快的SQL服务器上大约3-5s),但是在不知道可用索引的情况下,我们没有更好的选择。 然后,在我们的ITypedList实现中,我们请求100行页面中的行,其中第50行是正在绘制的行,因此我们限制每次访问索引器时执行的查询数量,并且我们给出了外观在我们的应用程序中提供所有数据。

进一步阅读:

http://msdn.microsoft.com/en-us/library/ms404298.aspx

http://msdn.microsoft.com/en-us/library/system.componentmodel.ibindinglist.aspx

看看这些文章(如果你还没有):

如何:在Windows窗体DataGridView控件中实现虚拟模式

在VirtualMode中使用DataGridView分页数据

答案是否定的

在这里看到第一条评论

如果有人知道更好的方式,请告诉我们

我会说是的…只要你坚持虚拟模式行为触发的事件(例如CellValueNeeded),并且你要好好清理你的手工构建缓冲区。 我已经显示了大量数据,超过1M而没有大惊小怪。

我对使用基于ITypedList的DataSource或任何IList相关接口实现的Kevin McCormick的实现有点好奇。 我想这只是另一个抽象层使用内部透明缓冲区,除了让用户或开发人员使用它来驱动DataGridView,但仍在内部处理本机VirtualMode以显示您已加载的信息缓冲。

除了解决虚拟模式的时尚方式之外,对我而言,DataGridView唯一存在的难题是RowCount限制:它仍然是Int32.Max。 这可能是由于Winforms绘图系统的遗留…就像图像甚至每个尺寸成员,宽度,Winform控件的高度……为什么不坚持UInt32类型?

我假设没有人看过控件或具有负尺寸的图片,但是这种类型仍然不适合使用环境。

请看下面的答案,如果您仍然遇到麻烦,可以帮助您,如果,我想它现在已经解决了很多年。 https://stackoverflow.com/a/16373108/1906567