0

I'm using a DataGridViewCheckBoxColumn inside a DataGridView in a WinForm panel.
When a checkbox is clicked, I need to compute things that might change a Control state outside the DataGridView.

To do so, I have to handle the CellContentClick event because I need to compute only when a checkbox value is actually changed.

Grid.CellContentClick += Grid_CellContentClick

private void Grid_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    DataGridView dgv = (DataGridView)sender;
    dgv.EndEdit();

    // Compute stuff
}

However CellContentClick doesn't always fire, while the internal event that does change the DataGridViewCheckboxCell checked state is.
Most importantly, fast successive clicks on a checkbox do not fire CellContentClick, only the first is catched until the user stops clicking.

As a result I end up in an invalid state where the control outside the DataGridView doesn't display as intended because the computation doesn't use the checkboxes final values.
I've tried to debounce the event and creating a pseudo-lock using MouseDown and the grid ReadOnly property, with no success.

Is there a way to catch only the last event of a series of clicks? Is there a better way to do this?

Cedt
  • 251
  • 1
  • 14
  • If you call `EndEdit()` in `CellContentClick`, you should evaluate the new value in `CellValueChanged`. -- The fast click on a Cell or Cell content doesn't affect the new Value. You will get the updated value in `CellValueChanged` in any case (when the value is actually updated). – Jimi Jul 07 '22 at 17:06
  • Sorry if I am missing something. How is the external control getting the cells check/unchecked state? In my small tests… if the user clicks on the same check box cell successively and quickly, then the grids `CellValueChanged` event may not fire, however, if the external control checks the cells checked/unchecked state then it “should” return the actual check state of the cell even if the grids `CellValueChanged` did not fire. – JohnG Jul 07 '22 at 17:12
  • @JohnG EndEdit() raises the `CellValueChanged` event immediately, before the code in `CellContentClick` completes. `CellValueChange` is raised no matter what, if the Cell's value changes. Here, the OP - apparently - only needs to counteract the effect of a fast click. They *could* just handle `CellValueChanged`, but, if the *effect* needs to be also *seen* immediately, it's sufficient to make the `CellContentClick` handler async and, e.g., `Await Task.Delay(100)` (when the Cell Type is `DataGridViewCheckBoxCell`). This nullifies the fast click action. – Jimi Jul 07 '22 at 17:26
  • @Jimi … in my small tests, I used the `CellContentClick` event to call the grids `EndEdit`. Also, I wired up the grids `CellValueChanged` event. Putting `Debug` statements in each event and in my small tests… when successively and quickly clicking on the same check box cell… I could see the `CellContentClick` event fire… but the `CellValueChange` event did NOT fire. At least I did not see the `Debug` statement when clicking quickly on the same check box cell. However, checking the cells value externally always returned the correct check box value. Am I doing something wrong in my test? – JohnG Jul 07 '22 at 17:33
  • @JohnG No, that's correct. That's why I suggested to add a small delay to counteract fast clicks. So the `CellValueChanged` handler can run its code. -- Restating, since it's important: the code in `CellContentClick` - after the `EndEdit()` call - is run only after the code in the `CellValueChanged` handler is executed. Assuming the `CellValueChanged` event can be fired (cannot while fast clicking the Cell's content). – Jimi Jul 07 '22 at 17:36
  • I can only assume that the `EndEdit` line in the `CellContenetClick` event is possibly not getting executed if clicking on the check box quickly. Sorry I did not see your comment before I posted this. Also, setting a delay would work, but IMO this is an awkward way to prevent the user from quickly clicking on the check box. – JohnG Jul 07 '22 at 17:42
  • @JohnG `EndEdit()` is executed, but the Cell's cached value has no time to update with the new value set in the UI, so the evaluation always returns `false` (no change), the Cell's `Value` is the cached value and the `CellValueChanged` event is not raised (hence *give it some time to update the current Value*). – Jimi Jul 07 '22 at 17:53
  • @Jimi … that makes sense… thanks for clarifying. – JohnG Jul 07 '22 at 17:55
  • Thank you both for your comments, I was able to solve my issue with your remarks. See my answer for more details – Cedt Jul 08 '22 at 09:06

1 Answers1

0

Thank you @Jimi and @JohnG for your insights, it helped me solving this issue.

I could not make it work using CellValueChanged and CellContentClick, even with async and await Task.Delay(...) as it did not fire correctly and triggered inter-thread exceptions in my computation afterwards.
It might just have been me though, but I wasn't very fond of using Threading in this context anyway.

I hadn't considered using CellValueChanged and noticed that it wouldn't trigger for a DataGridViewCheckBoxCell when clicked, so I ended up reading this thread and the solution is actually quite simple.

Grid.CurrentCellDirtyStateChanged += Grid_CurrentCellDirtyStateChanged;
Grid.CellValueChanged += Grid_CellValueChanged;

private void Grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (Grid.IsCurrentCellDirty)
    {
        Grid.CommitEdit(DataGridViewDataErrorContexts.Commit);
    }
}
private void Grid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
    DataGridViewCheckBoxCell cell = (DataGridViewCheckBoxCell)Grid[e.ColumnIndex, e.RowIndex];

    // Compute stuff
}

This code is executed each time a grid checkbox is clicked on, and has a far better logic since it relies on a direct value change.
However it means the computation takes place each time the value is changed.

I believe one could debounce the computation in order to improve this solution, fortunately mine isn't too resource expensive so it runs smoothly and I don't need to take it that far.

Cedt
  • 251
  • 1
  • 14
  • The equivalent (*throttling*) method - using either the `CellContentClick` or `CellValueChanged` events to read the current Value of a CheckBoxCell - as described in comments, is: `if (Grid[e.ColumnIndex, e.RowIndex] is DataGridViewCheckBoxCell cell && Grid.IsCurrentCellDirty) { await Task.Delay(100); Validate(); var v = cell.Value; }`. Set in `CellContentClick`, you can then read the `[Cell].Value` in both handlers, it should be synchronized. – Jimi Jul 08 '22 at 20:20
  • @Jimi it is most probably be a beginner issue on my end, but when I try this code I get an `Invalid cross-thread operation exception` in my computation, after `Validate();`. It is triggered because it tries to access the control I need to change the state of. I've tried to put a `lock` on it with no success. Would you have any idea on how to avoid this exception? – Cedt Jul 11 '22 at 08:32