DataGridView:コンボボックス列を連動させる方法(サンプルあり)

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

DataGridView で「1列目の『分類』で『家電』を選んだら、2列目の『商品名』コンボボックスには家電のリストだけを表示したい」というように、選択肢を動的に連動させたいケースは業務システムで非常によくあります。

しかし、DataGridView のコンボボックス列(DataGridViewComboBoxColumn)に対して単純にデータをバインドしてしまうと、すべての行で同じ選択肢が表示されてしまいます。また、行ごとに無理に選択肢を変えようとすると、DataGridView 特有の**DataError 例外(例外ダイアログ)** が多発して画面がクラッシュする原因になります。

今回は、非編集時の描画エラーを完全に防ぎつつ、編集開始時に対象行のデータに合わせてコンボボックスの選択肢を綺麗に絞り込む、実務で最も安定する実装手順について解説します。

コンボボックスの連動と DataError 発生の仕組み

行ごとにコンボボックスの選択肢を変えるアプローチとして「セル個別の DataGridViewComboBoxCell.DataSource を書き換える」という方法が思い浮かびますが、これには注意すべき点があります。

DataGridView は、画面を描画する(編集状態ではない)タイミングで、「セルの値(Value)」が「その列またはセルの DataSource」の中に存在するかどうかをチェックします。 たとえば、1行目に「テレビ(家電)」、2行目に「リンゴ(食品)」が表示されているとき、列やセルの DataSource を「家電のリスト」に絞り込んでしまうと、2行目の「リンゴ」を描画する際に、選択肢の中にリンゴが存在しないため、DataError イベントが発生します。

この事象を回避するための実務的な必勝パターンは以下の通りです。

  1. 非編集時(列全体):すべての行の値を描画できるように、「すべての選択肢(テレビ・冷蔵庫・リンゴ・バナナ)」をあらかじめ列全体の DataSource に含めておく。
  2. 編集開始時(セル単体)EditingControlShowing イベントを利用し、ユーザーがセルに入ってコンボボックスを開いた瞬間に、その行の分類に合わせて「編集用コントロール(ComboBox)の DataSource」だけを動的に絞り込む。

この「表示用は広く、編集用はその場で狭く」という二段階の制御を行うことで、他の行の描画に影響を与えずに、安全な連動コンボボックスが実現できます。

実装コード

以下は、DataTable をデータソースとし、「分類」コンボボックスの選択状態に応じて「商品名」コンボボックスの選択肢をリアルタイムに切り替えるコード例です。

Form1.cs
using System.Data;
namespace DataGridView_ComboLinkage;
public partial class Form1 : Form
{
private DataTable _dataTable = new DataTable();
public Form1()
{
InitializeComponent();
InitializeDataGridView();
}
private void InitializeDataGridView()
{
// 1. グリッドにバインドするメインのデータテーブル
_dataTable.Columns.Add("Category", typeof(string)); // 分類
_dataTable.Columns.Add("Product", typeof(string)); // 商品名
// テスト用の初期データ(家電と食品が混在)
_dataTable.Rows.Add("家電", "テレビ");
_dataTable.Rows.Add("食品", "リンゴ");
// 2. DataGridViewの列を手動で構成
// 【分類列の設定】
var colCategory = new DataGridViewComboBoxColumn();
colCategory.Name = "Category";
colCategory.HeaderText = "分類";
colCategory.DataSource = new string[] { "家電", "食品" };
dataGridView1.Columns.Add(colCategory);
// 【商品名列の設定】
var colProduct = new DataGridViewComboBoxColumn();
colProduct.Name = "Product";
colProduct.HeaderText = "商品名";
// 列全体のDataSourceには、描画エラーを防ぐために「すべての選択肢」を網羅しておきます
colProduct.DataSource = new string[] { "テレビ", "冷蔵庫", "リンゴ", "バナナ" };
dataGridView1.Columns.Add(colProduct);
// データソースの適用
dataGridView1.DataSource = _dataTable;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
// 3. 必要なイベントを購読
dataGridView1.EditingControlShowing += DataGridView1_EditingControlShowing;
dataGridView1.DataError += DataGridView1_DataError;
}
/// <summary>
/// セルが編集状態になり、内部のテキストボックスやコンボボックスが表示されたときに発生
/// </summary>
private void DataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
// 編集用コントロールが ComboBox(ドロップダウン)であるか確認
if (e.Control is ComboBox comboBox)
{
// 現在編集しようとしているセルの列名を取得
string columnName = dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name;
// 商品名列のコンボボックスが開かれたタイミングで、選択肢を絞り込む
if (columnName == "Product")
{
// 現在の行の「分類(Category)」に何が入っているかを取得
string category = dataGridView1.CurrentRow.Cells["Category"].Value?.ToString() ?? "";
// 分類の値に応じて、目の前のコンボボックスコントロールのDataSourceだけを上書きする
if (category == "家電")
{
comboBox.DataSource = new string[] { "テレビ", "冷蔵庫" };
}
else if (category == "食品")
{
comboBox.DataSource = new string[] { "リンゴ", "バナナ" };
}
else
{
// 分類が未選択などの場合は選択肢を空にする
comboBox.DataSource = new string[] { };
}
}
}
}
/// <summary>
/// DataGridViewの内部処理でデータ不整合などの例外が発生したときに呼び出されるイベント
/// </summary>
private void DataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
// 実務における予期せぬ「DataErrorダイアログ」の強制ポップアップを防ぐ防衛コードです。
// 例外をシステム上方にスローしないように設定し、画面のフリーズやクラッシュを未然に防ぎます。
e.ThrowException = false;
}
}

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

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

実行結果

「分類」列のドロップダウンの値に応じて、「商品名」列で選択できるドロップダウンの値が切り替わります。

留意すべき注意点と実装の背景

この連動制御をさらに堅牢にし、実務の運用に耐えうる画面にするためのポイントが2つあります。

分類を「書き換えた」際の古い値のクリア処理

上記のコードによりコンボボックスの連動は行われますが、ユーザーが「一度『家電・テレビ』と入力した行を、後から『食品』に選び直した」というケースにおいて、もう一つのケアが必要になります。

分類を「食品」に変えただけでは、商品名セルにはまだ古い値である「テレビ」が残ったままになります。この状態でセルからフォーカスを外そうとすると、「食品の選択肢(リンゴ・バナナ)の中にテレビは存在しない」ため、今度は編集確定のフェーズで不整合が起きます。

これを防ぐには、分類列の値が変更されたタイミング(CellValueChanged イベントなど)をキャッチし、隣の商品名セルの値を DBNull.Value や空文字("")にプログラム側で強制クリアするロジックを併せて記述しておくのが、業務アプリのUI設計として親切です。

最低限の防衛策としての DataError ハンドリング

コンボボックス列を扱う上で、DataError イベントの空ハンドリング(e.ThrowException = false;)は必須のセーフティネットです。

どれだけデータソースを同期させているつもりでも、データベースから予期せぬ NULL データや、マスタから削除された古い区分値(コード)がグリッドに読み込まれた瞬間、DataGridView は「対応する値がDataSourceにない」として DataError 例外をスローします。

このイベントを購読し、例外のトス(ThrowException)を false で握りつぶしておくコードを1行書いておくだけで、ユーザーの画面に無骨な英語のエラーダイアログが突然ポップアップしてシステムが停止する最悪の事態を確実に回避できます。

まとめ

今回は、DataGridView のコンボボックス列を行ごとに動的に連動させ、かつ DataError を発生させない実装方法について解説しました。

列全体の DataSource には全選択肢をあらかじめ登録して描画エラーを完全に封じ込め、EditingControlShowing イベントでユーザーが操作するコンボボックスのみを狙い撃ちして絞り込むアプローチが、最もシンプルかつ堅牢です。

  • 列の DataSource には、すべての行が表示できるように「全選択肢」を持たせておく
  • EditingControlShowing 内で、選択された行の条件に応じて e.Control(ComboBox)の DataSource を上書きする
  • 万が一のデータ不整合に備え、DataError イベントで例外の発生をガードしておく

この二段階バインドの手法をマスターすることで、マスタ登録画面や伝票入力画面などの複雑な連動入力をスムーズに処理できるようになります。高度なグリッド画面を構築する際の参考になりましたら幸いです。


💬 コメント