DataGridView:大量データでも重くならない条件付き書式とスクロール遅延対策(サンプルあり)

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

DataGridView で特定の条件(例:在庫切れ、期限切れなど)を満たすセルや行の色を動的に変更する際、データ量が数百件程度であれば問題になりませんが、数千件〜数万行の大規模なデータを扱うと、突然「画面のスクロールが極端に重くなる」「フリーズしたようにガタつく」といったパフォーマンス劣化に直面することがあります。

一見、マシンスペックや .NET の描画限界のように思えますが、実は DataGridView 特有の「描画のライフサイクル」と「メモリの消費特性」を考慮したコードを書くことで、何万行あっても滑らかにスクロールする快適な画面を実現できます。

今回は、条件付き書式を実装した DataGridView が重くなる根本原因と、パフォーマンスを最大化するための具体的な改善テクニックについて解説します。

条件付き書式でスクロールが重くなる2つの根本原因

基本的な色の変更手順については、別記事「DataGridView 条件付き書式の実装テクニック(サンプルあり)」で解説している通り、CellFormatting イベントを利用するのが標準的です。

しかし、この CellFormatting イベントは、画面にセルが表示される瞬間や、マウスカーソルがセルの上を通過した瞬間など、1秒間に数百〜数千回という膨大な頻度でリアルタイムに呼び出されるという特性を持っています。

このイベントの内部で以下のような実装を行ってしまうと、アプリの動作は致命的に重くなります。

原因①:イベントの内部で毎回インスタンス(new)を生成している

// ❌ パフォーマンスを著しく低下させるNGコード
private void DataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// イベントが走るたびに、毎回新しいフォントやスタイルオブジェクトが作られる
e.CellStyle.Font = new Font("MS UI Gothic", 9, FontStyle.Italic);
e.CellStyle.BackColor = Color.FromArgb(255, 230, 230);
}

一瞬のスクロールの間に何千回も new が繰り返されるため、メモリ(ヒープ領域)が急激に圧迫され、不要になったメモリを解放するための「ガベージコレクション(GC)」がバックグラウンドで頻発します。この GC が走る瞬間に、画面が一瞬カクついたりフリーズしたりします。

原因②:コントロール側の Style プロパティを直接書き換えている

// ❌ 無限ループや描画遅延を引き起こすNGコード
dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Style.BackColor = Color.Red;

イベントの内部でコントロールが保持する実体の Style を直接変更すると、DataGridView は「スタイルが書き換わったから画面を再描画しなければならない」と判断し、再び自身で CellFormatting イベントを発生させます。結果として処理の無限連鎖(または極めて非効率な再描画処理)が起き、CPU使用率が100%に張り付く原因になります。

解決方法:スタイルのキャッシュと再利用(ApplyStyle)

これらの問題をクリアし、大量データでも滑らかに動作させるための最適解は、「使用する色やフォントのスタイルオブジェクトをあらかじめ作成してキャッシュ(保持)しておき、イベント内ではそれを再利用する」 という設計をとることです。

また、コントロールのプロパティを直接いじるのではなく、イベントの引数として渡されてくる e.CellStyle に対して適用するのが正しいルールです。

実装コード

以下は、事前に作成したスタイルをキャッシュし、CellFormatting イベント内で効率的に適用するパフォーマンス特化型の実装例です。

Form1.cs
public partial class Form1 : Form
{
private BindingList<ProductModel> _products = new();
// パフォーマンス対策:使い回すためのスタイルオブジェクトを事前にキャッシュ
private DataGridViewCellStyle _alertStyle = new DataGridViewCellStyle();
private DataGridViewCellStyle _disabledStyle = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
InitializeStyles(); // スタイルの事前作成
InitializeDataGridView();
}
// 1. フォーム初期化時に、色やフォントの設定を「一度だけ」生成しておく
private void InitializeStyles()
{
// 在庫切れ時の背景色(薄い赤)
_alertStyle.BackColor = Color.MistyRose;
// 注文不可時の文字色とフォント(グレー・斜体)
_disabledStyle.ForeColor = Color.Gray;
_disabledStyle.Font = new Font(dataGridView1.Font, FontStyle.Italic);
}
private void InitializeDataGridView()
{
// 大量データの読み込みを想定(サンプルデータ)
for (int i = 0; i < 5000; i++)
{
_products.Add(new ProductModel($"P{i:D4}", $"商品データ_{i}", i % 5 == 0 ? 0 : 10, i % 10 == 0 ? "注文不可" : "通常"));
}
dataGridView1.DataSource = _products;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
// イベントの購読
dataGridView1.CellFormatting += DataGridView1_CellFormatting;
}
// 2. 高速に呼び出される描画イベント
private void DataGridView1_CellFormatting(object? sender, DataGridViewCellFormattingEventArgs e)
{
if (e.RowIndex < 0 || e.RowIndex >= dataGridView1.Rows.Count) return;
if (dataGridView1.Rows[e.RowIndex].DataBoundItem is ProductModel product)
{
// 条件①:在庫数が 0 の場合(行全体の背景色を変更)
if (product.Stock == 0)
{
// 既存のカラー値をそのまま代入(newしない)
e.CellStyle.BackColor = _alertStyle.BackColor;
}
// 条件②:ステータスが「注文不可」かつ「ステータス」列の場合(セルの文字色・フォントを変更)
var columnName = dataGridView1.Columns[e.ColumnIndex].DataPropertyName;
if (columnName == "Status" && product.Status == "注文不可")
{
// ApplyStyle メソッドを使い、キャッシュしておいたスタイルを丸ごと適用する
e.CellStyle.ApplyStyle(_disabledStyle);
}
}
}
}

実行結果

5000行のデータに条件書式を設定していますが、問題なく縦スクロールが動作します。

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

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

まとめ

今回は、DataGridView で条件付き書式を実装した際にスクロールが重くなる原因と、その具体的な解決策について解説しました。
数千行〜数万行に及ぶ実務の業務システム開発において、パフォーマンスへの配慮は見た目の美しさと同じくらい重要になります。

  1. CellFormatting イベント内では絶対に new によるフォントやスタイルの生成を行わない。
  2. 必要なスタイルはフォーム初期化時に生成して使い回す(キャッシュする)。
  3. コントロールの Style プロパティではなく、e.CellStyle を操作する。

この原則を徹底するだけで、DataGridView の描画負荷は劇的に軽減され、驚くほど軽快に動作するようになります。既存の条件付き書式のコードを見直す際の、参考になりましたら幸いです。


💬 コメント