如何在 DataGridView 控件中实现级联组合框?
Posted
技术标签:
【中文标题】如何在 DataGridView 控件中实现级联组合框?【英文标题】:How to implement cascading ComboBoxes in DataGridView control? 【发布时间】:2014-01-07 20:35:41 【问题描述】:我需要在几个DataGridView
s 中实现级联ComboBoxes
。作为概念证明,我将下面的代码放在一起。 3 列(Customer、Country、City) 选择Country 时,City 应填充,但它不起作用。
有没有更好的方法来实现这一点并解决我做错的事情?
public partial class Form1 : Form
private List<Customer> customers;
private List<Country> countries;
private List<City> cities;
private ComboBox cboCountry;
private ComboBox cboCity;
public Form1()
InitializeComponent();
countries = GetCountries();
customers = GetCustomers();
SetupDataGridView();
private List<Customer> GetCustomers()
var customerList = new List<Customer>
new Customer Id=1,Name = "Jo",Surname = "Smith",
new Customer Id=2,Name = "Mary",Surname = "Glog",
new Customer Id=3,Name = "Mark",Surname = "Bloggs"
;
return customerList;
private List<Country> GetCountries()
var countryList = new List<Country>
new Country Id=1,Name = "England",
new Country Id=2,Name = "Spain",
new Country Id=3,Name = "Germany"
;
return countryList;
private List<City> GetCities(string countryName)
var cityList = new List<City>();
if (countryName == "England") cityList.Add(new City Id = 1, Name = "London" );
if (countryName == "Spain") cityList.Add(new City Id = 2, Name = "Madrid" );
if (countryName == "Germany") cityList.Add(new City Id = 3, Name = "Berlin" );
return cityList;
private void SetupDataGridView()
dataGridView1.CellLeave += dataGridView1_CellLeave;
dataGridView1.EditingControlShowing += dataGridView1_EditingControlShowing;
DataGridViewTextBoxColumn colCustomer = new DataGridViewTextBoxColumn();
colCustomer.Name = "colCustomer";
colCustomer.HeaderText = "CustomerName";
DataGridViewComboBoxColumn colCountry = new DataGridViewComboBoxColumn();
colCountry.Name = "colCountry";
colCountry.HeaderText = "Country";
DataGridViewComboBoxColumn colCity = new DataGridViewComboBoxColumn();
colCity.Name = "colCity";
colCity.HeaderText = "City";
dataGridView1.Columns.Add(colCustomer);
dataGridView1.Columns.Add(colCountry);
dataGridView1.Columns.Add(colCity);
//Databind gridview columns
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCountry"]).DisplayMember = "Name";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCountry"]).ValueMember = "Id";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCountry"]).DataSource = countries;
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCity"]).DisplayMember = "Name";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCity"]).ValueMember = "Id";
((DataGridViewComboBoxColumn)dataGridView1.Columns["colCity"]).DataSource = cities;
foreach (Customer cust in customers)
dataGridView1.Rows.Add(cust.Name + " " + cust.Surname);
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
//register a event to filter displaying value of items column.
if (dataGridView1.CurrentRow != null && dataGridView1.CurrentCell.ColumnIndex == 2)
cboCity = e.Control as ComboBox;
if (cboCity != null)
cboCity.DropDown += cboCity_DropDown;
//Register SelectedValueChanged event and reset item comboBox to default if category changes
if (dataGridView1.CurrentRow != null && dataGridView1.CurrentCell.ColumnIndex == 1)
cboCountry = e.Control as ComboBox;
if (cboCountry != null)
cboCountry.SelectedValueChanged += cboCountry_SelectedValueChanged;
void cboCountry_SelectedValueChanged(object sender, EventArgs e)
//If category value changed then reset item to default.
dataGridView1.CurrentRow.Cells[2].Value = 0;
void cboCity_DropDown(object sender, EventArgs e)
string countryName = dataGridView1.CurrentRow.Cells[1].Value.ToString();
List<City> cities = new List<City>();
cities = GetCities(countryName);
cboCity.DataSource = cities;
cboCity.DisplayMember = "Name";
cboCity.ValueMember = "Id";
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
if (cboCity != null) cboCity.DropDown -= cboCity_DropDown;
if (cboCountry != null)
cboCountry.SelectedValueChanged -= cboCountry_SelectedValueChanged;
public class Country
public int Id get; set;
public string Name get; set;
public class City
public int Id get; set;
public string Name get; set;
public class Customer
public int Id get; set;
public string Name get; set;
public string Surname get; set;
【问题讨论】:
你的代码不是太小,你打算做的也不是直截了当的。在任何情况下,请注意您将 ValueMember 设置为“Id”(因此 Value 属性返回“Id”),但随后您检查国家名称,因此根本不会发生城市人口。只需将所有 ValueMember 更改为“Name”,城市人口应该可以工作(但恐怕这还不是全部)。 @varocarbas 感谢您抽出宝贵的时间。很难平衡要放多少代码,我也很快把一些东西放在一起,因为我不想问“你如何在没有任何代码的情况下做到这一点!”。我知道并不简单,正在寻找样本但找不到任何实际工作的样本。我收到错误“datagridcomboboxcell.value 无效”。你知道我可以下载并查看它是如何工作的任何示例吗? datagridview 的问题在于它是一个相当复杂的控件,有很多事件被系统地调用。组合框类型的单元格接受一种非常特定的格式。您提到的错误表明在某些时候缺少正确的格式。这很难跟踪。我建议你一步一步做事,并确认每个中间步骤都很好。如果你没有得到任何帮助,我可能会在一段时间内编写一些小代码。 DataGridView Cascading/Dependent ComboBox Columns 【参考方案1】:我希望我能用几行代码轻松地为您提供一个编码解决方案,但我可能不得不发布整个 Visual Studio 项目以用代码进行演示。
这里的想法是,您永远不应该尝试通过 Controls 的事件来控制这种情况。相反,您应该以使用 Windows 窗体的数据绑定机制为目标。通过将控件绑定到能够让 UI 知道其状态何时发生变化的数据源,您只需修改底层数据,UI 就会相应地更新自身。
您需要设置通常称为 ViewModel 的东西来保存所涉及的各种控件的状态,并在此范围内处理任何业务逻辑(例如根据国家/地区设置城市列表) ViewModel 对象对设置属性的反应。
我邀请您搜索有关数据绑定以及参与其中的各种 .NET 接口的信息。第一个肯定是 INotifyPropertyChanged,您的 ViewModel 需要实现它以在其状态更改时触发 UI 中的更改。
明智地使用 BindingSource 组件也将有助于您的工作,例如用所需的值填充各种 ComboBox。
熟悉 Windows 窗体的数据绑定,您在处理此类场景时的痛苦将大大减少。
就像我说的那样,我希望我能用几行代码来演示这一点,我希望我所写的内容能为您指明正确的方向。
干杯
【讨论】:
通用语句的问题在于它们很少有帮助。这是一个复杂的控制,特别是对 OP 的条件(组合框单元格/列)很挑剔。您能否提供更直接适用的帮助? 没有。就像我说的那样,我认为在 SO 的背景下不可能正确解决他的问题。我可以在 github 上提供一个完整的解决方案,但我没有时间。如果你愿意,请做我的客人。 -1 因为一个疯狂的(懦夫)白痴现在对我的答案进行了 -1 的回答(并且大概支持了你的回答)。我很少投反对票,但您的回答显然没有解决这里的问题(恕我直言),因此它不是投票最多的答案。 @varocarbas 我不知道你在说什么,但如果你只是因为有些人反对你的回答而反对我的回答,那么我认为你有个人问题需要解决。请问从现在开始你完全不理我和我的回答,我会为你做同样的事情吗? 我不理你,别担心。这是一次友好的交流(永远-1 不说)。为了理解人们,请注意一下这些词:我确实说过因为新情况(你的答案是投票最多的一个)我认为我不正确;自己获得 -1 或与 -1ed 答案完全无关的事实并不重要。【参考方案2】:正如上面 cmets 中所解释的,DataGridViewComboBox
相关问题可能会变得棘手(您基本上是在一个已经很复杂的控件中添加不同的控件);您的目标确实使这种配置达到了极限。 DataGridView
是一种旨在简化中等复杂性数据相关问题管理的控件;您可以通过其最具定义性的功能(例如,基于文本框的单元格、在单元格验证后触发的事件等)获得最佳性能。因此,只要您不将其性能发挥到极限,包括组合框(或复选框或等效项)单元格就可以了。为了获得您想要的最佳结果(协调不同的组合框),我建议您不要依赖 DataGridView
控件(或者至少不依赖于组合框协调部分),只要实现有问题,最终结果并不像它所能得到的那样可靠,并且在任何情况下,整体结构都比独立于 DGV 的方法(即单独的ComboBox
控制)更加严格。
无论如何,我对这个实现感到好奇(主要是在我的初步测试中看到不少问题之后)并决定编写这段代码来回答您的问题。
private void Form1_Load(object sender, EventArgs e)
dataGridView1.EditingControlShowing +=new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
DataGridViewComboBoxColumn curCol1 = new DataGridViewComboBoxColumn();
List<string> source1 = new List<string>() "val1", "val2", "val3" ;
curCol1.DataSource = source1;
DataGridViewComboBoxColumn curCol2 = new DataGridViewComboBoxColumn();
dataGridView1.Columns.Add(curCol1);
dataGridView1.Columns.Add(curCol2);
for (int i = 0; i <= 5; i++)
dataGridView1.Rows.Add();
dataGridView1[0, i].Value = source1[0];
changeSourceCol2((string)dataGridView1[0, i].Value, (DataGridViewComboBoxCell)dataGridView1[1, i]);
private void changeSourceCol2(string col1Val, DataGridViewComboBoxCell cellToChange)
if (col1Val != null)
List<string> source2 = new List<string>() col1Val + "1", col1Val + "2", col1Val + "3" ;
cellToChange.DataSource = source2;
cellToChange.Value = source2[0];
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
if (dataGridView1.CurrentRow != null)
ComboBox col1Combo = e.Control as ComboBox;
if (col1Combo != null)
if (dataGridView1.CurrentCell.ColumnIndex == 0)
col1Combo.SelectedIndexChanged += col1Combo_SelectedIndexChanged;
private void col1Combo_SelectedIndexChanged(object sender, EventArgs e)
if (dataGridView1.CurrentCell.ColumnIndex == 0)
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
changeSourceCol2(dataGridView1.CurrentCell.Value.ToString(), (DataGridViewComboBoxCell)dataGridView1[1, dataGridView1.CurrentCell.RowIndex]);
此代码可以正常工作,但有一个限制:当您更改第一个组合框的索引时,该值不会立即提交(因此无法更新第二个组合框)。在做了一些测试之后,我确认了建议的配置(即,在填充第二个组合框源之前只写dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
提供了最佳性能)。尽管如此,请注意此代码在这方面并不完美:它从第二个选择开始工作(每次在第一个选择中选择新项目时自动更新第二个组合框);不确定其确切原因,但如前所述,我尝试过的任何其他替代方案的性能都更差。我在这方面没有做太多工作,因为我前面提到的 cmets(实际上,这样做甚至不推荐)并且因为感觉你必须做部分工作.....
【讨论】:
以上是关于如何在 DataGridView 控件中实现级联组合框?的主要内容,如果未能解决你的问题,请参考以下文章
winform窗体——DataGridView控件及通过此控件中实现增删改查
C# Winform DataGridView中实现二维表头
关于Winform下DataGridView中实现checkbox全选反选同步列表项的处理