在DataGridView中加载十亿行






2.25/5 (8投票s)
本文介绍如何在不使用其 RowCount(占用大量内存)的情况下,以虚拟方式在 DataGridView 中加载行。
引言
本文介绍如何在 DataGridView
中加载行,而无需使用其 RowCount
。
DataGridView
的 VirtualMode 机制并没有达到它承诺的效果。每当你设置 RowCount
时,DataGridView
将基于它创建行实例;当将其设置为十万或百万时,行对象实例化时间可能会令人沮丧。
促使我编写这段代码的原因是我创建了自己的查找框,并且我希望使其具有前瞻性,即,即使有成千上万或数十万行数据进行查找,程序也应该能够扩展。在初始代码中,我使用了 DataGridView
的 VirtualMode,但当数据源为十万行时,存在一个问题,这个问题与这篇文章中的另一位程序员共享:http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/thread/243a81e7-909b-4c8e-9d28-6114248cf66e。
Using the Code
为了更清晰地划分 UI 和数据访问,我将它们放在单独的区域中。查询结果被分配给模块范围内可见的 _ds
(DataSet
)对象。
_ds = Material_LookupList(Filter, 0);
显示结果的列
SetGridDisplay();
分配屏幕可见的行
AdjustRowCount();
最后,显示结果的值
DisplayValues();
我们程序滚动机制中的核心事件(vsb
是 VScrollBar
的一个实例)
private void vsb_ValueChanged(object sender, EventArgs e)
{
DisplayValues();
}
这是我们设置 DataGrid
列的方式
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: ;
}
}
以下代码是程序的滚动机制(键盘、鼠标、滚动条)。关键原则是只依赖于一个核心逻辑。在我们的代码中,我们只使用垂直滚动条的 Value
。相应地,键盘和鼠标滚轮也会请求滚动机制获取 VScrollbar
的值。
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;
}
}
关注点
编写代码时遇到的有趣/有趣/令人恼火的事情? 越界错误 :-) 特别是在 Keys.Down
和 Keys.PageDown
上。这促使我稍微回退代码;更优化的代码,但充斥着错误的,在另一个类(loftyGoalForm1
)中可用。如果存在一个回退代码无法处理的用例,我会回到绘图板并重新访问 loftyGoalForm1
的逻辑。
已知限制
十亿行,因为 .NET 的 VScrollbar
只能支持 32 位数字。如果我找到一个 64 位垂直滚动条,我会将其集成到我的 MycFramework 的查找控件中。