【问题标题】:ComboBox added programmatically to DataGridView doesn't open until after the cell loses focus and then is clicked on again以编程方式添加到 DataGridView 的 ComboBox 直到单元格失去焦点然后再次单击后才会打开
【发布时间】:2020-07-28 15:50:12
【问题描述】:

在 C# WinForms 项目中,我从 DataTable 填充 DGV。当用户单击其中一列的单元格时,我需要填充 ComboBox单击一次打开它。

但是,只有当相关单元格失去焦点(单击表单上的其他位置)然后重新获得焦点(再次单击该单元格)时,CBO 才会打开 - 并且只有在单击 CBO 的向下箭头时才会打开,如果CBO 的文本被点击。当点击 CBO 的文本时,我还需要打开 CBO。

private void dgvCategories_Click(Object sender, DataGridViewCellEventArgs e)
{
    try
    {
        // Prevent code from executing if user clicks on a cell that already has a CBO
        if (e.ColumnIndex == 5 && !(dgvCategories.Rows[e.RowIndex].Cells[e.ColumnIndex].GetType().Name == "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";

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

            // Create CBO object
            DataGridViewComboBoxCell cboNewValueList = new DataGridViewComboBoxCell();

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

            // Bind the CBO to the DGV
            dgvCategories[e.ColumnIndex, e.RowIndex] = cboNewValueList;

            var editingControl = dgvCategories.EditingControl as DataGridViewComboBoxEditingControl;
            if (editingControl != null) editingControl.DroppedDown = true;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("dgvCategories_Click Exception: " + ex.Message);
    }

}

DataGridViewEditMode 设置为EditOnEnterDataGrieViewSelectionMode 设置为CellSelect

最后的两行来自 SO Question,“DataGridViewComboBoxColumn - Have to click cell twice to display combo box

我不知道还能尝试什么...

【问题讨论】:

  • 我看不懂if的说法,尤其是第二部分有感叹号。我认为当列 is DataGridViewComboBoxCell 类型时应该执行以下代码块。
  • @TomášPaul,if 语句只是阻止代码在单击的单元格中已存在 CBO 时执行以下代码 - 我仍然希望 CBO 在填充后在后续单击时打开,我只是不想/不需要在用户每次点击它时填充它。

标签: c# winforms datagridview datagridviewcomboboxcell


【解决方案1】:

如果您考虑使用CellBeginEdit 事件来解决您的问题,那么您可以使用下一种方法:

private void dgvCategories_CellBeginEdit(Object sender, DataGridViewCellCancelEventArgs e)
{
    if (e.ColumnIndex == 5)
    {
        if (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 = getCboValues();

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

            // Store current cell value into ComboBox cell. It is for convenience.
            cboNewValueList.Value = dgvCategories[e.ColumnIndex, e.RowIndex].Value;

            // Cancel current BeginEdit event because it occurs on the cell of type TextBox.
            // Later we'll launch another BeginEdit event on cell of type ComboBox.
            e.Cancel = true;

            // BeginInvoke is needed because:
            // 1. there is a known problem with DataGridView:
            //   - https://stackoverflow.com/questions/5114668/why-is-my-bound-datagridview-throwing-an-operation-not-valid-because-it-results
            //   - https://stackoverflow.com/questions/26522927/how-to-evade-reentrant-call-to-setcurrentcelladdresscore/26527759#26527759
            // 2. current cell has type TextBox, but we want to change its type to
            //   ComboBox and then begin editing ComboBox.
            BeginInvoke(new Action(
                () =>
                {
                    // Change type of current cell to ComboBox.
                    dgvCategories[e.ColumnIndex, e.RowIndex] = cboNewValueList;
                    // Begin editing of the ComboBox cell.
                    dgvCategories.BeginEdit(true);

                    // Here (after BeginEdit) the type of the cell is ComboBox.
                    // Automatically drop down ComboBox cell.
                    if (dgvCategories.EditingControl != null)
                        ((DataGridViewComboBoxEditingControl)dgvCategories.EditingControl).DroppedDown = true;
                }));
        }
        else
        {
            // If current cell is already of type ComboBox then we simply drop down it.
            BeginInvoke(new Action(
                () =>
                {
                    if (dgvCategories.EditingControl != null)
                        ((DataGridViewComboBoxEditingControl)dgvCategories.EditingControl).DroppedDown = true;
                }));
        }
    }
}

以下是对这种方法的解释:

  • 如果CellBeginEdit 事件发生在TextBox 类型的单元格上,我们取消此事件,然后:
    • 将当前单元格类型更改为ComboBox;
    • 为当前ComboBox 单元启动CellBeginEdit 事件;
    • 自动下拉当前ComboBox单元格;
  • 如果CellBeginEdit 事件发生在ComboBox 类型的单元格上,我们只需将其下拉即可;
  • 我们使用BeginInvoke 更改当前单元格的类型,然后开始编辑,因为:
    • DataGridView12)存在一个已知问题,迫使我们使用BeginInvoke
    • 当事件CellBeginEdit 完成时,单元格的类型会发生变化。

使用CellBeginEdit 的方法还有下一个好处:它可以更改单元格类型,无论是用户使用鼠标还是键盘导航到单元格。


另一种方法是使用CellStateChanged事件:

private void dgvCategories_CellStateChanged(object sender, DataGridViewCellStateChangedEventArgs e)
{
    if (e.StateChanged == DataGridViewElementStates.Selected)
    {
        int col = e.Cell.ColumnIndex;
        int row = e.Cell.RowIndex;

        if (col == 5)
        {
            if (dgvCategories.Rows[row].Cells[col].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 = getCboValues();

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

                // 
                cboNewValueList.Value = dgvCategories[col, row].Value;
                dgvCategories[col, row] = cboNewValueList;

                // To drop down current cell we must call BeginInvoke,
                // because in the CellStateChanged event handler
                // dgvCategories.EditingControl is null.
                BeginInvoke(new Action(
                    () =>
                    {
                        if (dgvCategories.EditingControl != null)
                            ((DataGridViewComboBoxEditingControl) dgvCategories.EditingControl).DroppedDown = true;
                    }));
            }
            else
            {
                // If current cell is already ComboBox we simply drop it down.
                BeginInvoke(new Action(
                    () =>
                    {
                        if (dgvCategories.EditingControl != null)
                            ((DataGridViewComboBoxEditingControl)dgvCategories.EditingControl).DroppedDown = true;
                    }));
            }
        }
    }
}

【讨论】:

  • @marky 是否回答了您的问题,您需要更多详细信息或信息吗?
猜你喜欢
  • 2020-07-23
  • 1970-01-01
  • 1970-01-01
  • 2015-04-27
  • 2014-10-16
  • 1970-01-01
  • 2011-05-25
  • 2020-12-18
  • 1970-01-01
相关资源
最近更新 更多