DataGridView:新規行を判定する方法(サンプルあり)

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

DataGridView でユーザーに行を追加させる設定(AllowUserToAddRows = true)にしている場合、最下部には常に空の「新規入力行(左端に * がある行)」が表示されます。

この行を通常のデータ行と同じように処理しようとするとシステムエラー(例外)の原因になるため、IsNewRow プロパティを使って判定を行うのが一般的です。しかし、実務で「保存ボタン」などを押したタイミングで判定を行おうとすると、DataGridViewの仕様による2つの大きな罠に直面し、意図通りに判定できないケースが多発します。

今回は、文字入力中やボタンクリック時でも100%正確に対象の行が「新規行(未確定行)」であるかを見極める、実務直結の制御方法について解説します。

DataGridView 新規行判定2つのポイント

標準の row.IsNewRow を使うだけでは、以下の2つの挙動によって誤判定やバグが発生します。

  1. 文字入力による昇格の罠 ユーザーが最下部の空行に1文字でも打ち込んだ瞬間、その行の IsNewRow は即座に false に変わります。「入力中のデータ行」へと昇格し、その真下に新しい空行(新たな IsNewRow == true の行)が自動生成されるためです。そのため、別の行に移動してデータが確定するまでの間、標準のプロパティだけでは「新しく追加している最中の行」だと判断できなくなります。
  2. ボタンクリック時のフォーカス移動の罠 空の新規行(* の行)を選択した状態で「保存」などのボタンをクリックすると、ボタンにフォーカスが移った瞬間に DataGridView は新規行の編集を自動的にキャンセルし、現在の行(CurrentRow)を強制的に1つ上の確定行へと引き戻してしまいます。 結果として、プログラムが走るときには選択行がズレており、正しい行を判定できなくなります。

これらの罠をクリアするために、「フォーカスがある間だけ最後に選んでいた行位置を記憶する」処理と、「裏側のデータソースの状態(DataRowView.IsNew)」を組み合わせた確実なロジックを実装します。

実装コード

以下は、DataTable をデータソースとしてバインドし、ボタンをクリックした瞬間に、対象の行が「既存の確定行」か「未入力の空行」か「現在入力中の未確定行」かを完全に仕分けるコード例です。

Form1.cs
using System.Data;
namespace DataGridView_Check_IsNewRow
{
public partial class Form1 : Form
{
private DataTable _dataTable = new DataTable();
// ユーザーが最後に選択していた行インデックスを記憶する変数
private int _lastSelectedRowIndex = -1;
public Form1()
{
InitializeComponent();
InitializeDataGridView();
btnCheckStatus.Click += btnCheckStatus_Click;
}
private void InitializeDataGridView()
{
_dataTable.Columns.Add("Code", typeof(string));
_dataTable.Columns.Add("Name", typeof(string));
_dataTable.Rows.Add("P001", "ノートパソコン");
dataGridView1.DataSource = _dataTable;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
// 選択行が変わったときに、インデックスを記憶するイベントを購読
dataGridView1.SelectionChanged += DataGridView1_SelectionChanged;
}
/// <summary>
/// 選択行が変わったときに呼び出されるイベント
/// </summary>
private void DataGridView1_SelectionChanged(object? sender, EventArgs e)
{
// DataGridView自身、またはその内部にフォーカスがあるときだけ、現在の行位置を記憶します
// これにより、ボタンがクリックされてフォーカスが逃げたときの「自動引き戻し」による行移動を無視できます
if (dataGridView1.ContainsFocus && dataGridView1.CurrentRow != null)
{
_lastSelectedRowIndex = dataGridView1.CurrentRow.Index;
}
}
/// <summary>
/// 「新規行かを確認」ボタンのクリックイベント
/// </summary>
private void btnCheckStatus_Click(object? sender, EventArgs e)
{
// 記憶している行インデックスが有効かチェック
if (_lastSelectedRowIndex == -1 || _lastSelectedRowIndex >= dataGridView1.RowCount)
{
MessageBox.Show("行が選択されていません。");
return;
}
// CurrentRow(勝手に動いてしまうプロパティ)の代わりに、
// 記憶しておいたインデックスから「本当にユーザーが選んでいた行」を直接取得します
DataGridViewRow targetRow = dataGridView1.Rows[_lastSelectedRowIndex];
// 1. 完全に空っぽの「新規入力用の最下行」である場合
// 逃げる前のインデックスを捕まえているので、ボタンクリックでもここを通過します。
if (targetRow.IsNewRow)
{
MessageBox.Show("【ステータス】未入力の新規行です(左端に * がある行です)。");
return;
}
// 2. データバインド環境の場合
if (dataGridView1.DataSource != null)
{
if (targetRow.DataBoundItem is DataRowView dataRowView)
{
if (dataRowView.IsNew)
{
MessageBox.Show("【ステータス】現在新しく入力している最中(未確定)の新規行です!");
}
else
{
MessageBox.Show("【ステータス】データベース等から読み込まれた、既存の確定データ行です。");
}
return;
}
}
else
{
// 3. 非バインド環境の場合
if (dataGridView1.NewRowIndex != -1 && targetRow.Index == dataGridView1.NewRowIndex - 1)
{
MessageBox.Show("【ステータス】現在新しく入力している最中(未確定)の新規行です!");
return;
}
}
MessageBox.Show("【ステータス】データベース等から読み込まれた、既存の確定データ行です。");
}
}
}

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

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

コードの解説と仕組み

この実装によって、DataGridView特有の2つの罠をどのように回避しているのか解説します。

1. ボタンクリックによる「行の自動引き戻し」を無効化する

空の新規行を選択した状態でボタンをクリックすると、フォーカスがボタンに移った瞬間にグリッド内の選択位置が勝手に上の行(確定行)へ戻されてしまいます。

サンプルコードでは、SelectionChanged イベントの中で if (dataGridView1.ContainsFocus) という条件式を挟んでいます。

ユーザーがグリッドを操作している間は、この条件が true になるため、最新の行番号が _lastSelectedRowIndex に記憶され続けます。その後、ボタンが押されてフォーカスが外れた瞬間にも行移動(自動引き戻し)が走ってイベントが発生しますが、その時点ではすでにフォーカスがボタン側にあるため条件式が false となり、上書きされずに直前の正しい行番号が維持されます。

ボタンの処理内では、この記憶されたインデックス(targetRow)を狙い撃ちして判定するため、フォーカス移動に邪魔されることがありません。

2. 文字入力を始めた後の「未確定状態」を見破る

最下行に文字を入れると IsNewRow はすぐ false になりますが、別の行に移るまではデータソース側に正式なレコードとしてコミット(確定)されていません。この期間を見破るアプローチは環境によって異なります。

  • データバインド環境(DataTableなど) 行の実体である DataRowViewIsNew プロパティを参照します。このプロパティは、編集が完全に確定してコミットされるまでの間、true を返し続けます。
  • 非バインド環境(直接行を追加) 文字を入れた行の真下に新しい空行(NewRowIndex)ができる仕様を利用します。確定前の入力中行のインデックスは、必ず NewRowIndex - 1 になるため、この引き算の整合性をチェックすることで特定が可能です。

まとめ

今回は、DataGridView で新規行(未確定行)かどうかを文字入力中やボタンクリック時でも正確に調べる実装方法について解説しました。

標準の IsNewRow だけで制御しようとすると、入力開始によるプロパティの自動変更や、フォーカス喪失時のカレント行の巻き戻しといった仕様に阻まれますが、位置の事前記憶とデータソース側のステータスを組み合わせることで完全にコントロールできます。

  • SelectionChangedContainsFocus を使い、フォーカスが外れる直前の行番号を保持する
  • ボタンクリック時は、CurrentRow ではなく保持した行オブジェクトに対して判定を行う
  • 入力中の未確定状態は、DataRowView.IsNewNewRowIndex - 1 を利用して検知する

一括保存ボタンが押された際の「新規追加された行だけを対象にした入力チェック(バリデーション)」など、実務の業務システムで堅牢なロジックを構築する際の参考になりましたら幸いです。


💬 コメント