【问题标题】:How to implement cascading ComboBoxes in DataGridView control?如何在 DataGridView 控件中实现级联组合框?
【发布时间】: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 的问题在于它是一个相当复杂的控件,有很多事件被系统地调用。组合框类型的单元格接受一种非常特定的格式。您提到的错误表明在某些时候缺少正确的格式。这很难跟踪。我建议你一步一步做事,并确认每个中间步骤都很好。如果你没有得到任何帮助,我可能会在一段时间内编写一些小代码。

标签: c# winforms datagridview cascadingdropdown


【解决方案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(实际上,这样做甚至不推荐)并且因为感觉你必须做部分工作.....

【讨论】:

    猜你喜欢
    • 2013-06-22
    • 2017-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-23
    • 2021-12-05
    相关资源
    最近更新 更多