DataGridView:BindingSource での双方向バインディングと反映タイミングの制御(サンプルあり)

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

Windows Forms 開発において、画面の一方に DataGridView で一覧(マスター)を表示し、選択した行のデータをもう一方のテキストボックス群(詳細入力欄)に自動表示・編集させる「マスター・詳細」形式の画面は、業務システムで広く採用されています。

これを実装する際、DataGridView の選択行変更イベント(SelectionChanged)をトリガーにして手動で1セルずつテキストボックスに値を代入したり、逆にテキストボックスの変更をグリッドに書き戻すロジックを自作したりすると、コード量が膨らみバグの原因になります。

BindingSource コンポーネントの「複数コントロール間における選択行の自動同期機能」と「データバインディング」を活用することで、最小限のコードでグリッドと個別の入力欄を双方向に完全連動させることができます。

今回は、選択行の変更に伴う自動同期手順と、実務で必ず直面する「編集内容が即座にグリッドに反映されない現象」への対策について解説します。

複数コントロールが自動で連動する仕組み

BindingSource を使ったマスター・詳細連動の利点は、同一の BindingSource を複数のコントロールで共有する点にあります。

DataGridView と詳細表示用のテキストボックス群に対して、すべて共通の BindingSource をデータソースとして紐付けます。BindingSource は内部で「現在どの行が選択されているか」を示す位置情報を一元管理しているため、DataGridView の行選択が変わると、それを検知した各テキストボックスが自動的に自分自身の中身を選択行のデータに書き換えます。

さらに、テキストボックス側で文字を編集すると、その変更は BindingSource を経由して裏側のデータソースへ書き戻され、DataGridView の表示も同期します。

実装コード

画面に DataGridView(dataGridView1)と、詳細入力欄として3つのテキストボックス(txtCode, txtName, txtPrice)が配置されてます。


Form1.cs
using System.Data;
namespace DataGridView_MasterDetailBinding;
public partial class Form1 : Form
{
private DataTable _productTable = new DataTable();
// データと複数のUIコントロールを仲介する、共通の BindingSource
private BindingSource _productBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
InitializeData();
InitializeDataGridView();
BindDetailControls(); // 詳細入力欄の連動設定を呼び出す
}
private void InitializeData()
{
// テスト用データの準備
_productTable.Columns.Add("Code", typeof(string));
_productTable.Columns.Add("Name", typeof(string));
_productTable.Columns.Add("Price", typeof(int));
_productTable.Rows.Add("P001", "ノートパソコン", 120000);
_productTable.Rows.Add("P002", "USBメモリ", 2500);
_productTable.Rows.Add("P003", "液晶モニター", 35000);
// 詳細欄のテキストボックスが「フォーカスアウト(確定)」した時のイベントを購読
txtName.Validated += DetailTextBox_Validated;
txtPrice.Validated += DetailTextBox_Validated;
}
/// <summary>
/// 詳細欄のテキストボックスの入力が確定した瞬間に呼び出される
/// </summary>
private void DetailTextBox_Validated(object? sender, EventArgs e)
{
// 行の編集を確定する
_productBindingSource.EndEdit();
}
private void InitializeDataGridView()
{
// 階層構造でデータソースを紐付ける(DataTable ➡️ BindingSource ➡️ DataGridView)
_productBindingSource.DataSource = _productTable;
dataGridView1.DataSource = _productBindingSource;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
/// <summary>
/// 各テキストボックスと BindingSource をデータバインディングで結びつける
/// </summary>
private void BindDetailControls()
{
// DataBindings.Add メソッドを使い、コントロールのプロパティと、
// 共通の BindingSource 内にある特定の列名(プロパティ名)をペアリングします。
// 1. 商品コードの連動(編集不可にするため ReadOnly に設定)
txtCode.DataBindings.Add("Text", _productBindingSource, "Code", true);
txtCode.ReadOnly = true;
// 2. 商品名の連動(DataSourceUpdateMode を明示的に指定:後述)
txtName.DataBindings.Add("Text", _productBindingSource, "Name", true, DataSourceUpdateMode.OnValidation);
// 3. 価格の連動
txtPrice.DataBindings.Add("Text", _productBindingSource, "Price", true, DataSourceUpdateMode.OnValidation);
}
}

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

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

実行結果

DataGridView の選択行の値をテキストボックスで編集します。テキストボックスのフォーカスが外れたタイミングで、DataGridView 側にも変更が反映されます。

ポイント:行を変えるまでグリッドに反映されない現象と対策

データバインディングの標準仕様では、詳細欄のテキストボックス(例: 商品名)を編集して Tab キーで次のテキストボックス(例: 価格)へフォーカスを移動させた際、初回のみ即座に DataGridView 側へ値が反映されますが、2回目以降の編集では行自体を切り替えるまでグリッド側の表示が更新されないという挙動を示します。

これは不具合ではなく、BindingSource(および裏側の DataTable)の「トランザクション(編集ロック)機能」による仕様です。

現象が発生するメカニズム

  1. 初回編集時(フォーカスアウト):テキストボックスからフォーカスが外れた瞬間、データが裏のデータソースへ送られます。このとき、BindingSource は対象行の「編集モード(BeginEdit)」を開始します。初回はデータが流入した勢いでグリッドが再描画されますが、同時に該当行に「編集中ロック」がかかります。
  2. 2回目以降の編集時:同じ行のコントロール間でフォーカスを移動させている間、データ自体は裏のデータソースに書き込まれていますが、システム側は「まだ同じ行を編集しているため、確定(コミット)または取消(キャンセル)の指示を待つ」という状態になります。そのため、行全体の編集が本確定するまでは、DataGridView 側の描画更新が保留されます。
  3. 行を切り替えた時:DataGridView の選択行が別の行に移動した瞬間に、BindingSource は自動的に EndEdit()(編集完了) を呼び出します。ここで初めてデータが行単位で本確定するため、保留されていたグリッドの表示が一気に最新状態に更新されます。

EndEdit() によるリアルタイム同期の解決策

この保留仕様を解除し、どのテキストボックスからフォーカスが外れたときでも即座に DataGridView 側へ値を反映させたい場合は、テキストボックスの Validated イベント(入力検証完了時) を利用します。

サンプルコードにある通り、フォーカスアウトが確定したタイミングで明示的に _productBindingSource.EndEdit(); を呼び出すことで、仮保存状態がその都度解除され、行を移動することなく即座に DataGridView へ最新のデータが同期されるようになります。

補足:実務で考慮すべき設定

1. データの書き戻しタイミング(DataSourceUpdateMode

DataBindings.Add の引数で指定している DataSourceUpdateMode は、詳細欄の編集内容をいつデータソースに送るかを制御します。 実務では、入力を終えてフォーカスが外れたタイミングで反映する DataSourceUpdateMode.OnValidation が最も安定しており一般的です(1文字タイピングするごとに反映する OnPropertyChanged もありますが、入力途中の不完全なデータが裏に流れるため数値型などではバリデーションエラーを起こしやすくなります)。

2. 空文字入力によるクラッシュの回避

価格(Price)などの数値型にバインドされているテキストボックスの内容を、ユーザーがバックスペースで全て消して「空文字」のままフォーカスアウトすると、型変換エラーが発生してフォーカス移動がロックされてしまいます。 これを防ぐには、以下のように Binding オブジェクトの NullValue プロパティに初期値を設定しておく手法が有効です。

// 空文字が入力されたら、自動的に「0」に変換してデータソースに書き戻す設定例
Binding priceBinding = new Binding("Text", _productBindingSource, "Price", true, DataSourceUpdateMode.OnValidation);
priceBinding.NullValue = "0";
txtPrice.DataBindings.Add(priceBinding);

まとめ

今回は、DataGridView と個別の入力欄を BindingSource で完全連動させる手順と、描画保留仕様への対策について解説しました。 コントロール間の仲介役として BindingSource を正しく配置し、適切なタイミングで EndEdit() を呼び出すことで、コードの肥大化を防ぎつつ、直感的で堅牢な双方向同期画面を構築できます。

  • 全ての連動コントロールで「同一の BindingSource インスタンス」を共有する
  • テクトボックスの Validated イベントで BindingSource.EndEdit() を呼び出し、グリッドへの即時描画を確定させる
  • データのライフサイクル(編集中・確定)を意識してコントロールの挙動を制御する

マスター・詳細画面を設計する際の実践的なアプローチとして参考になりましたら幸いです。


💬 コメント