DataGridViewやListViewのコントロールは、数万行のデータになってくると表示に時間がかかってしまいます。これは、描画処理が大量に発生して遅くなっているようです。
ListViewは、BeginUpdate()~EndUpdate()で描画を止めれば多少なり速くはなりますが、DataGridViewは、SuspendLayout()~ResumeLayout()を行ってもあまり効果がない場合があります。
また、行や列のAutoSizeをAllCellsなど指定すると、すごく遅くなるので行追加時はAutoSizeを切るなどして避けたいところです。
その他にDataGridViewの速度改善を調べてみると、手動で行を追加する方法とDataSauceに引き渡してバインドする方法とで速さに違いが出るそうです。実際に速度に違いがあるのか未検証だったので試してみました。
以下のような4パターンの検証です。

①(非バインド1)Rows.Add()で1行ずつ追加していくパターン
②(非バインド2)Rows.AddRange()で一気に追加するパターン
③(バインド1)DataTableをバインドするパターン
④(バインド2)独自クラスをバインドするパターン
スポンサードリンク
※ DataGridViewのAllowUserToAddRows=falseしています。
※ 1000行ほど適当な値をセットしています。
行をAddして値をセットしていく方法です。ソース自体は何をしているかわかりやすいですね。
この例ですが、私はDB以外からデータを取得するときによく使っている気がしますw
最初に変数上のList<DataGridViewRow>に追加していき、最後にAddRangeで一気に追加しているパターンです。私はあまり使用したことのないパターンです。
データバインディングで一番馴染みがあるDataTableクラスを使った例です。入門的なサイトや書籍でもこの例が一番多いような気がします。DataTableは、非常によくできたクラスです。ですが、個人的に色々な機能を持ちすぎて重たい気がします。まぁ便利なんでよく使ってますが^^;
MyDataというデータクラスを作成しています。その配列をデータソースに設定しています。途中、カラムのDataPropertyNameにプロパティ名をセットしています。 データ構成がはっきりしている場合はこの記述で問題ないと思います。
最近は、データをモデル化する傾向がありますし、インターフェースのトラブルをなくしたり、XMLやJSONにシリアライズしやすいですね。
※ 環境により速度は変化する場合がございます。
この記事を書く前から大体予想はついていたのですが、「こんなに違うのかぁぁ!!」って感じです。①と③の差は10分の1だし、①と④なんか100分の1近く速度が違うのです!!
(結構Addで書いてきたシステムあるのに・・・目をつぶろうww)
件数が万を超えている場合、特に理由がなければバインドの方を採用していきたいところですね。
ListViewは、BeginUpdate()~EndUpdate()で描画を止めれば多少なり速くはなりますが、DataGridViewは、SuspendLayout()~ResumeLayout()を行ってもあまり効果がない場合があります。
また、行や列のAutoSizeをAllCellsなど指定すると、すごく遅くなるので行追加時はAutoSizeを切るなどして避けたいところです。
その他にDataGridViewの速度改善を調べてみると、手動で行を追加する方法とDataSauceに引き渡してバインドする方法とで速さに違いが出るそうです。実際に速度に違いがあるのか未検証だったので試してみました。
以下のような4パターンの検証です。

①(非バインド1)Rows.Add()で1行ずつ追加していくパターン
②(非バインド2)Rows.AddRange()で一気に追加するパターン
③(バインド1)DataTableをバインドするパターン
④(バインド2)独自クラスをバインドするパターン
①(非バインド1)Rows.Add()で1行ずつ追加していくパターン
C#
private void Button1_Click(object sender, EventArgs e) { // クリア this.DataGridView1.DataSource = null; this.DataGridView1.Rows.Clear(); this.DataGridView1.Columns.Clear(); this.Button1.Enabled = false; System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); // start try { this.DataGridView1.SuspendLayout(); // DataGridViewカラム追加 for (int iCol = 0; iCol < 10; iCol++) { this.DataGridView1.Columns.Add("col_" + iCol, iCol.ToString()); } // 行追加 for (int iRow = 0; iRow < 10000; iRow++) { this.DataGridView1.Rows.Add(); // 1セルずつ for (int iCol = 0; iCol < 10; iCol++) { this.DataGridView1.Rows[this.DataGridView1.RowCount - 1].Cells[iCol].Value = iRow + iCol; } } } finally { this.DataGridView1.ResumeLayout(); } sw.Stop(); this.txt_speed.Text = sw.Elapsed.TotalMilliseconds.ToString(); this.Button1.Enabled = true; }
※ DataGridViewのAllowUserToAddRows=falseしています。
※ 1000行ほど適当な値をセットしています。
行をAddして値をセットしていく方法です。ソース自体は何をしているかわかりやすいですね。
この例ですが、私はDB以外からデータを取得するときによく使っている気がしますw
②(非バインド2)Rows.AddRange()で一気に追加するパターン
C#
private void Button2_Click(object sender, EventArgs e) { // クリア this.DataGridView1.DataSource = null; this.DataGridView1.Rows.Clear(); this.DataGridView1.Columns.Clear(); this.Button2.Enabled = false; System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); // start try { this.DataGridView1.SuspendLayout(); // DataGridViewカラム追加 for (int iCol = 0; iCol < 10; iCol++) { this.DataGridView1.Columns.Add("col_" + iCol, iCol.ToString()); } // 行の生成 var addRows = new List(); for (int iRow = 0; iRow < 10000; iRow++) { var addRow = new DataGridViewRow(); addRow.CreateCells(this.DataGridView1); object[] vals = new object[10]; for (int iCol = 0; iCol < 10; iCol++) { vals[iCol] = iRow + iCol; } addRow.SetValues(vals); addRows.Add(addRow); } // レンジで追加 this.DataGridView1.Rows.AddRange(addRows.ToArray()); } finally { this.DataGridView1.ResumeLayout(); } sw.Stop(); this.txt_speed.Text = sw.Elapsed.TotalMilliseconds.ToString(); this.Button2.Enabled = true; }
最初に変数上のList<DataGridViewRow>に追加していき、最後にAddRangeで一気に追加しているパターンです。私はあまり使用したことのないパターンです。
③(バインド1)DataTableをバインドするパターン
C#
private void Button3_Click(object sender, EventArgs e) { // クリア this.DataGridView1.DataSource = null; this.DataGridView1.Rows.Clear(); this.DataGridView1.Columns.Clear(); this.Button3.Enabled = false; System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); // start var tbl = new DataTable(); try { this.DataGridView1.SuspendLayout(); // DataRow生成 for (int iCol = 0; iCol < 10; iCol++) { tbl.Columns.Add(iCol.ToString()); } // DataRow追加 for (int iRow = 0; iRow < 10000; iRow++) { DataRow addRow = tbl.NewRow(); for (int iCol = 0; iCol < 10; iCol++) { addRow[iCol] = iRow + iCol; } tbl.Rows.Add(addRow); } // バインド this.DataGridView1.DataSource = tbl; } finally { this.DataGridView1.ResumeLayout(); } sw.Stop(); this.txt_speed.Text = sw.Elapsed.TotalMilliseconds.ToString(); this.Button3.Enabled = true; }
データバインディングで一番馴染みがあるDataTableクラスを使った例です。入門的なサイトや書籍でもこの例が一番多いような気がします。DataTableは、非常によくできたクラスです。ですが、個人的に色々な機能を持ちすぎて重たい気がします。まぁ便利なんでよく使ってますが^^;
④(バインド2)独自クラスをバインドするパターン
C#
//独自データクラス public class MyData{ public int col_0 { get; set;} public int col_1 { get; set;} public int col_2 { get; set;} public int col_3 { get; set;} public int col_4 { get; set;} public int col_5 { get; set;} public int col_6 { get; set;} public int col_7 { get; set;} public int col_8 { get; set;} public int col_9 { get; set;} } private void Button4_Click(object sender, EventArgs e) { // クリア this.DataGridView1.DataSource = null; this.DataGridView1.Rows.Clear(); this.DataGridView1.Columns.Clear(); this.Button4.Enabled = false; System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); // start var data = new List(); try { // DataGridViewカラム追加 for (int iCol = 0; iCol < 10; iCol++) { var col = new DataGridViewTextBoxColumn(); col.HeaderText = iCol.ToString(); col.DataPropertyName = "col_" + iCol; // クラスのプロパティと名前を合わせる this.DataGridView1.Columns.Add(col); } // DataRow追加 for (int iRow = 0; iRow < 10000; iRow++) { var one = new MyData(); one.col_0 = iRow + 0; one.col_1 = iRow + 1; one.col_2 = iRow + 2; one.col_3 = iRow + 3; one.col_4 = iRow + 4; one.col_5 = iRow + 5; one.col_6 = iRow + 6; one.col_7 = iRow + 7; one.col_8 = iRow + 8; one.col_9 = iRow + 9; data.Add(one); } // バインド this.DataGridView1.DataSource = data; } finally { this.DataGridView1.ResumeLayout(); } sw.Stop(); this.txt_speed.Text = sw.Elapsed.TotalMilliseconds.ToString(); this.Button4.Enabled = true; }
MyDataというデータクラスを作成しています。その配列をデータソースに設定しています。途中、カラムのDataPropertyNameにプロパティ名をセットしています。 データ構成がはっきりしている場合はこの記述で問題ないと思います。
最近は、データをモデル化する傾向がありますし、インターフェースのトラブルをなくしたり、XMLやJSONにシリアライズしやすいですね。
結果・まとめ
パターン | 速度(ms) |
---|---|
①非バインド1 | 3058.403 |
②非バインド2 | 718.4012 |
③バインド1 | 366.6652 |
④バインド2 | 35.6305 |
この記事を書く前から大体予想はついていたのですが、「こんなに違うのかぁぁ!!」って感じです。①と③の差は10分の1だし、①と④なんか100分の1近く速度が違うのです!!
(結構Addで書いてきたシステムあるのに・・・目をつぶろうww)
件数が万を超えている場合、特に理由がなければバインドの方を採用していきたいところですね。