使用简单列表作为源的DataGridView虚拟模式

7

之前我提出了一个关于dataGridView性能的问题,因为它需要显示大量基于传入流添加的行。多个解决方案被给出,其中之一是启用虚拟模式。MSDN有一篇关于这个主题的文章,但它感觉比我需要的更复杂,因为它使用了一个可编辑字段的数据库。我的DataGridView仅用于显示,并且我要显示的数据存放在List中。

在我接受了一个答案后,我收到了这个链接:http://www.codeproject.com/Articles/23937/Paging-Data-with-DataGridView-in-VirtualMode。即使那个例子使用了数据库,它更适合我所需要的。将包含我要显示数据的List声明如下:

List<ResultRow> captureResults = new List<ResultRow>();

一个ResultRow对象定义如下:
/* Simplified */
public class ResultRow
{
    private int first = 0;
    private string second = "";
    private UInt64 third = 0;
    private IPAddress fourth = null;
    /* etc */

    public ResultRow()
    {
    }

    public void Set (<the values>) //In actuallity a KeyValuePair
    {
        //field gets set here
    }

    public UInt64 Third
    {
        get { return third; }
        set { third = value; }
    }

    /* etc. */

根据上述文章,我创建了一个ResultRowCache。该对象的创建方式如下:

/* Page size set to 100. */
ResultRowCache _cache   = new ResultRowCache(PAGE_SIZE, captureResults);

在我的表单加载事件中,我执行以下操作(与此问题相关。我还添加了事件处理程序,尽管这是使用IDE完成的,因此不直接显示在此代码中。定义如下!):

dataGrid.VirtualMode = true;

_cache = new ResultRowCache(PAGE_SIZE, captureResults);

dataGrid.Columns.Add("FirstColumn"  , "First column header");
dataGrid.Columns.Add("Second Column", "Second column header");
/* Etc. Adding all columns. (Every member or ResultRow has it's own column. */

dataGrid.RowCount = (int)_cache.TotalCount;

我想知道RowCount在这里是如何初始化的。由于ResultRowCache的构造函数调用,它可能是0,但似乎从未再次更改。这个分配算是引用吗?它是如何自我更新的?

无论如何,接下来介绍一下我所拥有的,ResultRowCache定义如下:

public class ResultRowCache
{
    public int  PageSize    = 100;
    public long TotalCount;
    public List<ResultRow> CachedData = null;
    private List<ResultRow> FullData;

    int _lastRowIndex = -1;

    public ResultRowCache (int pageSize, List<ResultRow> total)
    {
        PageSize = pageSize;
        FullData = total;

        LoadPage( 0 );
    }

    public void LoadPage (int rowIndex)
    {
         int lastRowIndex = rowIndex - ( rowIndex % PageSize );

         /* Page already loaded */
         if( lastRowIndex == _lastRowIndex ) return;

         /* New page */
         _lastRowIndex = lastRowIndex;

         /* Create a new cashes data object */
         if( CachedData == null ) CachedData = new List<ResultRow>();

         /* If cached data already existed, clear */
         CachedData.Clear();

         /* The index is valid (there is data */
         if (lastRowIndex < FullData.Count)
         {
             /* Not a full page */
             if (lastRowIndex + PageSize > FullData.Count)
             {
                 CachedData = FullData.GetRange(lastRowIndex, ((lastRowIndex + PageSize) - 1) - FullData.Count);

             }
            /* Full page */
            else
            {
                CachedData = FullData.GetRange(lastRowIndex, PageSize);
            }
        }

        TotalCount = CachedData.Count;
    }
    }
}

最后,我的 datagrid 的 CellValueNeeded 事件定义如下:
void DataGridCellValueNeededEvent(object sender, DataGridViewCellValueEventArgs e)
{
    _cache.LoadPage(e.RowIndex);

    int rowIndex = e.RowIndex % _cache.PageSize;

    switch (dataGrid.Columns[e.ColumnIndex].Name)
    {
        /* Not actual names, example */
    case "FirstColumn":   e.Value = _cache.CachedData[rowIndex].First;  break;
        case "SecondColumn":  e.Value = _cache.CachedData[rowIndex].Second; break;
        /* Rest of the possibly columns/ResultRow values */
    }
}

问题:我的数据网格仍然为空,即使“captureResults”列表被填充。以下是我尝试过的内容:
  • 在事件中的开关后更新数据网格的RowCount成员。
  • 使用缓存中总结果数声明列表,以确保它始终是最新的。(我担心即使它是引用传递的,也不能通过缓存构造函数传递的List进行“外部修改”。(对C#还很陌生))
  • 在表单的加载事件中将datagrid的RowCount设置为100(硬编码)。
  • 在向“captureResults”列表添加内容之后,将“Update()”调用添加到数据网格中。(这发生在一个特殊线程中,该线程Invoke一个向列表添加内容的函数)

以上任何一项都没有改变任何东西。网格仍然为空。我觉得我在这里漏了一些非常明显的东西。有什么建议吗?

-编辑- 添加了一些我尝试让它工作的内容。

1个回答

2

我认为使用缓存有些让流程复杂化了(尽管我觉得在我向您发送实现方式类似的 MSDN 链接后,我也对此负有责任)。

我建议作为起点的是:

  1. Throw away the cache (this may be useful later if you run into memory problems, but for now lets just get your datagrid populating)

  2. Store your List<ResultsRow> in an instance variable.

  3. Ensure that dataGrid.VirtualMode = true; (or equivilant)

  4. Implement CellValueNeeded as follows:

        private void gridContacts_CellValueNeeded(object sender,  DataGridViewCellValueEventArgs e)
        {
           ResultRow dataObject = resultRows[e.RowIndex];
    
           switch(e.ColumnIndex)
           {
               case 0:
                   e.Value = dataObject.First;
                   break;
               case 1 :
                   e.Value = dataObject.Second;
                   break;
               //etc..
           }
        }
    
注意:您需要在DataObject中公开一些额外的公共属性,以便它们可以在该方法中设置为值。
看看您在其中的表现如何。如果您在CellValueNeeded方法中设置了一些断点,那应该有助于调试任何进一步的意外行为。祝你好运。

我设法让它工作(没有缓存),通过在每次添加后使用我的列表大小更新RowCount。然而,性能似乎更差。 (也许是因为我在每次添加时更新表单,而不是通过后台工作人员每<x>毫秒更新一次)。 我的应用程序在处理数据后也很快冻结。 这是虚拟模式的错误实现还是问题出在其他地方? (由于某种原因,对于这个系统来说,绘制可以具有不同背景颜色的行非常困难。我的笔记本电脑(更强大)可以更轻松地完成它。 - Arnold4107176
你为什么要在每次添加后更新RowCount呢?能否分享一下这段代码,因为我不确定它是如何工作的,而且听起来不太对。如果将List<ResultRow>分配给bindingSource,然后将其分配给Grid.DataSource,那么rowCounts和内部列表应该被充分跟踪。 - Patrick McCurley
啊,我觉得这是我对概念理解不足的结果。当我启用虚拟模式时,我没有为我的DataGridView设置数据源,因为我认为这是性能问题。如果我保持不变,唯一的区别就是我将VirtualMode属性设置为true,并定义了CellValueNeeded事件。这样做是正确的吗?我不太明白为什么这会消除你否则会遇到的性能问题。是事件的默认实现对大型数据集如此低效吗?覆盖它并关闭虚拟模式可能行不通,我需要阅读一下。 - Arnold4107176
上一篇帖子字数不够,但我想补充说明一下,我将尝试使用启用数据源并禁用行计数更新的代码。我会告诉你结果的!(不幸的是,我不能立即测试这个) - Arnold4107176
这真的取决于情况,如果你正确地实现了它,就不应该有任何性能损失。但是,如果你有一个自动属性,它反过来从数据库中填充了一些子属性的DataRow,那么就会有性能问题。这只是众多例子中的一个,如果没有看到代码,很难帮助你,你介意发表跟进吗? - Patrick McCurley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接