0
votes

In a C# WinForms project I am populating a DGV from a DataTable. When a user clicks on the cell of one of the columns I need to populate a ComboBox and open it on one click.

However the CBO will only open when the cell in question loses focus (click somewhere else on the form) and then gets focus back (click in that cell again) - and only if the CBO's down arrow is clicked, not if the CBO's text is clicked. I also need the CBO to open when the CBO's text is clicked.

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 is set to EditOnEnter and DataGrieViewSelectionMode is set to CellSelect.

The two lines at the end are from the SO Question, "DataGridViewComboBoxColumn - Have to click cell twice to display combo box"

I'm not sure what else to try...

1
I do not understand the if statement, especially the second part with exclamation mark. I thought that following block of code should be executed when the column is DataGridViewComboBoxCell type.Tomas Paul
@TomášPaul, the if statement simply prevents the code from following code from executing if a CBO already exists in the clicked cell - I still want the CBO to open on subsequent clicks after it has been populated, I just don't want/need to populate it every time the user clicks on it.marky

1 Answers

0
votes

If you consider using CellBeginEdit event to solve you problem then you can use the next approach:

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://stackguides.com/questions/5114668/why-is-my-bound-datagridview-throwing-an-operation-not-valid-because-it-results
            //   - https://stackguides.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;
                }));
        }
    }
}

Here is explanation of this approach:

  • if CellBeginEdit event occurs on the cell of type TextBox we cancel this event and then:
    • change type of current cell to ComboBox;
    • launch CellBeginEdit event for current ComboBox cell;
    • automatically drop down current ComboBox cell;
  • if CellBeginEdit event occurs on the cell of type ComboBox we simply drop down it;
  • we use BeginInvoke to change type of the current cell and then begin editing because:
    • there is a known problem with DataGridView (1, 2) that forces us using BeginInvoke:
    • the type of the cell is changed when event CellBeginEdit completes.

Approach with CellBeginEdit also has the next benefit: it changes cell type either user navigates to the cell by using mouse or keyboard.


Another approach is to use CellStateChanged event:

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;
                    }));
            }
        }
    }
}