WinForms中DataGridView下拉框按外键过滤问题(C#)
实现DataGridViewComboBox列根据当前物料动态筛选价目表
这个需求我之前做过类似的,核心就是抓住用户进入ComboBox单元格编辑状态的时机,根据当前行的物料ID去筛选对应的价目表数据源,替换掉ComboBox的默认选项。下面给你一步步拆解实现方法:
1. 准备好数据源
假设你已经有三个核心DataTable:
materialsTable:存储物料信息,包含MaterialId(主键)和MaterialName(显示用的物料名称)pricelistsTable:存储价目表信息,包含PricelistId(主键)、PricelistName(显示用的价目表名称)、MaterialId(关联物料的外键)mainTable:绑定到DataGridView的主表,包含MaterialId和PricelistId两个字段
2. 初始化DataGridView控件
在窗体加载时,先配置好列的基础属性,包括MaterialId的只读显示(显示物料名称而非ID),以及PricelistId的ComboBox列初始设置:
private void Form1_Load(object sender, EventArgs e) { // 绑定主表到DataGridView dataGridView1.DataSource = mainTable; // 配置MaterialId列:只读,显示物料名称 var materialCol = dataGridView1.Columns["MaterialId"] as DataGridViewTextBoxColumn; materialCol.ReadOnly = true; // 通过CellFormatting事件把ID转换成名称显示 dataGridView1.CellFormatting += DataGridView1_CellFormatting; // 配置PricelistId的ComboBox列 var pricelistCol = dataGridView1.Columns["PricelistId"] as DataGridViewComboBoxColumn; pricelistCol.DisplayMember = "PricelistName"; pricelistCol.ValueMember = "PricelistId"; // 先绑定全部价目表,后续会动态替换 pricelistCol.DataSource = pricelistsTable; } // 处理MaterialId列的显示转换 private void DataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { // 判断当前列是MaterialId列且值不为空 if (e.ColumnIndex == dataGridView1.Columns["MaterialId"].Index && e.Value != null && e.Value != DBNull.Value) { // 根据ID找到对应的物料名称 var targetRow = materialsTable.Select($"MaterialId = {e.Value}").FirstOrDefault(); if (targetRow != null) { e.Value = targetRow["MaterialName"]; e.FormattingApplied = true; } } }
3. 核心:动态筛选ComboBox的数据源
通过EditingControlShowing事件,在用户点击ComboBox单元格进入编辑状态时,获取当前行的物料ID,筛选出对应的价目表并绑定到ComboBox:
private void DataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) { // 只处理PricelistId列的单元格 if (dataGridView1.CurrentCell.ColumnIndex == dataGridView1.Columns["PricelistId"].Index) { var editComboBox = e.Control as ComboBox; if (editComboBox != null) { // 先移除旧的SelectedIndexChanged事件,避免重复触发 editComboBox.SelectedIndexChanged -= EditComboBox_SelectedIndexChanged; // 获取当前行的物料ID var currentMaterialId = dataGridView1.CurrentRow.Cells["MaterialId"].Value; if (currentMaterialId != null && currentMaterialId != DBNull.Value) { // 筛选对应物料的价目表(注意如果MaterialId是字符串,要加单引号:$"MaterialId = '{currentMaterialId}'") DataRow[] filteredRows = pricelistsTable.Select($"MaterialId = {currentMaterialId}"); DataTable filteredPricelist = filteredRows.CopyToDataTable(); // 如果没有匹配的价目表,可以添加一个提示项 if (filteredPricelist.Rows.Count == 0) { filteredPricelist.Rows.Add(-1, "无对应价目表"); } // 绑定筛选后的数据源到ComboBox editComboBox.DataSource = filteredPricelist; editComboBox.DisplayMember = "PricelistName"; editComboBox.ValueMember = "PricelistId"; // 保持当前单元格已有的选中值 if (dataGridView1.CurrentCell.Value != DBNull.Value) { editComboBox.SelectedValue = dataGridView1.CurrentCell.Value; } } // 重新绑定SelectedIndexChanged事件,确保选择后更新单元格值 editComboBox.SelectedIndexChanged += EditComboBox_SelectedIndexChanged; } } } // 处理ComboBox选择后的单元格值更新 private void EditComboBox_SelectedIndexChanged(object sender, EventArgs e) { var comboBox = sender as ComboBox; if (comboBox != null && dataGridView1.CurrentCell != null) { dataGridView1.CurrentCell.Value = comboBox.SelectedValue; } }
注意事项
- 如果你的
MaterialId是字符串类型,筛选语句里要给值加上单引号,避免语法错误(比如$"MaterialId = '{currentMaterialId}'") - 要处理
DBNull.Value的情况,避免空引用异常 - 如果筛选后没有匹配的价目表,建议添加提示项,提升用户体验
内容的提问来源于stack exchange,提问作者Alex




