如何在 DataGridView 控件中实现级联组合框?

Posted

技术标签:

【中文标题】如何在 DataGridView 控件中实现级联组合框?【英文标题】:How to implement cascading ComboBoxes in DataGridView control? 【发布时间】:2014-01-07 20:35:41 【问题描述】:

我需要在几个DataGridViews 中实现级联ComboBoxes。作为概念证明,我将下面的代码放在一起。 3 列(CustomerCountryCity) 选择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全选反选同步列表项的处理

c# datagridview控件如何修改行高

如何验证 DataGridView 中单元格编辑控件的输入?

如何从 DataGridView 控件底部删除空行?