C# DataGridView 行追加の3パターン — Rows.Add / DataSource バインド / BindingList の使い分け

みなさんこんにちは!ヒロポンです!

つい先日、後輩に相談されまして。「DataGridView に行を足したいんすけど、grid.Rows.Add が例外吐くんですよ」と。ん? DataSource バインドしてない?? ……案の定でした。

業務の入力画面やマスタ編集で、やりたいのは「グリッドに1行足す」だけ。なのに思った通りに入らない。地味にハマるんですよね、これ。俺も昔やらかしました。

今回は C# DataGridView 行追加 のやり方を、業務SEが現場で踏む3パターンに絞って書いていきます。Rows.Add で直接足す、DataSource にバインドしてる時、BindingList<T> 経由。この3つ。全部コピペで動くコードで出します。

💡 DataGridView の クリックの出し分け(左/右/ダブル)は C# WinForms DataGridView のクリック3アクション行選択イベントの使い分けは C# DataGridView 行選択イベント3種 にまとめてます。今回はその実務クラスタの 「行を足す」編 です。

目次

結論: 行追加は「バインドしてるか」で3つに分かれる

先に結論。DataGridView の行追加でやることは、「DataSource をバインドしてるかどうか」で書き方が変わる。これだけです。迷ったらこの表を見て選べばOK。

DataGridView 行追加3パターンの使い分け早見表(バインド状態 / 足す相手 / 主な使い所)

ひとことで言うと、DataGridViewデータソースをバインドした瞬間、grid 側の直接追加を禁止する。だから「足す相手」を間違えると例外で落ちる。ここさえ押さえれば9割解決です。

なぜ行追加でつまずくのか

こんな単純な操作で、なんでつまずくのか。理由は2つ。

1つは、DataSource を設定すると grid.Rows.Add が使えなくなること。バインド中の grid は「表示はソースに任せる」モードになります。grid に直接足そうとすると InvalidOperationException で蹴られる。

もう1つは、AllowUserToAddRows の新規入力行。末尾に出る空っぽの行のことなんですけど、これが有効だと Rows.Count に幻の1行が混ざる。件数チェックで「あれ、1件多い??」ってなるやつです。

この2つは後半のハマりポイントで潰します。まずは動くコードから。

最短対処: コピペで動く3つの行追加

動作確認メモ: コードはすべて C# 7.3(.NET Framework 4.7.2)でビルド確認済みです。DataTable への追加と BindingList<T> の変更通知は実機ログで裏取りしてます(このあと実行結果を載せます)。一方 DataGridView 自体の画面表示が絡む部分は、Windows + Visual Studio の実環境で確認してください(Linux の検証環境は画面サーバーが無く、grid の描画までは再現できないため)。

パターン①: 非バインド時 — Rows.Add / Rows.Insert

DataSource を使わず、grid に手で列と行を組むパターン。一番シンプルです。

// 先に列を用意(非バインド)
grid.Columns.Add("Id", "ID");
grid.Columns.Add("Name", "名前");

// 末尾に1行追加(値をそのまま渡す)
grid.Rows.Add("1", "佐藤");

// 空行を足してからセルに代入してもいい
int idx = grid.Rows.Add();
grid.Rows[idx].Cells["Name"].Value = "鈴木";

// 指定位置に差し込みたい時は Insert
grid.Rows.Insert(0, "0", "先頭に入る行");

Rows.Add が末尾、Rows.Insert(index, ...) が指定位置。手組みの一覧なら、これでいい感じに足せます。

パターン②: DataTable バインド時 — ソース側に足す

grid.DataSource = dt でバインドしてる場合は、grid じゃなくて DataTable 側に足す。「grid に足してるのに増えない??」ってなるやつ、だいたいこれです。一番ハマる人が多い。

// DataSource にバインド済みの DataTable を取り出して足す
var dt = (DataTable)grid.DataSource;
dt.Rows.Add(3, "高橋"); // grid に自動で反映される

// DataRow を作って詰めてから Add でもOK
var row = dt.NewRow();
row["Id"] = 4;
row["Name"] = "渡辺";
dt.Rows.Add(row);

ソース(dt)に足せば、grid は勝手に追従します。こんな感じで、SQL から引いた表をそのまま出してる画面なら、この形が一番素直。

実行結果(DataTable 側に足した中身を確認):

DataTable のソース側に行追加した実行結果(dt.Rows.Add と NewRow で2件追加・高橋/渡辺)

パターン③: BindingList / BindingSource 経由

DataTable じゃなくて「クラスで持ちたい」時は BindingList<T> を使います。型がある分、後の保守が楽になる。

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// BindingSource を噛ませてバインド
var list = new BindingList<Employee>();
var source = new BindingSource { DataSource = list };
grid.DataSource = source;

// list に Add すると grid に即反映される
list.Add(new Employee { Id = 5, Name = "中村" });

List<T> だと追加が grid に反映されない。でも BindingList<T> は変更通知を持ってるんで、Add した瞬間に grid が更新されます。こんな感じで、クラスベースの画面はこれが定番。

実行結果(Add で変更通知が発火しているのを確認):

BindingList の変更通知が発火する実行結果(list.Add で ListChanged 発火・SupportsChangeNotification=True)

ハマりポイント: 知らないと一晩飛ぶやつ

コピペで動くのは確認した。ここからは、知らずに本番へ流すと夜中に呼び出されるやつを2つ。

ハマり①: DataSource 設定済みで grid.Rows.Add → InvalidOperationException

冒頭の後輩がハマったのがこれ。俺も2年目で同じ例外を出して、30分ほど原因が分からず固まりました。

var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("Name", typeof(string));

var grid = new DataGridView();
grid.DataSource = dt; // バインドした

grid.Rows.Add(1, "佐藤"); // ← ここで InvalidOperationException

例外メッセージはこう出ます。

System.InvalidOperationException:
データ連結されている場合、DataGridView コントロールの行コレクションをプログラムで変更することはできません。

これは .NET の仕様で、バインド中の grid.Rows.Add は必ず弾かれます。直し方はパターン②と同じ。grid じゃなく dt.Rows.Add(1, "佐藤") 側に足す。この「ソース側に足す」だけは実環境でも確認済みなんで、安心して使ってOK。バインド中は「grid は表示係、追加はソース係」と役割が分かれてる。こう覚えておくと迷いません。

ハマり②: AllowUserToAddRows の新規入力行で件数が1ズレる

AllowUserToAddRows(既定 true)が有効だと、末尾に空の入力行が出ます。これ自体は便利なんですけど、Rows.Count にこの1行が含まれる。

// AllowUserToAddRows = true(既定)のまま件数を数えると…
int count = grid.Rows.Count; // データ3行でも「4」が返る(+新規入力行)

// データ行だけ数えたい時は IsNewRow を除外する
int real = grid.Rows.Cast<DataGridViewRow>().Count(r => !r.IsNewRow);

// そもそも入力行が要らない画面なら、最初にオフにしておく
grid.AllowUserToAddRows = false;

「件数が1多い」「最後の行が空でループがコケる」系のバグ。だいたいこの新規入力行が犯人です。表示専用のグリッドなら、最初に false にしておく。件数もいい感じに素直になります。

現場メモ: マスタ編集画面で時間を溶かさないために

流通系の基幹システムを保守してた頃、商品マスタの編集画面で「行を1件足す」ボタンが例外を吐く、という問い合わせが来たことがあって。数千行のマスタを DataTable でバインドしてる画面でした。

原因はまさにハマり①。前任者が grid.Rows.Add で書いてて、テスト時はバインド前に呼んでたから動いてた。本番でデータを先に読むようにしたら、バインド後に Rows.Add が走って落ちる。時限式のバグだったんですよね。

直したのは1行です。grid.Rows.Add(...)((DataTable)grid.DataSource).Rows.Add(...) に変えただけ。ボタン1個の修正でしたが、原因にたどり着くまでが長かった。

教訓は、「この grid はバインドされてるか?」を最初に確認する。これだけで行追加まわりの事故は8割減ります。バインド済みなら grid に直接触らない。それだけ。

まとめ

DataGridView の行追加、整理するとこう。

  • ① Rows.Add / Rows.Insert — 非バインド(手組み)の時。grid.Rows.Add(...)
  • ② DataTable 側に足す — DataTable をバインドしてる時。((DataTable)grid.DataSource).Rows.Add(...)
  • ③ BindingList — クラスで持つ時。list.Add(new Employee{...})

ハマりは「バインド済みで grid.Rows.Add すると例外」と「新規入力行で件数が1ズレる」の2つ。バインドしてるかを先に確認して、してるならソース側に足す。これで詰まることはほぼ無くなります!!

よくある質問

Q1. DataSource をバインドした DataGridView に grid.Rows.Add で行を足せますか?

A. 足せません。InvalidOperationException(「データ連結されている場合…」)が出ます。バインド中はソース側(DataTable や BindingList)に Add すると、grid に自動反映されます。

Q2. DataGridView の AllowUserToAddRows とは何ですか?

A. 末尾に表示される新規入力用の空行(プレースホルダ)を出すかどうかの設定です。既定は true で、有効だと Rows.Count にその1行が含まれて件数が1件ズレます。表示専用なら false にしておくのが無難。

Q3. BindingList と DataTable、行追加ではどちらを使うべきですか?

A. クラス(型)で扱いたいなら BindingList<T>、SQL から引いた表をそのまま出すなら DataTable が素直です。どちらもソース側に Add すれば grid に反映されます。逆に List<T> だと反映されないので注意。

Q4. Rows.Add と Rows.Insert の違いは何ですか?

A. Rows.Add は末尾に追加、Rows.Insert(index, ...) は指定位置に差し込みます。どちらも非バインド(DataSource 未設定)の時に使うメソッドです。

Q5. なぜ BindingList を使うのか、List じゃダメなんですか?

A. List<T> は「中身が変わった」という通知を grid に送れないからです。Add しても画面が更新されない。BindingList<T> は変更通知(IBindingList)を持ってるので、追加・削除が即グリッドに反映されます。クラスで持つならこっち一択です。


ここまでで DataGridView の行追加は押さえられたはず。

ただね、こういう「バインドしてるか1回確認すれば3秒、しないと30分」の勘どころって、現場で一個ずつ拾っていくしかないんですよね。で、こういう地味な事故対応を積み重ねてる業務SEほど、なぜか自分の市場価値を低く見積もりがち。「俺なんてただの保守要員だから」って。その自己評価の話を、下の関連記事の最後に置いときます。技術の合間の箸休めにどうぞ。

関連記事

以上!

同じところで詰まってる人いたら、どんどんシェア待ってるぜ!!


執筆者

バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次