DataGridView paging using virtual mode

You can download the code for this blog post from here

Paging is a great way to limit the amount of data displayed to the user at one time, but is also a very good way of stopping lots of data being transmitted across the network, being held in memory or big queries on databases...  It solves many problems. 

The most common solution is (just like on a Google search) is you get shown a list of pages and you can navigate around pages either going up / down a page at a time or clicking on a page number. 

Another way is to make it seem like there is actually one big list but page behind the scenes so the user feels like they are actually viewing one big list.  An example where you see this done is in TFS.  You can do this with a windows forms data grid (DataGridView).

DataGridView has two main modes.  The first is the most common, you data bind your grid to a data source and all the data in that source is loaded into the grid.  This is very easy to do, but with big lists of data can get quite slow!  The other is virtual mode, where you are notified when data is required for a row and you populate it.  That works much better for big lists as the grid only loads what is currently on the screen and when the cells are no longer visible they are destroyed...

The paging solution uses the grid in virtual mode:

   1: gridPager = new PagedVirtualDataGridController<TestData>(
   2: new PagedVirtualDataGridView<TestData>(dataGridView1, gridColumnMapper), 100, (pageNumber, pageSize) =>
   3:  {
   4:     // delegate to fetch the page...
   5:  });

Bit of background reading on the DataGridView and the VirtualMode property can be found in this walk through.

When working in virtual mode it becomes easier to page as the grid will only request what it needs.  However, it does mean you lose data binding.  Setting up the columns is easy, just add them through the designer.  But, when the CellValueNeeded event is raised we need to know what information is required for that cell.  This is where the GridColumnMapper comes in, it enables you to specify what value should be returned for each column (in the sample above you can see that gridColumnMapper is being passed into PagedVirtualDataGridView):

   1: gridColumnMapper = new GridColumnMapper<TestData>();
   2: gridColumnMapper.AddMapping(0, t => t.Name);
   3: gridColumnMapper.AddMapping(1, t => t.Value);

The paged grid is implemented using the MVC (Model View Controller) pattern.  The view (IPagedVirtualGridView) is an abstraction of the DataGridView - it exposes only what is needed to enable paging.  The PagedVirtualDataGridView (which implements IPagedVirtualGridView) accepts a DataGridView and GridColumnMapper in its constructor.  The view listens to its Model (an IPagedDataModel) and handles the PageFetchCompleted event.  When this event is fired it causes the DataGridView to request data for the cells which are contained on the page which was just fetched (using UpdateCellValue).

The Model (PagedDataModel : I PagedDataModel) contains all the pages which have been fetched and is responsible for asynchronously fetching the page.  It uses a FetchPageDelegate to actually go and fetch the page.  If the page has already been fetched then that data is returned synchronously (all fetched pages are stored in a dictionary).  If the page has not been fetched then it queue a thread pool work item to fetch the page.  There is some synchronisation code to ensure only one page is fetched at a time and the same page is not fetched more than once.  When the page is fetched it raises the PageFetchCompleted thus notifying the view to refresh its cells.

The controller is attaches to the IPagedVirtualGridView CellValueNeeded event and asks the model for the object which relates to that cell.  If there is data available then it sets the DataGridViewCellValueEventArgs Value property using the views column mapper.

At the moment this implementation is read only, but it wouldn't be difficult to use the CellValuePushed event to ensure data is written back to the Model.

I've uploaded a solution where you can download the code plus a simple example.  The usage on the sample form is below.  The example delegate sleeps (simulating it going back to the server to get some data).

   1: gridColumnMapper = new GridColumnMapper<TestData>();
   2: gridColumnMapper.AddMapping(0, t => t.Name);
   3: gridColumnMapper.AddMapping(1, t => t.Value);
   4:  
   5: var data = new List<TestData>();
   6:  
   7: for (var i = 0; i < 100000; i++)
   8: {
   9:    data.Add(new TestData { Name = string.Format("Number {0}", i), Value = i.ToString() });
  10: }
  11:  
  12: dataGridView1.RowCount = data.Count;
  13:  
  14: gridPager = new PagedVirtualDataGridController<TestData>(
  15:    new PagedVirtualDataGridView<TestData>(dataGridView1, gridColumnMapper), 100, (n, s) =>
  16:     {
  17:        Thread.Sleep(500);
  18:        if (n * s > data.Count)
  19:           return null;
  20:        var items = new TestData[s];
  21:        for (int y = n * s, p = 0; y < (n + 1) * s && y < data.Count; y++, p++)
  22:        {
  23:           items[p] = data[y];
  24:        }
  25:        return items;
  26:     });
One Comment Filed Under [ Windows Forms ]

Comments

# re: DataGridView paging using virtual mode
Gravatar How to use the GridColumnMapper with SqlDataReader or DataTable object?

Thanks!
Left by codeviiv on 11/18/2009 9:42 AM

Leave Your Comment

Title*
Name*
Email (never displayed)
 (will show your gravatar)
Url
Comment*

Please add 4 and 4 and type the answer here:

Preview Your Comment.