みなさんこんにちは!ヒロポンです!!
朝、客先で席に着いた瞬間に「DataGridView の行を選択したら下のパネルに明細を表示してほしいんですけど」って肩を叩かれた経験、ないですか??
ん?普通に SelectionChanged で書けばよくない??俺も最初はそう思ってた。素直に SelectionChanged でハンドラ書いて、動作確認 OK、レビューも通って、本番投入。ここまでは普通の業務 SE の朝です。
数日後、業務側から「Ctrl+A で全選択した瞬間に画面が固まる」と連絡が来た。ログ見たら、1000 行選んだ瞬間にハンドラが 1000 回走って、明細パネルを 1000 回書き換えてた。マジか……。
DataGridView で「行を選んだ瞬間」を取るだけのことに、イベントが 3 つある。SelectionChanged / CellEnter / CurrentCellChanged。発火タイミングが微妙に違って、朝礼前のデバッグで一番踏みやすいやつです。
今回は、この 3 つを使い分けるための早見表と、コピペで動くコードを置いておきます。
結論: 行選択 → 明細表示なら CurrentCellChanged + 起動時 guard
先に答えだけ言うと、「行を選んだ瞬間に下のパネルへ明細を流す」系の業務 UI なら、CurrentCellChanged + Form_Load 直後の null guard が一番安全です。
private void dgv_CurrentCellChanged(object sender, EventArgs e)
{
// Form_Load 直後にも 1 回呼ばれるので、CurrentRow null チェック必須
var row = dgv.CurrentRow;
if (row == null || row.IsNewRow) return;
var id = row.Cells["Id"].Value?.ToString();
LoadDetailPanel(id);
}
SelectionChanged だと Ctrl+A で爆死、CellEnter だと編集モード進入でも発火する。地雷を両方避けられます。
なぜ 3 つもイベントがあるのか
そもそも DataGridView って Excel ライクな UI を意識して作られたコントロールです。「選択範囲」「カレント セル」「セル移動」の 3 つの状態概念 がそれぞれ独立して存在してて、それぞれにイベントが用意されてる、という建付け。
| 概念 | 何を表すか | 対応イベント |
|---|---|---|
| 選択範囲 (Selection) | 複数行・複数セルを Ctrl/Shift で選んだ集合 | SelectionChanged |
| カレント セル (CurrentCell) | キーボード操作の起点となる「青枠の付いた 1 セル」 | CurrentCellChanged |
| セルへの入場 (Enter) | フォーカスがそのセルに移動した瞬間 | CellEnter |
業務系 UI で「行を選んだ瞬間に処理を走らせたい」と思う時、欲しいのはだいたい「カレント セルが移動した = 注目してる行が変わった」って意味なんですよね。だから CurrentCellChanged が正解になるケースが多い。
最短対処: 3 イベントの早見表 + コピペで動くコード
発火タイミングと用途を表にまとめます。朝礼前に上司へ説明する時、これ 1 枚で済むやつ。

3 パターンをそれぞれ最小コードで書くとこんな感じ。
パターン 1: SelectionChanged — 複数選択の集計に使う
private void dgv_SelectionChanged(object sender, EventArgs e)
{
// 選択範囲のセル数を集計する用途には向く
var selectedCount = dgv.SelectedRows.Count;
lblStatus.Text = $"選択中: {selectedCount} 行";
}
落とし穴は、行を「明細表示」みたいな重い処理に使ったとき。Ctrl+A で 1000 行選択するとハンドラが 1000 回呼ばれて固まります。これ、俺がやらかしたやつ。
パターン 2: CellEnter — セル入場時の検証に使う
private void dgv_CellEnter(object sender, DataGridViewCellEventArgs e)
{
// 「このセルに入ってきた瞬間」を取れる
if (e.RowIndex < 0) return; // ヘッダー行ガード
Debug.WriteLine($"CellEnter: Row={e.RowIndex}, Col={e.ColumnIndex}");
}
落とし穴: ユーザーがセル編集中に Tab キーで隣のセルへ動くと、また発火 します。「行が変わった時だけ動かしたい」という用途には不向き。
パターン 3: CurrentCellChanged — 行を選んだ瞬間の明細表示に使う (推奨)
private void dgv_CurrentCellChanged(object sender, EventArgs e)
{
var row = dgv.CurrentRow;
if (row == null || row.IsNewRow) return; // ★ Form_Load 直後の 1 回発火対策
var id = row.Cells["Id"].Value?.ToString();
if (string.IsNullOrEmpty(id)) return;
LoadDetailPanel(id);
}
これで、行が変わった瞬間だけ明細パネルが更新されます。Ctrl+A で全選択しても 1 回しか走らない。いい感じ。
素朴に書いた版 → 推奨形への書き直し
冒頭で俺が踏んだやつを before/after で残しておきます。

差分のポイントは 3 つ。①イベントを CurrentCellChanged に変える、②CurrentRow の null/IsNewRow チェック、③Cells["Id"].Value の null セーフキャスト。これだけで、全選択時の爆発と起動時の NullRef を両方塞げます。
ハマりポイント — 知らないと一晩飛ぶやつ 3 つ
⏱ 対処目安: 全部合わせて 30 分以内に潰せます。深夜デバッグで詰まる前にチェック。
① SelectionChanged 全選択で大量発火 (⏱ 5 分)
Ctrl+A で全行選択した瞬間、ハンドラが行数分連続コール。明細表示・グラフ再描画・DB 検索を仕込んでると画面が固まります。俺は物流系の基幹で、業務側から「画面が固まりますって連絡が来てます」と報告を受けて気付いた。技術的には数行の書き換えで終わるんですが、業務側の信頼回復に翌朝の朝礼で 30 分使ったやつ。
② CellEnter が編集モード中も呼ばれる (⏱ 10 分)
CellEnter は「フォーカスがセルに入った時」のイベント。ユーザーがセル編集中に Tab で隣のセルへ動くたびに発火します。「行が変わった時だけ走らせたい」と思って書くと、編集中もハンドラが走って予期しない挙動になる。CurrentCellChanged ならカレント セル単位の発火なので、編集進入では呼ばれません。
③ CurrentCellChanged が Form_Load 直後に 1 回発火する (⏱ 15 分)
これがいちばん意地悪な罠。DataGridView に DataSource を設定した瞬間、最初の 1 行がカレント セル扱いになるので CurrentCellChanged が 1 回呼ばれます。この時 dgv.CurrentRow が null になっているケースがあって、そのまま row.Cells["Id"].Value を触ると NullReferenceException で落ちる。冒頭の推奨コードに入れてある if (row == null || row.IsNewRow) return; がその対策です。
現場メモ: 障害対応で時間を溶かさないために
物流系の基幹で SelectionChanged で組んだ画面、結局 CurrentCellChanged + 起動時 guard に書き直して落ち着きました。書き換え自体は 5 分。
問題はその後。業務側に「修正が入ります、試してください」と説明する電話で 20 分。翌朝の朝礼で「ご迷惑おかけしました」と頭を下げる時間で 30 分。技術ミスを 5 分で直しても、業務側の信頼を取り戻すのには時間がかかるんですよね。
朝の検索でこの記事に辿り着いた人は、デバッガで止めて発火回数を見るより、まずイベントを CurrentCellChanged に差し替えるのが最短です。
まとめ
DataGridView の「行を選んだ瞬間」を取るイベントは 3 つあって、用途が全部違います。
- 複数選択の集計 →
SelectionChanged - セル入場時の検証 →
CellEnter - 行を選んだ瞬間の明細表示 →
CurrentCellChanged+ 起動時 null guard
業務 SE が一番踏むのは 3 つ目の「明細表示」系。SelectionChanged で組んで全選択時に固まるパターン。これを朝礼までに直したいなら、イベントを差し替えて null guard を 2 行足すだけ。それで OK。
DataGridView は 1 つのコントロールでも、選択 / カレント セル / セル入場の 3 概念がそれぞれ独立して動いてる。歴史的経緯で似たイベントが並んでるだけなので、「どれが何を取るか」さえ覚えれば、朝礼前のデバッグで詰まらずに済みます。
……話ずれるけど、業務 SE が DataGridView 1 つでこれだけ詰まるって、現場あるあるなんですよね。同じ詰まりを面談で 30 秒で説明できるか、って観点でキャリア記事も置いてあるので、並行稼働や単価交渉の弾薬にしたい人はそっちもどうぞ。
よくある質問
Q1. SelectionChanged と CurrentCellChanged は同時に発火しますか?
A. マウスクリックで行を変えた時は、大抵 両方発火します。順序は CurrentCellChanged → SelectionChanged。「行が変わった瞬間に 1 回だけ動かしたい」用途なら、どちらか片方にだけ処理を書けば十分です。
Q2. RowEnter ってイベントもあった気がするんですが、違いは?
A. RowEnter は「カレント セルの行が変わった瞬間」で、CurrentCellChanged とほぼ同じタイミング。差は微妙で、RowEnter の方が e.RowIndex を直接取れるので書き味が少しスッキリします。挙動は CurrentCellChanged と読み替えてもらって OK。
Q3. SelectionChanged を使いたい場面ってあるんですか?
A.「Ctrl/Shift で複数選択した結果を集計したい」系。たとえば「選択した行の金額合計をフッターに出す」みたいな用途は SelectionChanged が向きます。「単一行の明細表示」には向かない、という整理。
Q4. なぜ CurrentCellChanged は Form_Load 直後に 1 回発火するんですか?
A. DataGridView に DataSource を割り当てた時点で、内部的に最初のセルが「カレント セル」になります。「null → 最初のセル」への変化として CurrentCellChanged が発火する建付け。コンポーネントの仕様なので、ハンドラ側で null guard を入れるのが定石です。
Q5. CurrentRow と CurrentCell の違いは?
A. CurrentCell は「カレント セル」(列 × 行で 1 個のセル)、CurrentRow はそのセルが属する行 全体を返すプロパティ。行単位の明細表示には CurrentRow が直感的で書きやすいですが、内部的には CurrentCell.OwningRow と同じものを指してます。row.IsNewRow で新規行追加用のダミー行を弾けるので、合わせて使うのがおすすめ。
Q6. EnableHeadersVisualStyles とか他のプロパティとの相互作用はある?
A. 行選択イベントの発火タイミング自体には影響しません。発火順序や null 周りの挙動は DataSource の種類 (DataTable / List / BindingList) によって微妙に変わることがあるので、独自バインディングで詰まったら DataSource の差し替えで切り分けるのが早いです。基本は今回紹介した null guard と IsNewRow チェックでだいたい吸収できます。
余談: DataGridView の「行選択 = 行選択イベント」だと思ってた頃の話
新人の頃、行選択ってまさか 3 種類もあるとは思ってなくて、最初に Visual Studio のデザイナで DataGridView 選んで「Events タブ」を開いた時、Cell〜 だけで 30 個近くイベントが並んでて目眩がした。
「ぜんぶ調べてたら今日中に終わらない」と思って、結局 最初に出会った SelectionChanged で書いてレビュー出して、業務側から連絡が来てから「あ、複数あるのか」と気付いた、という流れ。あなたも経験あるでしょ?こういうやつ。
今思えば、朝の客先で「ちょっとこれ動かしたい」と言われた瞬間に、こういう発火タイミングの早見表 1 枚を持ってるかどうかで、朝礼までの 30 分の使い方が全然変わるんですよね。深夜の自宅でこの記事に辿り着いた人は、表だけスクショして翌朝の現場に持ち込んでもらって大丈夫です。
同じ罠を踏んだ業務 SE 仲間がいたら、どんどんシェア待ってるぜ!!
動作確認メモ: ここで紹介した DataGridView 行選択イベントの発火タイミング検証は、Mono container でコンパイル成功確認のみ実施しました。実機 (Windows + Visual Studio 2019 + .NET Framework 4.7.2) での実際の発火挙動 (Ctrl+A 全選択時の大量発火 / Form_Load 直後の 1 回発火) は、お手元の開発環境で別途お試しください。
関連記事
- C# WinForms DataGridView のクリック3アクション — 左クリック / 右クリック / ダブルクリックを分ける実装 — 行選択イベントと組み合わせて使う、クリック系イベントの早見表はこっち
- WinForms DataGridView の編集モード完全ガイド — ReadOnly / EditMode / RowValidating の使い分け —
CellEnterが編集モード中も再発火する原因と回避パターンの解説 - C# DataGridView の DataSource を後から変更する全パターン — 行選択イベントが起動時に 1 回発火する DataSource 設定順の解説
- DataGridViewのDataSourceを指定した後列名を自由自在に変更する方法 —
Cells["Id"].Valueで列名指定する時の前提知識 - 面談で『俺が入れば〇〇』と提示する技術アピール術 — フリーランス業務SE の単価交渉の核 — DataGridView 1 つでこれだけ詰まるあるあるを、面談で 30 秒で説明できるか? という観点でキャリアに振った話
以上!
執筆者
バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


コメント