How to get the last click event on DataGridViewCheckBoxCell - winforms

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?

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.

Related

Visual Studio "Not Responding" when I change a datetimepicker value in Winforms [duplicate]

I have a datetimepicker in C#. When I click on it, it expands to show a monthly calendar, when I click the left arrow to go back a month, it changes the value and calls my event. The event includes too much code to include here but it calls several functions needless to say.
The problem I'm having is that when I click that left arrow it gets stuck in some sort of loop and keeps descending through the months and I can't stop it. One of the functions that is being called contains a Application.DoEvents() and if I comment that out it doesn't get stuck in the loop, but I need that command to update another section of the interface. Any idea why this is happening?
I can duplicate it sometimes with this code, sometimes it just does it a couple times, sometimes it gets stuck in the loop.
private void DateTimePickerValueChangedEvent(object sender, EventArgs e)
{
afunction();
}
private void afunction()
{
listView1.Clear();
panel1.Visible = true;
Application.DoEvents();
}
I also have the same problem. In my case, instead of calling DoEvents I'm updating a Crystal Report view. The only workaround I found is to update my view upon the CloseUp event instead of ValueChanged or TextChanged.
Scott, how did you finally corrected your problem ?
The DateTimePicker ValueChanged event is buggy. Per Microsoft Windows Forms Team on this page https://connect.microsoft.com/VisualStudio/feedback/details/1290685/debugging-datetimepicker-event-hangs-vs:
"The DateTimePicker control installs a mouse hook as part of its functionality, but when the debugger has the WinForms application stopped on a breakpoint, it allows the possibility of a deadlock if VS happens to get a mouse message. For now, the deadlock is unfortunately a consequence of the DateTimePicker's design. The mouse hook is installed when the drop down is clicked to display the calendar. This means that breakpoints should not be sent in any event handlers which would be called while the calendar is active. We are currently investigating whether it is possible to address this issue and we will update this thread with further information if we are able to make a fix available."
Without seeing any of the code, try these steps:
Comment out the entire event handler
to see how fast it runs with nothing
attached to it.
Uncomment lines one at a time to see
which ones are causing the most
problems.
Analyze those method calls.
...
Profit!
You could try a couple of things. Get rid of the DoEvents inside of the ChangedEvent.
Call the doevents inside of a seperate function after maybe a period of time (thread.sleep() ?).
I know doevents does cause issues but I rarely use it.
event procedure ValueChanged :
set parameter in sender.tag
enableTimer and execute parameter using sender.tag
example:
private void DateTimePicker_ValueChanged(object sender, EventArgs e)
{
DateTimePicker ThisSender = (DateTimePicker)sender;
Timer.Tag = ThisSender.Name.ToString() + "=" + ThisSender.Value;
Timer.Enabled = true;
}

Why my textbox TextChanged event gets fired after I enter "only" one character in my text box?

private void NameVal_TextChanged(object sender, EventArgs e)
{
String text = NameVal.Text;
}
As soon as I enter the first letter of my Name this program gets executed . How do I make the program wait until I finish entering the whole string for the field (such as Name ex: James) is entered.
If you want to determine when the user has finished typing, you can catch the leave event instead - this is fired when the focus is lost on that text box - ie the user clicks outside of the textbox:
private void NameVal_Leave(object sender, EventArgs e)
{
//Do your stuff
String text = NameVal.Text;
}
If you are working with VS2019 there is a correlation between the TextChanged event and the Focus Leave event. You must have a TextChanged Event then when you search the Events and find Focus select Leave then there will be a drop down box with the TextChanged event for the same object as the Focus Event, which you will select. That will add the Leave Event to the Form1 definition area but it will not generate the code in the code area of Form1 like when you normally double click the TextBox. That you must create yourself. This article helped me do that part.
Later that day.
I was mistaken. You don't have to pick the TextChanged event. It was the only one available in the drop down list at the time. I had to add both events again in the form1 definition area since I had deleted the TextChanged event and manuallu added the Focus_Leave event, then went back in to add the TextChanged Event. I'm experimenting here. But when I deleted them both and re-entered the events again in the form1.design file there was a possibility to pick the correct one. I had another bug, but now they are in sync again. It's all the code that is generated behind the scene with MS VS that makes it difficult to change this area. And of course they do tell you not to change it, but what do you do if you make a mistake like this, you don't start over, you dive right in and keep going and learn how they are doing what they do.

Event to know if Panel lost focus in Winforms application?

I have a simple Form with 4 panels in it. Each of those panels are docked in the parent, to ensure only one is visible at a given time. Now, for Panel2, when it is moving from front to back, I would like to work on that event. I am making panels visible by calling panel.BringToFront()
I have tried Leave event but that doesn't work. For Form, the event is Deactivate, what's the event for Panel?
I'm thinking LostFocus is what you're looking for.
Edit
As another strategy, you know that calling panel.BringToFront will queue an update in your UI. Wherever you are calling panel.BringToFront, perhaps you could just call one of your own methods, or trigger one of your own events. This way, you know when the event will be triggered, and exactly what will trigger it.
The reason I thought of this is that I doubt your Panel will ever actually have the focus itself - rather, one of its child controls will likely have the focus. By doing you own event trigger, you don't have to rely on something as volatile as focus. Plus, even if the Panel did have the focus, it's always possible that it could lose focus in other ways than your own panel switching.
Edit #2
Here's an attempt at a quick implementation of my previous ramblings. I'll be making the assumption that this code be placed somewhere in the same class as all your Panel instances (i.e. in your Form class).
// This will be the custom event to which you can subscribe
// in order to detect a switch in panels.
public event EventHandler PanelSwapEvent;
// This reference the currently visible panel - should be set
// to the default panel in the form's constructor, if possible.
private Panel currentPanel;
// This actually switches the panels, to minimize code duplication.
private void switchToPanel(Panel p)
{
Panel lastPanel = currentPanel;
currentPanel = p;
// Move the panels, and invoke the event.
p.BringToFront();
if(PanelSwapEvent != null)
PanelSwapEvent(lastPanel, new EventArgs());
}
// Here's the actual event handler (replaces your
// pnlServiceInfo_LostFocus handler).
private void PanelSwapHandler(object sender, EventArgs e)
{
// whatever you want to do when panels are swapped
}
In this example, the sender of the event handler is the panel that lost "focus". Using it is as simple as saying switchToPanel(pnl_whatever) to indicate that you would like to switch from the current panel to the panel named pnl_whatever.

PerformenceProgessBar and ObservableCollection

I have an ObservableCollection that gets data from a foreach. The UI updates great when changes are made to the collection, but there is a lag as the collection size starts to increase in size. I have about 500+ items in my collection that get deleted, reordered and added. But I've noticed that there is a definite lag between when the changes are made and when they are reflected in the UI. The example below is just a simple one. Obviously, the progress bar won't be on for long in this case (fraction of a second), but the point I'm making is that the UI won't reflect the changes I've made for many, many noticeable seconds after the progress bar is stopped below. What event can I capture or implement in my object that will allow me to add code that updates the progress bar while the ObservableCollection is being pushed to the UI? Or does someone want to send me a "DealWithIt" dog picture :)
a_progressbar.IsIndeterminate = true;
foreach (Group<SomeItem> sortedItem in sortedItems)
{
OList.Add(sortedItem);
}
a_progressbar.IsIndeterminate = false;
Update Here is the code I used to make it work.
_dataBinder = new BackgroundWorker();
_dataBinder.DoWork += new DoWorkEventHandler(DataBinderWork);
_dataBinder.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DataBinderComplete);
...
private void DataBinderComplete(object sender, RunWorkerCompletedEventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
a_progressbar.IsIndeterminate = false;
});
}
private void DataBinderWork(object sender, DoWorkEventArgs e)
{
Dispatcher.BeginInvoke(() => LotsOfWork());
}
This sounds like a good job for the BackgroundWorker component. The component will allow you to perform expensive operations in a background thread so your UI won't hang; meanwhile, the UI thread can be notified via events so it knows when the work has completed.
Update:
I must have misunderstood the question. If your question is about a list in your UI that is bound to the ObservableCollection being slow to update when adding a bunch of items, one thing you could attempt to do is to build a new ObservableCollection, filling it with the data you need, then assigning the new collection to the UI.
I've had problems with ObservableCollection in the past because it raises an event every time an item is added, which makes the UI responsiveness poor. My workaround was to create a sub-class of the collection where you could do an AddRange which only raised one event, but simply building a new collection that isn't bound to the UI then binding it after it's been populated is another option.
A big warning against the use of ProgressBar.IsIndeterminate - it takes more than 60% of the CPU resources and can have disastrous impact in some cases. See this article for more info.

What's the best way to prevent losing TextBox focus when there is a validation error?

I've messed around with PreviewLostKeyboardFocus which almost gets you there. I've seen a couple of implementations using LostFocus, but that just forces focus back on the TextBox after it's lost focus and you can easily see this shifting on the screen. Basically, I'm just looking for the same type of behavior you could get with using OnValidating in WinForms.
In my opinion, the best way is generally not to do it. It is almost always better to just disable the other controls or prevent saving until the value is valid.
But if your design really needs this ability, here is what you should do:
Intercept the Preview version of keyboard and mouse events at your window level, or whatever scope you want to prevent focus changes within (eg maybe not your menu bar).
When the Tab KeyDown or Return KeyDown is detected in the text box, or when a MouseDown is detected outside the text box while it has the focus, call UpdateSource() on the binding expression, then if the validation has failed set Handled=true to prevent the KeyDown or MouseDown event from being processed further.
Also continue handling PreviewLostKeyboardFocus to catch any causes of focus change that aren't from the keyboard or mouse, or that your other code didn't recognize.
To add onto Ray's answer:
UpdateSource is called like so:
BindingExpression be = userTextbox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
Also, as an alternative you can set the text box binding to:
UpdateSourceTrigger = "PropertyChanged";
The latter will cause a continuous check, whereas the former will check when needed (performant).
If you attempt to focus an element inside its own LostFocus handler you will face a StackOverflowException, I'm not sure about the root cause (I suspect the focus kind of bounces around) but there is an easy workaround: dispatch it.
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
var element = (sender as TextBox);
if (!theTextBoxWasValidated())
{
// doing this would cause a StackOverflowException
// element.Focus();
var restoreFocus = (System.Threading.ThreadStart)delegate { element.Focus(); };
Dispatcher.BeginInvoke(restoreFocus);
}
}
Through Dispatcher.BeginInvoke you make sure that restoring the focus doesn't get in the way of the in-progress loss of focus (and avoid the nasty exception you'd face otherwise)

Resources