This question already has answers here:
Is it possible to avoid multiple button clicks on a Winform?
(3 answers)
Closed 2 years ago.
I have an app with many user controls and many buttons on each, each button has an OnClick event which does some important stuff and then sends to a new user control.
The problem comes when the user clicks really fast multiple times, the event code gets executed more than once before exitting to a new user control, causing problems.
My current solution is to disable the button on the very first line of the event handler, but doing this to every window and handler would be troublesome, what can I do?
EDIT: Would it be a good solution to derive from Button, and override the OnClick event so it always does a check for a "working" variable, and if it is true, it doesnt start the event ? Something like:
public class MyButton : Button
{
private static bool isWorking = false;
protected override void OnClick(EventArgs e)
{
if (!isWorking)
{
isWorking = true;
base.OnClick(e);
isWorking = false;
}
//Else do nothing
}
}
You can use some timeStamp to delay between 2 clicks:
DateTime timeStamp;
//this will handle the clicks with the allowed interval being 0.5 second
//Note that a tick is equal to 1/10,000,000 of second.
private void click_Handler(object sender, EventArgs e) {
if ((DateTime.Now - timeStamp).Ticks < 5000000) return;
timeStamp = DateTime.Now;
//your code goes here ....
}
If you want all buttons to wait until one button's work is done, add a bool isProcessing variable to your form. Wrap the work of each button inside an if (!isProcessing), and set that flag to true in the first line inside the if statement. Then don't forget to set it back to false right before you exit the if.
I'm assuming you're doing all of this asynchronously, since if it's all in the same thread, the form will lock while it's processing the work. This will solve your issue though.
Disabling controls while sensitive operation is on-going is a typical solution that I always apply.
But since there can be quite a few controls on one screen that are affected by some click or change in UI, I typically design forms to have a specialized method which walks through all the affected controls and disables/enables them accordingly.
Something like this:
void EnableControls(bool enable)
{
foreach (Control ctl in this.Controls)
ctl.Enabled = enable;
}
Similarly, you could group controls into related buckets, so to disable/enable only one of them etc. Depends on your precise needs.
There is an alternative solution to use timer - disable the button, but enable it after 1 sec. This prevents nervous users from clicking multiple times if that would cause damage to data (i.e. each click is treated as a new operation).
I would call the same function from every button and then perform the specific task:
private void Button_Click(object sender, EventAgrs e)
{
Button btn = sender;
btn.disable = true;
switch (btn.AccessibleName)
// call specific function for the particular button or do it all here
}
I'm not sure if this would even work, but just an idea...
You could try with aspect oriented approach (with the help of Postsharp for example):
Create two aspects, one for method entry and one for method exit. In the method entry mark the current method as 'processing' (add the method name to a hash set for example). In the method exit mark the method as 'not processing' (remove it from the hash set). Then in the method entry check if the method is processing and if it is, then cancel the method (like this: https://stackoverflow.com/a/2437794/113858)
Mark all of your event handlers with this aspect.
Related
I want to prevent a button click from queuing. In testing I have a Form, a Button and in the Code-Behind I have the event handler:
private void button1_Click(object sender, EventArgs e)
{
if (_codeRunning)
return;
_codeRunning = true;
//Application.DoEvents();
//button1.Enabled = false;
_click ++;
Debug.WriteLine("Click Number: " + _click);
Task.Delay(5000).Wait();
//button1.Enabled = true;
_codeRunning = false;
}
When I run debug and click the button twice or three or four times rapidly, Debug Output shows each click about five seconds after the last one. What I would like it to show is a single Click and drop the rest until first Event is complete.
I have also tried to disable the button, as well as temporarily remove the Handler from the Button_click event. It is all the same results.
There are various amounts of trouble you'll get into when you hang-up the UI thread like this. This is certainly one of them, nothing pleasant happens when the user wildly bangs on the button to try to get something noticeable to happen. And sure, those clicks won't get lost, they stay stored in the message queue. To activate your Click event handler again when your event handler stops running.
Pretty important to learn how to use the BackgroundWorker or Task classes to avoid this kind of trouble. Just setting the button's Enabled property is then enough to solve this problem.
Purging the mouse clicks from the message queue is technically possible. But ugly to do, it requires pinvoke. I'll hesitantly post the alternative, don't assume that this is in general a good strategy. You'll need to read this post to have some insight into why DoEvents() is a dangerous method.
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
button1.Update();
'' long running code
''...
Application.DoEvents();
if (!button1.IsDisposed) button1.Enabled = true;
}
The Update() call ensures that the user gets the feedback he needs to know that banging the button repeatedly isn't going to do anything useful. The DoEvents() call will dispatch all the queued-up mouse clicks, nothing happens with them since the button is still disabled. The IsDisposed test is essential to solve the problem with DoEvents(), it ensures your program won't crash when the user clicked the window's Close button while the code was running.
Use the HourGlass class in this post to provide more feedback.
I had a button that on click event was going to run a method. Same issue happent and when the user clicked multiple times the method was triggered multiple times. So I made a boolean and changed it value when the method started.
private bool IsTaskRunning = false;
private void MyMethod()
{
if ( IsTaskRunning==false )
{
IsTaskRunning=true;
// My heavy duty code that takes a long time
IsTaskRunning=false; // When method is finished
}
}
So now the method runs only if it's done the last time.
I'm using User Controls in C# winforms, and I would like some code to be executed after the load event, and after the control has been shown. If no such event exists, is it possible to make one?
You could use one of events from this list. OnPaint would be most likely candidate.
Form Events:
Construtor
Load
Layout
Activated
PaintÂ
Closing
Closed
Deactivate
Dispose
and for Controls:
Enter
GotFocus
Leave
Validating
Validated
LostFocus
If you can't find one that fits you needs, this article explains how to construct and fire event.
Create a public method in user control
Call the method on Parent win-form Shown Event
This will call the code you want to run once.
You can also use user-control paint event, but this will Expensive call. Because your code will execute every time the control is redrawn.
So it is better to use a flag which you can determine whether to run the code or not
i.e.
private bool _run =true;
private void Control_Paint(object sender, PaintEventArgs e)
{
if(!_run) return;
//call the code you need to run
_run = false;
}
I'm writing a custom control and I'd like the control to switch from an editing state to it's normal state when a user clicks off of the control. I'm handling the LostFocus event and that helps when a user tabs away or if they click on to another control that's Focusable. But if they don't click on something Focusable, it won't switch out of it's editing state. So I have two solutions in mind:
Walk up the tree to the top most element when it goes in to an editing state and add a handler for MouseDownEvent (and handle "handled" events). In the handler I'd kick the control out of it's editing state and remove the handler from the top most element. This seems like a bit of a hack, but it would probably work well.
Example code:
private void RegisterTopMostParentMouseClickEvent()
{
_topMostParent = this.FindLastVisualAncestor<FrameworkElement>();
if ( _topMostParent == null )
return;
_topMostParent.AddHandler( Mouse.MouseDownEvent, new MouseButtonEventHandler( CustomControlMouseDownEvent ), true );
}
private void UnRegisterTopMostParentMouseClickEvent()
{
if ( _topMostParent == null )
return;
_topMostParent.RemoveHandler( Mouse.MouseDownEvent, new MouseButtonEventHandler( CustomControlMouseDownEvent ) );
_topMostParent = null;
}
Use Mouse.PreviewMouseDownOutsideCapturedElement and add a handler to my control. In the handler I'd kick the control out of it's editing state. But I don't seem to get the event to fire. When does the Mouse.PreviewMouseDownOutsideCapturedElement get kicked off?
Example code:
AddHandler( Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler( EditableTextBlockPreviewMouseDownOutsideCapturedElementEvent ), true );
Just to clarify the answer provided about mouse focus - it was useful but I had to do some further digging + mucking about to get something that actually worked:
I was trying to implement something like a combobox and needed similar behaviour - to get the drop down to disapear when clicking on something else, without the control having knowledge of what something else was.
I had the following event for a drop down button:
private void ClickButton(object sender, RoutedEventArgs routedEventArgs)
{
//do stuff (eg activate drop down)
Mouse.Capture(this, CaptureMode.SubTree);
AddHandler();
}
The CaptureMode.SubTree means you only get events that are outside the control and any mouse activity in the control is passed through to things as normal. You dont have the option to provide this Enum in UIElement's CaptureMouse, this means you will get calls to HandleClickOutsideOfControl INSTEAD of calls to any child controls or other handlers within the control. This is the case even if you dont subscribe to the events they are using - full Mouse capture is a bit too much!
private void AddHandler()
{
AddHandler(Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(HandleClickOutsideOfControl), true);
}
You would also need to hang on to + remove the handler at the appropriate points but I've left that out here for the sake of clarity/brevity.
Finally in the handler you need to release the capture again.
private void HandleClickOutsideOfControl(object sender, MouseButtonEventArgs e)
{
//do stuff (eg close drop down)
ReleaseMouseCapture();
}
Capture the mouse.
When an object captures the mouse, all mouse related events are treated as if the object with mouse capture perform the event, even if the mouse pointer is over another object.
I usually get the parent window and add a preview handler, even if already handled. Sometimes when MouseCapture is not enough, this technique comes handy:
Window.GetWindow(this).AddHandler
(
UIElement.MouseDownEvent,
(MouseButtonEventHandler)TextBox_PreviewMouseDown,
true
);
I would approach this a different way - have the form that contains the control remove focus from the control when a user clicks on another part of the form.
Having the control actually loose focus is far cleaner than attempting to have the control "simulate" focus being lost in certain situations, when in fact it hasn't. Bear in mind that unless the control has really lost focus it will still accept things like keyboard input.
I am pasting text in textbox1.text and I need textbox1 should fire its leave event by itself.
For now I am using this following code. but i will appreciate if anyone can suggest me an elegant or better way:-
private void event()
{
textbox1.Text = SearchedText;
textbox1.Focus();
textbox2.Focus();
}
First I am pasting text, then setting up Focus on the control, then set up focus again on second control. It is firing leave event of textbox1, but any thing better?
Just call the code directly, no need to wait for an event:
private void textBox1_Leave(object sender, EventArgs e) {
mumble();
}
private void someEvent() {
textBox1.Text = SearchedText;
mumble();
}
void mumble() {
// etc...
}
Just calling textBox1_Leave(this, EventArgs.Empty) works fine too.
You should handle the TextChanged or Validated events instead of the Leave event.
To FORCE Leave, Validating and so on Events, no matter what, I've found ONE working solution.
First i tried:
ProcessTabStop(true);
ProcessTabStop(false);
instead of:
textbox1.Focus();
textbox2.Focus();
Problem with the TextBox 1 and 2 Focus() is that its only Active Component that needs Leave, Validating and so on fired, not other Controls, and besides, what if the form is dynamic, you as a programmer not necessarily have any idea what Control you are trying to Leave, that's why i changed Control.Focus() method to ProcessTabStop method above. The problem is then, if only ONE Control has TabStop true, there is no control to go to and back from. So Events are NOT Fired.
Next problem is that i not necessarily Close the Form with the mouse so Focus doesn't change, I use a Key (Ctrl+Enter) to Accept the Form, and then Leave, validating and so on are NOT fired, when i Send Form Close, as Form Close registers weather there are changes or not. But Values are set in Leave on TextBoxes, so I had to find a solution that worked no matter what i did to it. I almost gave up, actually i had a problem report all filled out, when I thought, what if i set ActiveControl to Null and then to the Control it came from. It worked, but had som "Flicker" due to color change on Parent Panel depending on Active or Inactive.
The "Workaround" that works in all cases is:
Control Old = ActiveControl;
// ActiveControl.SuspendLayout();
// ActiveControl.FindForm().SuspendLayout();
ActiveControl = null;
ActiveControl = Old;
// ActiveControl.FindForm().ResumeLayout();
// ActiveControl.ResumeLayout();
That seems to fire Leave, Validating and so on Events, no matter number of Form Controls and TabStopped Controls. You MAY need to SuspendLayout on either ActiveControl, or Form. My Control (Parent Panel) changes color when Active/Inactive, if I do not Suspend Layout on Form, parent panel gets an unwanted "flicker" effect.
Looking at the solution, it is very obvious, now I've found it, but took me half a day to try different things that solved one or another problem, but not all.
I know this a VERY old thread, but one of very few articles I've found on the subject of Forcing Leave Event to be Fired.
I am trying to determine if my application is closed through clicking the "X" on the windows form, or if they clicked an "Exit" button I have on it. Right now I am using StackTrace.GetFrame(someIndex) to determine how, but i am looking for a more definitive way since it looks like these frame orders arent guaranteed. Is there a better way to make the distinction? This is a .NET 3.5 WinForm, and Im writing in C#.
Use a different event to handle your own "Exit" button click. In your own "Exit" event handler do your extra logic, or set some state variable, and then call the normal application close method.
Post some samples of how your events are wired up and I get give a more specific example. In general it would look something like this:
private void btnMyExit_Click(object sender, EventArgs e)
{
// TODO: add any special logic you want to execute when they click your own "Exit" button
doCustomExitWork();
}
public static void OnAppExit(object sender, EventArgs e)
{
doCustomExitWork();
}
private void doCustomExitWork()
{
// TODO: add any logic you want to always do when exiting the app, omit this whole method if you don't need it
}
Use the FormClosing event and query the FormClosingEventArgs for the enum CloseReason value.