Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Load a billion rows in a DataGridView

4 Aug 2009CPOL2 min read 65.5K   3.2K  
This article presents how to load rows virtually in a DataGridView without using its RowCount (memory hog).

Introduction

This article presents how to load rows in a DataGridView without using its RowCount.

DataGridView's VirtualMode mechanism doesn't deliver what it promises. Whenever you set the RowCount, the DataGridView will create row instances based on it; row object instantiation time can be aggravating when you set it to hundred thousands or millions.

What prompted me to formulate this code is I created my own lookup box, and I sort of wanted to future-proof it, i.e., the program should scale even if you have tens or hundred thousands of rows to look up. On initial code, I used DataGridView's VirtualMode, but there's a problem when the data source is of hundred thousand rows, the same problem shared by another coder from this article.

Using the Code

For clearer delineation between the UI and data access, I put them on separate regions. The query result is assigned on a module-wide visible _ds (DataSet) object.

C#
_ds = Material_LookupList(Filter, 0);

To show the result's columns:

C#
SetGridDisplay();

To allocate screen-visible rows:

C#
AdjustRowCount();

Finally, to display the result's values:

C#
DisplayValues();

The central event in our program scrolling mechanism (vsb is an instance of VScrollBar):

C#
private void vsb_ValueChanged(object sender, EventArgs e)
{
    DisplayValues();
}

This is how we setup the DataGrid's columns:

C#
void SetGridDisplay()
{
    var ftd = new List<string>();

    grd.Columns.Clear();    
    if (this.FieldsToDisplay != null && this.FieldsToDisplay.Length > 0)
    {
        ftd.AddRange(this.FieldsToDisplay);
    }
    else
    {
        foreach (DataColumn c in _ds.Tables["_lookup_result"].Columns)
        {
            if (c.ColumnName == "_lookup_row_number") continue;
            ftd.Add(c.ColumnName);
        }
    }

    foreach (var s in ftd)
    {
        if (s == this.ValueMember && !ShowValueMember) continue;


        string columnName = "ux" + s.Replace(" ", "");


        foreach (DataGridViewColumn dgc in grd.Columns)
        {
            if (dgc.DataPropertyName == s)
                goto goAlreadyAdded;
        }

        grd.Columns.Add(columnName, HeaderizeColumn(s));
        var c = grd.Columns[columnName];
        c.DataPropertyName = s;
        c.Visible = true;

        goAlreadyAdded: ;
    }
}

The following code is the data scrolling mechanism (keyboard, mouse, scrollbar) of the program. The key principle is to just depend on one central logic. In our code, we just use the vertical scroll bar's Value. Accordingly, the keyboard and mouse wheel also requests the scrolling mechanism for VScrollbar's Value.

C#
void DisplayValues()
{
    for (int r = 0; r < grd.Rows.Count; ++r)
    {
        int absRow = r + vsb.Value;
        DataRow[] drx = _ds.Tables["_lookup_result"].Select(
            string.Format("_lookup_row_number = {0}", absRow));
        
        if (drx.Length == 0)
        {
            _ds = Material_LookupList(Filter, absRow);
            drx = _ds.Tables["_lookup_result"].Select(
                string.Format("_lookup_row_number = {0}", absRow));
        }

        if (drx.Length == 1)
        {
            foreach (DataGridViewColumn c in grd.Columns)
            {
                grd.Rows[r].Cells[c.Name].Value = drx[0][c.DataPropertyName];
            }
        }
        else
        {
            foreach (DataGridViewColumn c in grd.Columns)
            {
                grd.Rows[r].Cells[c.Name].Value = null;
            }
        }

    }
}

void grd_KeyDown(object sender, KeyEventArgs e)
{
    if (grd.CurrentCell == null) return;

    if (e.KeyCode == Keys.Down)
    {
        if (grd.CurrentCell.RowIndex == grd.Rows.Count - 1)
        {
            if (vsb.Value <= vsb.Maximum - vsb.LargeChange)
            {
                e.Handled = true;
                ++vsb.Value;
            }
        }

    }
    else if (e.KeyCode == Keys.Up)
    {
        if (grd.CurrentCell.RowIndex == 0)
        {
            if (vsb.Value > 0)
            {
                e.Handled = true;
                --vsb.Value;
            }
        }

    }
    else if (e.KeyCode == Keys.PageDown)
    {
        if (grd.CurrentCell.RowIndex == grd.Rows.Count - 1)
        {
            if (!(vsb.Value + vsb.LargeChange > vsb.Maximum))
            {
                int newValue = vsb.Value + vsb.LargeChange;

                if (vsb.Maximum - newValue > vsb.LargeChange)
                {
                    vsb.Value = newValue;
                }
                else
                {
                    vsb.Value = vsb.Maximum - vsb.LargeChange + 1;
                }
            }
        }
    }
    else if (e.KeyCode == Keys.PageUp)
    {
        if (grd.CurrentCell.RowIndex == 0)
        {
            if (!(vsb.Value - vsb.LargeChange < 0))
            {
                vsb.Value -= vsb.LargeChange;
            }
            else
            {
                vsb.Value = 0;
            }
        }
    }
}

void grd_MouseWheel(object sender, MouseEventArgs e)
{
    if (e.Delta < 0)
    {
        long rowCount = (long)_ds.Tables["_lookup_count"].Rows[0]["_count"];

        if (vsb.Value < rowCount - vsb.LargeChange)
            ++vsb.Value;
    }
    else if (e.Delta > 0)
    {
        if (vsb.Value > 0)
            --vsb.Value;
    }
}

Points of Interest

Interesting/fun/annoying things while writing the code? Off-by-one error :-) Especially on Keys.Down and Keys.PageDown. This prompted me to scale back the code a little; the more optimize the code, but riddled with errors is available in another class (loftyGoalForm1). If there's a use case that the scaled back code can't handle, I will go back to the drawing board and re-visit loftyGoalForm1's logic.

Known Limitations

Two billion rows, because .NET's VScrollbar can only support 32 bit numbers. If I find a 64 bit vertical scrollbar, I will integrate that in my MycFramework's lookup control.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)