Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DataGridView virtual mode with a simple list as a source

Previously I asked a question about my dataGridView's performance due to it havign to display a large amount of rows that get added based on an incoming stream. Multiple solutions were given, one of them enabling virtual mode. MSDN has an article on the subject but it feels more complicated than what I need as it uses a database and an editable field. My DataGridView is only for displaying and the data I display is placed in a List.

After I accepted an answer I received this link: http://www.codeproject.com/Articles/23937/Paging-Data-with-DataGridView-in-VirtualMode . Even though that uses a database example, it's better suited for what I need. My List that will contain the data I want to display is declared as follows:

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

A ResultRow object is defined as follows:

/* 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. */

}

Following the article mentioned above, i created a ResultRowCache. The object is made as follows:

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

On my form Load event I do the following (related to this problem. I also added an event handler although that's done using the IDE so not directly showing in this code. Definition below!)):

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;

One thing I am wondering is how the RowCount is initialized here. It's probably 0 (due to the constructor call of ResultRowCache (see below)) but it never seems to get changed again. Does this assignment count as a reference? How does itupdate itself?

Anyway, onward with what I have, the ResultRowCache is defined as follows:

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;
    }
    }
}

Lastly, my CellValueNeeded event for the datagrid is defined as follows:

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 */
    }
}

The problem: My datagrid remains empty even though the "captureResults" list gets filled. Here's what I tried so far:

  • Update the RowCount member of the datagrid after the switch in the event.
  • Declare the list with the total amount of results within the cache to ensure it's always up to date. (I was afraid "outside modifications" wouldn't go through to the List i passed in the cached constructor even though it was a reference. (Fairly new with C#))
  • Set the RowCount of the datagrid to 100 (hard value) in the form's load event.
  • Added an "Update()" call to the datagrid after adding something to the captureResults list. (this happens from a special thread which Invoke's a function that adds something to the list)

None of the above changed anything. The grid remains empty. I think I am missing something quite obvious here. Any suggestions?

-edit- Added something I tried to make it work.

like image 447
Arnold4107176 Avatar asked May 09 '12 09:05

Arnold4107176


1 Answers

I feel that the use of Cache is complicating the process somewhat (although I do feel responsible after sending you the link to the msdn one which implements in this fashion).

What I would recommend as a starting point is:

  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..
           }
        }
    

Note : you will need to expose some extra public properties in DataObject so that they can be set as Values in the method.

See how you get on with that. If you set some breakpoints within the CellValueNeeded method that should help with debugging any further unexpected behavior. Good luck.

like image 94
Patrick McCurley Avatar answered Sep 29 '22 12:09

Patrick McCurley