【问题标题】:Does the Windows Forms DataGridView implement a true virtual mode?Windows 窗体 DataGridView 是否实现了真正的虚拟模式?
【发布时间】:2011-01-02 12:16:25
【问题描述】:

我有一个 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的单元格格式化能力,所以不能用。

对于可能能够做到的其他人,我使用四列 ListView(在详细模式下)、VirtualMode= True 和 VirtualListSize =100,000,000 行对其进行了测试。

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

【问题讨论】:

    标签: c# winforms datagridview


    【解决方案1】:

    我们只是有一个类似的要求,即能够在我们的应用程序中以“非常好的”性能显示任意、未索引的 1M+ 行表,使用股票 DataGridView。起初我认为这是不可能的,但经过足够的努力,我们花了几天时间倾注于 Reflector 和 .NET Profiler 之后,想出了一个效果很好的东西。 这很难做到,但结果非常值得。

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

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

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

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

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

    进一步阅读:

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

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

    【讨论】:

    • 我尝试复制您描述的实现,但遇到了几个问题。 1)当我将 grid.DataSource 设置为我的绑定列表时,它会立即询问计数和所有实体 2)当数据仍在获取时,我无法管理对索引器的调用 3)当排序顺序更改而数据仍在获取时使缓存无效返回过时的结果
    • 如果您尚未完成后台提取,请尝试返回计数 0。如果你想把你的一些代码发给我,我很乐意看看。
    【解决方案2】:
    【解决方案3】:

    答案是

    see first comment here

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

    【讨论】:

    • 我认为链接指向最近的版本,并且评论在那里不可见。是否是开头“此示例似乎无法使用 .NET 4.0”的评论?
    • 我不知道了,但我猜是“.NET Framework 3.0”,不过“Visual Studio 2010”也很有趣
    【解决方案4】:

    我会说是的...只要您坚持由虚拟模式行为(例如 CellValueNeeded)触发的事件,并且您注意清除您的手动内置缓冲区。我已经展示了大量的数据了,1M以上不费吹灰之力。

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

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

    我假设没有人见过带有负尺寸的控件或图片,但类型仍然不适合使用上下文。

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

    【讨论】:

      猜你喜欢
      • 2012-10-14
      • 2011-06-11
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多