119

I have a winforms app and want to trigger some code when a checkbox embedded in a DataGridView control is checked / unchecked. Every event I have tried either

  1. Triggers as soon as the CheckBox is clicked but before its checked state changes, or
  2. Triggers only once the CheckBox looses its focus

I can't seem to find event that triggers immediately after the checked state changes.


Edit:

What I am trying to achieve is that when the checked state of a CheckBox in one DataGridView changes, the data in two other DataGridViews changes. Yet all the events I have used, the data in the other grids only changes after the CheckBox in the first DataGridView looses focus.

daniele3004
  • 13,072
  • 12
  • 67
  • 75
PJW
  • 5,197
  • 19
  • 60
  • 74
  • 2
    Did you checked `CurrentCellDirtyStateChanged` Event? – Yograj Gupta Aug 07 '12 at 10:17
  • Still only executes when the user 'leaves' the cell. – PJW Aug 07 '12 at 10:26
  • 1
    Here is the MSDN article on this: http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.currentcelldirtystatechanged(v=vs.80).aspx similar but a little different to Killercam's answer – David Hall Aug 07 '12 at 16:08
  • You might want to consider using a ```ListView``` with ```listView.View = System.Windows.Forms.View.Details; listView.CheckBoxes = true;``` Then it displays as a table with checkbox at the start. There you can easily adress the Checkbox checked event with ```listView.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(methodname);``` – Sam Tigle Apr 27 '21 at 08:19

20 Answers20

132

To handle the DatGridViews CheckedChanged event you must first get the CellContentClick to fire (which does not have the CheckBoxes current state!) then call CommitEdit. This will in turn fire the CellValueChanged event which you can use to do your work. This is an oversight by Microsoft. Do some thing like the following...

private void dataGridViewSites_CellContentClick(object sender, 
    DataGridViewCellEventArgs e)
{
    dataGridViewSites.CommitEdit(DataGridViewDataErrorContexts.Commit);
}

/// <summary>
/// Works with the above.
/// </summary>
private void dataGridViewSites_CellValueChanged(object sender, 
    DataGridViewCellEventArgs e)
{
    UpdateDataGridViewSite();
}

P.S. Check this article https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.currentcelldirtystatechanged(v=vs.110).aspx

starball
  • 20,030
  • 7
  • 43
  • 238
MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • 5
    This is a good solution but does not works if the user clicks several times, an alternative has been posted below http://stackoverflow.com/questions/11843488/datagridview-checkbox-event#15011844 – 56ka Nov 08 '13 at 13:17
  • 1
    I would also highly suggest NOT using this solution for the double click problem. The EndEdit() function needs to be called... find the link from @56ka and click on the article link! – Luke Aug 20 '14 at 15:51
  • 1
    I did not spend long on this solution and if @56ka's solution is a better one, great. However, I am not sure what all the fuss about double clicking a `DataGridViewCheckBox` is. This is not WPF and double clicking the control is not breaking any data binding, it's WinForms. Double clicking may not update the control visually but it does not _break_ anything and in this case perhaps the solution below is the better one. Thanks. – MoonKnight Aug 20 '14 at 16:08
  • 2
    This works perfectly if you add the same code from `CellContentClick` into `CellContentDoubleClick` as well. `CellMouseUp` is will fire even if the cell is selected but checkbox is not clicked - which is not desired behaviour. – torpid prey Sep 02 '18 at 00:15
  • @torpidprey Or just set the `CellContentClick` event handler for `CellContentDoubleClick` instead of copy, to have a single handler fired by those two ones. Your solution fixed double click problem in my winform app. – August Feb 12 '22 at 14:13
99

I found @Killercam's solution to work but was a bit dodgy if the user double clicked too fast. Not sure if other's found that the case either. I found a another solution here.

It uses the datagrid's CellValueChanged and CellMouseUp. Changhong explains that

"The reason for that is OnCellvalueChanged event won’t fire until the DataGridView thinks you have completed editing. This makes senses for a TextBox Column, as OnCellvalueChanged wouldn’t [bother] to fire for each key strike, but it doesn’t [make sense] for a CheckBox."

Here it is in action from his example:

private void myDataGrid_OnCellValueChanged(object sender, DataGridViewCellEventArgs e)
{
    if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
    {
        // Handle checkbox state change here
    }
}

And the code to tell the checkbox it is done editing when it is clicked, instead of waiting till the user leaves the field:

private void myDataGrid_OnCellMouseUp(object sender,DataGridViewCellMouseEventArgs e)
{
    // End of edition on each click on column of checkbox
    if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
    {
        myDataGrid.EndEdit();
    }
}

Edit: A DoubleClick event is treated separate from a MouseUp event. If a DoubleClick event is detected, the application will ignore the first MouseUp event entirely. This logic needs to be added to the CellDoubleClick event in addition to the MouseUp event:

private void myDataGrid_OnCellDoubleClick(object sender,DataGridViewCellEventArgs e)
{
    // End of edition on each click on column of checkbox
    if (e.ColumnIndex == myCheckBoxColumn.Index && e.RowIndex != -1)
    {
        myDataGrid.EndEdit();
    }
}
Force
  • 3,288
  • 1
  • 13
  • 19
jsturtevant
  • 2,560
  • 1
  • 23
  • 23
  • 3
    I ran into the double-click problem noted by the responder, and this one worked much better than the first solution in handling that correctly. – Steve Ferguson Oct 01 '13 at 00:33
  • 1
    I also ran into the double-click problem and this solution fixed it. – Chris C Jul 30 '14 at 16:49
  • Click the 'here' button and check out the article. I had the same issue with the double click. – Luke Aug 20 '14 at 15:52
  • 4
    What if you toggle the toggle with the space bar? – Halfgaar Nov 27 '14 at 11:00
  • I'm working with a databinding, which change in the event handler. Consequently, I had to all `Refresh()` on the grid too, after `EndEdit()`. – Halfgaar Nov 28 '14 at 09:27
  • 2
    To 'fix' the spacebar issue, I set `KeyPreview` to true on the form and when `e.KeyCode == Keys.Space`, set `e.Handled = true`. In other words, I just disabled keyboard editing. – Halfgaar Dec 04 '14 at 08:42
  • Killercam's solution doesn't work at all, but this works perfectly. – Tassisto Jun 30 '16 at 08:38
9

jsturtevants's solution worked great. However, I opted to do the processing in the EndEdit event. I prefer this approach (in my application) because, unlike the CellValueChanged event, the EndEdit event does not fire while you are populating the grid.

Here is my code (part of which is stolen from jsturtevant:

private void gridCategories_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
    if (e.ColumnIndex == gridCategories.Columns["AddCategory"].Index)
    {
        //do some stuff
    }
}



private void gridCategories_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.ColumnIndex == gridCategories.Columns["AddCategory"].Index)
    {
        gridCategories.EndEdit();
    }
}
Mark Ainsworth
  • 811
  • 8
  • 24
  • 3
    Good answer, but it is preferable to use `CellContentClick` instead of `CellMouseUp` because the latter will be called when the user clicks anywhere inside the cell whereas the former is only called when the checkbox is clicked. – Jamie Kitson Jun 06 '17 at 09:50
8

Here is some code:

private void dgvStandingOrder_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    if (dgvStandingOrder.Columns[e.ColumnIndex].Name == "IsSelected" && dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
    {
        bool isChecked = (bool)dgvStandingOrder[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
        if (isChecked == false)
        {
            dgvStandingOrder.Rows[e.RowIndex].Cells["Status"].Value = "";
        }
        dgvStandingOrder.EndEdit();
    }
}

private void dgvStandingOrder_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{

    dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}

private void dgvStandingOrder_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
    {
        dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
    }
}
daniele3004
  • 13,072
  • 12
  • 67
  • 75
Nay Lin Aung
  • 81
  • 1
  • 1
  • 3
    This answer contains the correct answer, that handles both mouse and keyboard interactions, and repeated interactions without leaving the cell. But only the last handler is needed -- calling `CommitEdit` from `CurrentCellDirtyStateChanged` is the whole solution. – Ben Voigt May 21 '18 at 16:38
6

following Killercam'answer, My code

private void dgvProducts_CellContentClick(object sender, DataGridViewCellEventArgs e)
    {
        dgvProducts.CommitEdit(DataGridViewDataErrorContexts.Commit);
    }

and :

private void dgvProducts_CellValueChanged(object sender, DataGridViewCellEventArgs e)
    {
        if (dgvProducts.DataSource != null)
        {
            if (dgvProducts.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString() == "True")
            {
                //do something
            }
            else
            {
               //do something
            }
        }
    }
Nghĩa Lê
  • 61
  • 1
  • 5
6

This also handles the keyboard activation.

    private void dgvApps_CellContentClick(object sender, DataGridViewCellEventArgs e)
    {
        if(dgvApps.CurrentCell.GetType() == typeof(DataGridViewCheckBoxCell))
        {
            if (dgvApps.CurrentCell.IsInEditMode)
            {
                if (dgvApps.IsCurrentCellDirty)
                {
                    dgvApps.EndEdit();
                }
            }
        }
    }


    private void dgvApps_CellValueChanged(object sender, DataGridViewCellEventArgs e)
    {
          // handle value changed.....
    }
Chuck Fecteau
  • 101
  • 1
  • 2
6

Ben Voigt found the best solution in a comment-reply above:

private void dgvStandingOrder_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (dgvStandingOrder.CurrentCell is DataGridViewCheckBoxCell)
        dgvStandingOrder.CommitEdit(DataGridViewDataErrorContexts.Commit);
}

Seriously, that's ALL you need.

2

What worked for me was CurrentCellDirtyStateChanged in combination with datagridView1.EndEdit()

private void dataGridView1_CurrentCellDirtyStateChanged( object sender, EventArgs e ) {
    if ( dataGridView1.CurrentCell is DataGridViewCheckBoxCell ) {
        DataGridViewCheckBoxCell cb = (DataGridViewCheckBoxCell)dataGridView1.CurrentCell;
        if ( (byte)cb.Value == 1 ) {
            dataGridView1.CurrentRow.Cells["time_loadedCol"].Value = DateTime.Now.ToString();
        }
    }
    dataGridView1.EndEdit();
}
2

It's all about editing the cell, the problem that is the cell didn't edited actually, so you need to save The changes of the cell or the row to get the event when you click the check box so you can use this function:

datagridview.CommitEdit(DataGridViewDataErrorContexts.CurrentCellChange)

with this you can use it even with a different event.

2

I have found a simpler answer to this problem. I simply use reverse logic. The code is in VB but it is not much different than C#.

 Private Sub DataGridView1_CellContentClick(sender As Object, e As 
 DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick

    Dim _ColumnIndex As Integer = e.ColumnIndex
    Dim _RowIndex As Integer = e.RowIndex

    'Uses reverse logic for current cell because checkbox checked occures 
     'after click
    'If you know current state is False then logic dictates that a click 
     'event will set it true
    'With these 2 check boxes only one can be true while both can be off

    If DataGridView1.Rows(_RowIndex).Cells("Column2").Value = False And 
       DataGridView1.Rows(_RowIndex).Cells("Column3").Value = True Then
        DataGridView1.Rows(_RowIndex).Cells("Column3").Value = False
    End If

    If DataGridView1.Rows(_RowIndex).Cells("Column3").Value = False And 
    DataGridView1.Rows(_RowIndex).Cells("Column2").Value = True Then
        DataGridView1.Rows(_RowIndex).Cells("Column2").Value = False
    End If


End Sub

One of the best things about this is no need for multiple events.

Jimva
  • 36
  • 6
2

I've tried some answers from here, but I've always had some kind of problem (like double clicking or using the keyboard). So, I combined some of them and got a consistent behavior (it's not perfect, but works properly).

void gridView_CellContentClick(object sender, DataGridViewCellEventArgs e) {
  if(gridView.CurrentCell.GetType() != typeof(DataGridViewCheckBoxCell))
    return;
  if(!gridView.CurrentCell.IsInEditMode)
    return;
  if(!gridView.IsCurrentCellDirty)
    return;
  gridView.EndEdit();
}

void gridView_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e) {
  if(e.ColumnIndex == gridView.Columns["cFlag"].Index && e.RowIndex >= 0)
    gridView.EndEdit();
}

void gridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  if(e.ColumnIndex != gridView.Columns["cFlag"].Index || e.RowIndex < 0)
    return;

  // Do your stuff here.

}
1

The Code will loop in DataGridView and Will check if CheckBox Column is Checked

private void dgv1_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
{
    if (e.ColumnIndex == 0 && e.RowIndex > -1)
    {
        dgv1.CommitEdit(DataGridViewDataErrorContexts.Commit);
        var i = 0;
        foreach (DataGridViewRow row in dgv1.Rows)
        {
            if (Convert.ToBoolean(row.Cells[0].Value))
            {
                i++;
            }
        }

        //Enable Button1 if Checkbox is Checked
        if (i > 0)
        {
            Button1.Enabled = true;
        }
        else
        {
            Button1.Enabled = false;
        }
    }
}
ArnonZ
  • 3,822
  • 4
  • 32
  • 42
E Coder
  • 295
  • 4
  • 5
1

In the event CellContentClick you can use this strategy:

private void myDataGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{    
    if (e.ColumnIndex == 2)//set your checkbox column index instead of 2
    {   //When you check
        if (Convert.ToBoolean(myDataGrid.Rows[e.RowIndex].Cells[2].EditedFormattedValue) == true)
        {
            //EXAMPLE OF OTHER CODE
            myDataGrid.Rows[e.RowIndex].Cells[5].Value = DateTime.Now.ToShortDateString();

            //SET BY CODE THE CHECK BOX
            myDataGrid.Rows[e.RowIndex].Cells[2].Value = 1;
        }
        else //When you decheck
        {
            myDataGrid.Rows[e.RowIndex].Cells[5].Value = String.Empty;

            //SET BY CODE THE CHECK BOX
            myDataGrid.Rows[e.RowIndex].Cells[2].Value = 0;
        }
    }
}
daniele3004
  • 13,072
  • 12
  • 67
  • 75
1

The best way that I found (which also doesn't use multiple events) is by handling the CurrentCellDirtyStateChanged event.

private void dataGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
    {
        if (dataGridMatten.CurrentCell.OwningColumn == dataGridMatten.Columns["checkBoxColumn"] && dataGridMatten.IsCurrentCellDirty)
        {
            dataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);

            //your code goes here
        }
    }
J.F.
  • 13,927
  • 9
  • 27
  • 65
0

To do this when using the devexpress xtragrid, it is necessary to handle the EditValueChanged event of a corresponding repository item as described here. It is also important to call the gridView1.PostEditor() method to ensure the changed value has been posted. Here is an implementation:

        private void RepositoryItemCheckEdit1_EditValueChanged(object sender, System.EventArgs e)
        {
            gridView3.PostEditor();

            var isNoneOfTheAboveChecked = false;

            for (int i = 0; i < gridView3.DataRowCount; i++)
            {
                if ((bool) (gridView3.GetRowCellValue(i, "NoneOfTheAbove")) && (bool) (gridView3.GetRowCellValue(i, "Answer")))
                {
                    isNoneOfTheAboveChecked = true;
                    break;
                }
            }

            if (isNoneOfTheAboveChecked)
            {
                for (int i = 0; i < gridView3.DataRowCount; i++)
                {
                    if (!((bool)(gridView3.GetRowCellValue(i, "NoneOfTheAbove"))))
                    {
                        gridView3.SetRowCellValue(i, "Answer", false);
                    }
                }
            }
        }

Note that because the xtragrid doesnt provide an enumerator it is necessary to use a for loop to iterate over rows.

majjam
  • 1,286
  • 2
  • 15
  • 32
0

Removing the focus after the cell value changes allow the values to update in the DataGridView. Remove the focus by setting the CurrentCell to null.

private void DataGridView1OnCellValueChanged(object sender, DataGridViewCellEventArgs dataGridViewCellEventArgs)
{
    // Remove focus
    dataGridView1.CurrentCell = null;
    // Put in updates
    Update();
}

private void DataGridView1OnCurrentCellDirtyStateChanged(object sender, EventArgs eventArgs)
{
    if (dataGridView1.IsCurrentCellDirty)
    {
        dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
    }

}
Branden Huggins
  • 695
  • 7
  • 8
0

You can force the cell to commit the value as soon as you click the checkbox and then catch the CellValueChanged event. The CurrentCellDirtyStateChanged fires as soon as you click the checkbox.

The following code works for me:

private void grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
    {
        SendKeys.Send("{tab}");
    }

You can then insert your code in the CellValueChanged event.

David Ruiz
  • 383
  • 1
  • 4
  • 10
0

I use DataGridView with VirtualMode=true and only this option worked for me (when both the mouse and the space bar are working, including repeated space clicks):

private void doublesGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
   var data_grid = (DataGridView)sender;
      
   if (data_grid.CurrentCell.IsInEditMode && data_grid.IsCurrentCellDirty) {
      data_grid.EndEdit();            
   }
}

private void doublesGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
   if (e.ColumnIndex == CHECKED_COLUMN_NUM && e.RowIndex >= 0 && e.RowIndex < view_objects.Count) { // view_objects - pseudocode   
     view_objects[e.RowIndex].marked = !view_objects[e.RowIndex].marked;        // Invert the state of the displayed object
   }
}  
MaxAlex
  • 2,919
  • 1
  • 14
  • 14
0

this worked for me

  private void employeeDataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == employeeDataGridView.Columns["employeeStatusColumn"].Index)
            {
                bool isChecked = (bool)employeeDataGridView.CurrentCell.Value;
                if (isChecked)
                {
                    MessageBox.Show("Checked " + isChecked); //out true;
                }
                else
                {
                    MessageBox.Show("unChecked " + isChecked);
                }
            }

        }

        private void employeeDataGridView_CellMouseUp(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (employeeDataGridView.DataSource != null)
            {
                if (e.ColumnIndex == employeeDataGridView.Columns["employeeStatusColumn"].Index && e.RowIndex != -1)
                {
                    employeeDataGridView.EndEdit();
                }
            }
        }
  • rawaha welcome to Stack Overflow! It would be helpful if you can please add a description of how your code works and maybe explain how this is different or better from other solutions that were provided on this thread. – Jason D Apr 06 '21 at 18:13
0
private void dataGridViewPendingBill_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            bool isChecked = (bool) dataGridViewPendingBill[e.ColumnIndex, e.RowIndex].EditedFormattedValue;

            if (isChecked)
            {
                totalAmount += int.Parse(dataGridViewPendingBill.Rows[e.RowIndex].Cells["Amount"].Value.ToString());
                textBoxAmount.Text = totalAmount.ToString();
            }
            else
            {
                totalAmount -= int.Parse(dataGridViewPendingBill.Rows[e.RowIndex].Cells["Amount"].Value.ToString());
                textBoxAmount.Text = totalAmount.ToString();
            }

            dataGridViewPendingBill.EndEdit();
        }
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 18 '22 at 04:02