Click here to Skip to main content
Licence CPOL
First Posted 4 Aug 2009
Views 14,267
Downloads 634
Bookmarked 16 times

Load a billion rows in a DataGridView

This article presents how to load rows virtually in a DataGridView without using its RowCount (memory hog).
 
Part of The SQL Zone sponsored by
See Also

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.

_ds = Material_LookupList(Filter, 0);

To show the result's columns:

SetGridDisplay();

To allocate screen-visible rows:

AdjustRowCount();

Finally, to display the result's values:

DisplayValues();

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

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

This is how we setup the DataGrid's columns:

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.

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)

About the Author

anagram of iEnableMuch = Michael Buen



Philippines Philippines

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 2 PinmemberFlorian Reischl4:47 18 Jul '10  
GeneralThe GOTO part can be written as... Pinmembermusacj21:54 12 Aug '09  
GeneralMy vote of 1 PinmemberSavara15:36 4 Aug '09  
GeneralRe: My vote of 1 Pinmemberanagram of iEnableMuch = Michael Buen16:15 4 Aug '09  
GeneralMy vote of 1 PinmemberFrank Meffert14:12 4 Aug '09  
QuestionGOTO? PinmemberRodentor Sano13:54 4 Aug '09  
GeneralWow a goto Pinmemberspoodygoon13:17 4 Aug '09  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120517.1 | Last Updated 4 Aug 2009
Article Copyright 2009 by anagram of iEnableMuch = Michael Buen
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid