DataGridView:仮想モードで大量データを高速表示(サンプルあり)

📋 目次(クリックで展開)

DataGridView に大量のデータを表示しようとする際、件数が数万件、数十万件と増えるにつれて、データの読み込みに時間がかかったり、スクロールの動作が重くなったりすることがあります。これは、DataGridView がすべての行やセルのオブジェクトをメモリ上に生成して保持しようとするためです。

このような大量のデータを遅延なく高速に表示・スクロールさせるための機能として、DataGridView には 「仮想モード(Virtual Mode)」 が用意されています。

今回は、100万件のデータであってもメモリ消費を抑え、軽快にスクロール動作を維持するための仮想モードの実装手順について解説します。

仮想モードの仕組み

通常、DataGridView にデータを表示するときは DataSource プロパティに DataTable などをバインドしますが、仮想モードではデータソースを直接バインドしません。

仮想モードでは、DataGridView には「データの総行数(RowCount)」だけを通知し、実際のデータ管理はプログラム側の配列やリストなどで個別に行います。

画面のスクロールなどによってセルを描画する段階になって初めて、DataGridView から「〇行目の〇列目のデータを表示したい」というリクエスト(CellValueNeeded イベント)が発生します。プログラム側はその要求されたセルのデータのみをデータソースから取得して返します。

画面に見えている数十行分のセルオブジェクトしか生成されないため、データ件数が100万件あっても起動が速く、メモリの消費も抑えられます。

実装コード

以下は、仮想モードを有効にし、100万行のデータを表示するコード例です。

サンプルでは仮想化の効果を分かりやすく実験するため、メモリの消費を抑える目的で、行インデックス(行番号)からその場で動的にテキストを生成して返すモック方式を採用しています。

Form1.cs

Form1.cs
namespace DataGridView_VirtualMode;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeDataGridView();
}
private void InitializeDataGridView()
{
// 1. 手動で列を定義する(仮想モードでは自動生成されないため)
var colCode = new DataGridViewTextBoxColumn();
colCode.Name = "Code";
colCode.HeaderText = "商品コード";
dataGridView1.Columns.Add(colCode);
var colName = new DataGridViewTextBoxColumn();
colName.Name = "Name";
colName.HeaderText = "商品名";
dataGridView1.Columns.Add(colName);
var colPrice = new DataGridViewTextBoxColumn();
colPrice.Name = "Price";
colPrice.HeaderText = "単価";
dataGridView1.Columns.Add(colPrice);
// 2. 仮想モードを有効にする
dataGridView1.VirtualMode = true;
// 3. 総行数を設定(100万行)
// 通常モードとは異なり、この行数を指定した瞬間に一瞬でグリッドが生成されます
dataGridView1.RowCount = 1000000;
// グリッドのサイズ調整(表示領域のみを対象にする設定)
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
// 4. データ要求イベントを購読
dataGridView1.CellValueNeeded += DataGridView1_CellValueNeeded;
}
/// <summary>
/// セルが画面に描画される際、データが必要になったタイミングで呼び出されるイベント
/// </summary>
private void DataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
// ヘッダー行などの場合は処理をスキップ
if (e.RowIndex < 0 || e.ColumnIndex < 0) return;
// 現在要求されている列名を取得
string columnName = dataGridView1.Columns[e.ColumnIndex].Name;
// 要求された行番号(e.RowIndex)をもとに、データを動的に返します
// ※実務では、ここで行番号をキーに独自のキャッシュリストや配列からデータを引き当てます
if (columnName == "Code")
{
e.Value = $"P{e.RowIndex + 1:D6}";
}
else if (columnName == "Name")
{
e.Value = $"テスト商品_{e.RowIndex + 1}";
}
else if (columnName == "Price")
{
// 行番号に応じた適当な数値を返す例
e.Value = (e.RowIndex % 10 + 1) * 100;
}
}
}

サンプルアプリケーション

DataGridView_Virtualization.zip
※ サンプルアプリケーションは、.NET 10 をターゲットに作成しています。

留意すべき注意点と実装の背景

仮想モードを利用して大量のデータを安定して処理する際、パフォーマンスを低下させないために把握しておくとよい点が2つあります。

自動サイズ調整機能(AutoSize)の制限

仮想モードを導入する上で最も注意が必要なのが、列幅や行高さの自動調整プロパティ(AutoSizeColumnsModeAutoSizeRowsMode)の設定です。

これらのモードに AllCells(全行のセルを計算対象にする設定)が有効になっていると、DataGridView は最適なサイズを計算するために、画面に見えていない行も含めて100万行すべての CellValueNeeded イベントを内部で走査してしまいます。これにより仮想化のメリットが失われ、起動時やスクロール時にフリーズしたような状態になります。

仮想モードを使用する場合は、サイズ調整モードを None(手動固定)にするか、サンプルコードのように表示されている範囲のみを計算対象とする DisplayedCells などのモードに設定する必要があります。

データソース側のアクセス速度

DataGridView 側の描画処理が最適化されても、CellValueNeeded イベントの中で呼び出すデータソース側の取得処理に時間がかかると、スクロールがカクつく原因になります。

100万件のデータをメモリ上の配列(Array)や高速なリスト(List<T>)で保持している場合は、インデックスによる参照が高速なため問題になりません。しかし、データベース(SQL Server や SQLite など)からその都度 SQL を発行してデータを取得する設計にすると、通信やディスクI/Oの負荷により動作が著しく低下します。

データベースのデータを仮想モードで表示する場合は、必要な件数(例:数ファイル分、または数百行単位)をまとめて事前にメモリに読み込んでおく「ページング(キャッシュ)」の仕組みを、データソース側で実装する構成が適しています。

まとめ

今回は、DataGridView の仮想モード(Virtual Mode)を利用して、100万レコードを超える大量データでも高速に表示・スクロールさせる方法について解説しました。

標準のバインド処理から仮想モードへの切り替えには列の手動定義やイベントの実装が必要になりますが、データ量に応じたメモリの逼迫を回避することができます。

  • VirtualMode プロパティを true に設定し、RowCount で総行数を一括指定する
  • CellValueNeeded イベントを利用して、画面に現れたセルの値だけをその都度引き渡す
  • 負荷集中を防ぐため、全行を対象とする自動サイズ調整機能は併用しない

大規模なログデータやマスタデータを扱うデスクトップアプリケーションを開発する際の参考になりましたら幸いです。


💬 コメント