【问题标题】:ComboBox added programmatically to DataGridView cell not expanding on cell click以编程方式添加到 DataGridView 单元格的 ComboBox 未在单元格单击时展开
【发布时间】:2020-07-23 04:58:40
【问题描述】:

我在 C# WinForms 项目中有一个 DataGridView,在该项目中,当用户单击某些 DGV 单元格时,单元格将更改为 DataGridViewComboBoxCell,并且 ComboBox 会填充一些值供用户选择。这是 DataGridView_Click 事件的表单代码:

private void dgvCategories_Click(Object sender, DataGridViewCellEventArgs e)
{
    if (e.ColumnIndex == 5 && !(dgvCategories.Rows[e.RowIndex].Cells[e.ColumnIndex].GetType().Name == "DataGridViewComboBoxCell"))
    {
        // Bind combobox to dgv and than bind new values datasource to combobox
        DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();

        // Get fields to build New Value query
        List<string> lsNewValuesResult = new List<string>();
        string strCategory = dtCategories.Rows[e.RowIndex][1].ToString();
        string strCompanyName = cboSelectCompany.Text;
        string strQueryGetNewValuesValidationInfo = "SELECT validationdb, validationtable, validationfield, validationfield2, validationvalue2" +
                                                " FROM masterfiles.categories" +
                                                " WHERE category = @category";
                                                //" WHERE category = '" + strCategory + "'";

        // Pass validation info query to db and return list of New Values
        db getListOfNewValues = new db();
        lsNewValuesResult = getListOfNewValues.GetNewValuesList(strQueryGetNewValuesValidationInfo, strCategory, strCompanyName);

        //Populate the combobox with the list of New Values
        foreach (string strListItem in lsNewValuesResult)
        {
            cboNewValueList.Items.Add(strListItem);
        }

        // 
        dgvCategories[e.ColumnIndex, e.RowIndex] = cboNewValueList;

    }
}

这是填充 ComboBox 的 db 类中的代码(出于此问题的目的,这可能没有必要包括在内,但为了完整起见,我将其包括在内,以防相关):

public List<string> GetNewValuesList(string strValidationInfoQuery, string strCategory, string strCompanyName)
{
    List<string> lsValidationInfo = new List<string>();
    List<string> lsNewValuesList = new List<string>();

    using (NpgsqlConnection conn = new NpgsqlConnection(connString))
    using (NpgsqlCommand cmd = new NpgsqlCommand(strValidationInfoQuery, conn))
    {
        cmd.Parameters.AddWithValue("category", strCategory);

        conn.Open();

        using (NpgsqlDataReader reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                int intReaderIndex;
                for (intReaderIndex = 0; intReaderIndex <= reader.FieldCount - 1; intReaderIndex++)
                {

                    // reader indexes 3 & 4 correspond to categories.validationfield2 and validationvalue2, which can be null
                    if (string.IsNullOrEmpty(reader[intReaderIndex].ToString()))
                    {
                        lsValidationInfo.Add("");
                    }
                    else
                    {
                        lsValidationInfo.Add(reader.GetString(intReaderIndex));
                    }
                    //Console.WriteLine("reader index " + intReaderIndex + ": " + reader.GetString(intReaderIndex));
                }
            }
        }
    }

    string strValidationDb = lsValidationInfo[0];
    string strValidationTable = lsValidationInfo[1];
    string strValidationField = lsValidationInfo[2];
    string strValidationField2 = lsValidationInfo[3];
    string strValidationValue2 = lsValidationInfo[4];

    string strQueryGetNewValues = "SELECT DISTINCT " + strValidationField +
                        " FROM " + strValidationDb + "." + strValidationTable +
                        " WHERE company_id = (SELECT id FROM company WHERE name = '" + strCompanyName + "')";

    if (!string.IsNullOrEmpty(strValidationField2) && !string.IsNullOrEmpty(strValidationValue2)) strQueryGetNewValues += " AND " + strValidationField2 + " = '" + strValidationValue2 + "'";

    strQueryGetNewValues += " ORDER BY " + strValidationField;

    using (NpgsqlConnection conn = new NpgsqlConnection(connString))
    using (NpgsqlCommand cmd = new NpgsqlCommand(strQueryGetNewValues, conn))
    {
        conn.Open();

        using (NpgsqlDataReader reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                int intReaderIndex;
                for (intReaderIndex = 0; intReaderIndex <= reader.FieldCount - 1; intReaderIndex++)
                {
                    // reader indexes 3 & 4 correspond to categories.validationfield2 and validationvalue2, which can be null
                    if (string.IsNullOrEmpty(reader[intReaderIndex].ToString()))
                    {
                        lsNewValuesList.Add("");
                    }
                    else
                    {
                        lsNewValuesList.Add(reader.GetString(intReaderIndex));
                    }
                    Console.WriteLine("reader index " + intReaderIndex + ": " + reader.GetString(intReaderIndex));
                }
            }
        }
    }

    return lsNewValuesList;
}

组合框正在填充,因为我可以在_Click 方法中访问lsNewValuesResult 中的项目。 DGV 编辑模式设置为EditOnEnter。我试过EditOnKeystroke,但这并没有导致组合框在鼠标点击时展开。

这是单击单元格并填充 CBO 并将其添加到 DGV 单元格时组合框的外观:

那是在我点击了两个单元格之后。

[已解决]

请参阅下面的答案。

不幸的是解决了这个revealed a new issue

【问题讨论】:

  • DataGridViewComboBoxColumn 添加到列集合中。
  • @RezaAghaei,对不起,我在哪里可以做到这一点?
  • @RezaAghaei,我找到了在哪里执行此操作,但这在我的情况下不起作用,因为这样做只会添加一个新列,而不是在填充 DGV 后替换现有列。
  • 我过去使用的一个技巧是动态创建标准ComboBox 控件并将其浮动在DataGridView 上。 DataGridViewComboBoxCell 在过去有点不稳定,不确定是否仍然如此。
  • @marky 您需要设置列的DataPropertyName 属性。例如看看this post

标签: c# winforms datagridview datagridviewcomboboxcell


【解决方案1】:

您可以考虑以下有关 DataGridView 的事实:

  • 如果您将AutoGenerateColumns 设置为false,则需要手动将列添加到Columns 集合中。

  • 如果您将AutoGenerateColumns 设置为true,则当您将数据分配给DataSource 时,控件会自动为数据源生成列。在这种情况下,控件在数据源的列列表中查找,如果在控件的Columns 集合中没有与数据源的列名相同DataPropertyName 的列,它将添加一列到Columns收藏。

  • datagridviews 的列的DataPropertyName 决定了数据源的绑定列。

  • 您通常希望将DataGridViewXXXXColumn 添加到列集合中,而不是为单元格使用DataGridViewXXXXCell

  • 如果你将EditMode设置为EditOnEnter,那么如果你点击下拉按钮,一键就够了。如果单击单元格内容,则需要单击两次。

  • 如果您希望即使单击单元格内容也可以单击,请查看this post。 (注:这个例子我没用过,有点烦。)

  • 您可以将DisplayStyle 设置为Nothing,然后它将列显示为组合框,只是处于编辑模式。

使用 DataGridViewComboBoxColumn 的基本示例

我想您将在 DataGridView 中显示具有 (Id, Name, Price, CategoryId) 的产品列表,并且 CategoryId 应该来自具有 (Id, Name) 的类别列表,您将将 CategoryId 显示为 ComboBox。

其实这是DataGridViewComboBoxColumn的基本经典例子:

private void Form1_Load(object sender, EventArgs e) {
    var categories = GetCategories();
    var products = GetProducts();
    var idColumn = new DataGridViewTextBoxColumn() {
      Name = "Id", HeaderText = "Id", DataPropertyName = "Id"
    };
    var nameColumn = new DataGridViewTextBoxColumn() {
      Name = "Name", HeaderText = "Name", DataPropertyName = "Name"
    };
    var priceColumn = new DataGridViewTextBoxColumn() {
      Name = "Price", HeaderText = "Price", DataPropertyName = "Price"
    };
    var categoryIdColumn = new DataGridViewComboBoxColumn() {
      Name = "CategoryId", HeaderText = "Category Id", DataPropertyName = "CategoryId",
      DataSource = categories, DisplayMember = "Name", ValueMember = "Id",
      DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing
    };
    dataGridView1.Columns.AddRange(idColumn, nameColumn, priceColumn, categoryIdColumn);
    dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
    dataGridView1.AutoGenerateColumns = false;
    dataGridView1.DataSource = products;
}
public DataTable GetProducts() {
    var products = new DataTable();
    products.Columns.Add("Id", typeof(int));
    products.Columns.Add("Name", typeof(string));
    products.Columns.Add("Price", typeof(int));
    products.Columns.Add("CategoryId", typeof(int));
    products.Rows.Add(1, "Product 1", 100, 1);
    products.Rows.Add(2, "Product 2", 200, 2);
    return products;
}
public DataTable GetCategories() {
    var categories = new DataTable();
    categories.Columns.Add("Id", typeof(int));
    categories.Columns.Add("Name", typeof(string));
    categories.Rows.Add(1, "Category 1");
    categories.Rows.Add(2, "Category 2");
    return categories;
}

了解详情

要了解有关DataGridView 的更多信息,请查看DataGridView Control (Windows Forms)。它包含一些文档的链接和有用的 How To 文章,包括:

【讨论】:

    【解决方案2】:

    您是否遇到了由于某种原因未显示的错误?

    如果我使用您的代码,DataGridViewComboBoxCell 似乎填充了值,但我收到 DataGridViewComboBoxCell value is not valid 运行时错误。

    这个测试代码对我来说很好用:

    private void dgvCategories_CellContentClick(object sender, DataGridViewCellEventArgs e)
    {
        DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();
    
        List<string> lsNewValuesResult = new List<string>();
        lsNewValuesResult.Add("Value1");
        lsNewValuesResult.Add("Value2");
        lsNewValuesResult.Add("Value3");
    
        foreach (string strListItem in lsNewValuesResult)
        {
            cboNewValueList.Items.Add(strListItem);
        }
    
        dgvCategories[e.ColumnIndex, e.RowIndex] = cboNewValueList;
    
        // Added setting of initial value
        cboNewValueList.Value = cboNewValueList.Items[0];   
    }
    

    因此,在将DataGridViewComboBoxCell 添加到DataGridView 之后,不妨尝试设置它的初始值。

    【讨论】:

      【解决方案3】:

      如果我正确识别您的问题,在我的测试应用程序中添加一个DataGridView whit 6 列,EditMode = EditOnEnter (其他人需要点击三下才能打开下拉菜单,据我所知)并处理CellStateChanged envent。

      private void dgvCategories_CellStateChanged(object sender, DataGridViewCellStateChangedEventArgs e)
      {
          if (e.StateChanged == DataGridViewElementStates.Selected)
          {
              DataGridViewCell cell = e.Cell;
              int columnIndex = cell.ColumnIndex;
              int rowIndex = cell.RowIndex;
              //---IF CONDITIONS--
              //columnIndex == 5
              //          Only cells in Columns[5]
              //cell.Selected
              //          Because this event raised two time, first for last selected cell and once again
              //          for currently selected cell and we need only currently selected cell.
              //cell.EditType.Name != "DataGridViewComboBoxEditingControl"
              //          If this cell "CellStateChanged" raised for second time, only other cell types allowed
              //          to edit, otherwise the current cell lost last selected item.
              if (columnIndex == 5 && cell.Selected && cell.EditType.Name != "DataGridViewComboBoxEditingControl")
              {
                  DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();
      
                  //Add items to DataGridViewComboBoxCell for test, replace it with yours.
                  for (int i = 0; i < 10; i++)
                      cboNewValueList.Items.Add($"Item {i}");
      
                  dgvCategories[columnIndex, rowIndex] = cboNewValueList;
              }
          }
      }
      

      注意:用户必须在单元格中单击两次才能打开下拉菜单。

      编辑一个: Reza Aghaei 建议单击单元格:

      private void dgvCategories_CellClick(object sender, DataGridViewCellEventArgs e)
      {
          DataGridViewComboBoxEditingControl editingControl = dgvCategories.EditingControl as DataGridViewComboBoxEditingControl;
          if (editingControl != null)
              editingControl.DroppedDown = true;
      }
      

      【讨论】:

      • 如果你将EditMode设置为EditOnEnter,那么如果你点击下拉按钮,一键就够了。如果单击单元格内容,则需要单击两次。如果您希望即使单击单元格内容也可以单击,请查看this post
      【解决方案4】:

      我即将公开承认我很愚蠢:

      出于该项目所需的设计和功能原因,我手动设置 DGV 列的宽度和名称,并且我还需要第 2 到第 4 列 ReadOnly = true。嗯,我不小心把第5列——这个问题要问的那一列也设置成了ReadOnly = true

      感谢大家尝试回答。这只是为了提醒我们,如此简单的事情如何导致看似大问题并且如此容易被忽视!

      【讨论】:

      • 可见minimal reproducible example的重要性。它可以帮助您在干净的环境中以最小的完整示例重现问题。你无法重现的问题,你无法解决。然后,在使用最少的代码在干净的环境中重现问题后,您很可能可以自己解决问题。如果您无法解决它,那么它可以帮助其他用户尝试解决您面临的完全相同的问题,而不是不同的问题。
      • @RezaAghaei。非常真实。我会记住的!
      • 回到使用组合框编辑单元格的主题,正确的方法是使用DataGridViewComboBoxColumn,而不是创建DataGridViewComboBoxCell。这是要走的路。
      • 这是简单的事情,只在我试图提供帮助的 MSDN 论坛中提到这一点。
      • @Reza Aghaei,我假设使用DataGridViewComboBoxColumn 会将整个单元格列更改为组合框。如果是这种情况,那不是我需要在这个用例中工作的方式。我只希望用户单击的单元格更改为ComboBox
      【解决方案5】:

      您可能需要关闭AutoGenerateColumns

      此外,似乎需要单击三下才能弹出下拉菜单。

          public Form1()
          {
              InitializeComponent();
              dataGridView1.AutoGenerateColumns = false;
              dataGridView1.DataSource = GetDataSource();
              DataGridViewComboBoxColumn dgvcbc = new DataGridViewComboBoxColumn();
              dgvcbc.Items.Add("R0C0");
              dgvcbc.Items.Add("R1C0");
              dgvcbc.Items.Add("R2C0");
              dgvcbc.Items.Add("R3C0");
              dgvcbc.DataPropertyName = "Col0";
              dataGridView1.Columns.Add(dgvcbc);
          }
      
          DataTable GetDataSource()
          {
              var dtb = new DataTable();
              dtb.Columns.Add("Col0", typeof(string));
              dtb.Columns.Add("Col1", typeof(string));
              dtb.Columns.Add("Col2", typeof(string));
              dtb.Columns.Add("Col3", typeof(string));
              dtb.Columns.Add("Col4", typeof(string));
              dtb.Rows.Add("R0C0", "R0C1", "R0C2", "R0C3", "R0C4");
              dtb.Rows.Add("R1C0", "R1C1", "R1C2", "R1C3", "R1C4");
              dtb.Rows.Add("R2C0", "R2C1", "R2C2", "R2C3", "R2C4");
              dtb.Rows.Add("R3C0", "R3C1", "R3C2", "R3C3", "R3C4");
              return dtb;
          }
      

      【讨论】:

      • 我在frmMain.Designer.cs 中尝试了AutoGenerateColumns 并且DGV 根本没有填充,所以我在dgvCategories_Click 事件中尝试了它,在开始时关闭它并在结束时打开它事件代码和 CBO 甚至都没有出现。另外,我可以多次单击单元格,但 CBO 永远不会打开 - 它只是在第一次点击时创建并显示顶部项目,之后没有任何反应 - 它永远不会打开。
      • 当您将AutoGenerateColumns 设置为false 时,您需要指定将显示哪些列。这就是我示例中的dataGridView1.Columns.Add(dgvcbc) 行的作用。如果您想拥有一些隐藏的列,此技术很有用。
      猜你喜欢
      • 1970-01-01
      • 2015-04-27
      • 2014-10-16
      • 2020-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-21
      • 1970-01-01
      相关资源
      最近更新 更多