Strange issue when disabling a button - c#

I've got a WinForm, which contains a button. In the button's click event, I call button1.Enabled = false and then a number of lines of code follow. After this code, I call button1.Enabled = true.
This is done so that the button cannot be pressed while the execution is being carried out. The strange thing is that while the button is disabled, if the user clicks on the button many times, the button is actually clicked.
Could anyone explain why this is happening? The code looks like this:
button1.Enabled = false
//Code
//Code
//Code
button1.Enabled = true

My guess is that you're actually doing a load of work in those lines of code, blocking the UI thread. You shouldn't do that - you should set the button to be disabled, then start off your work in a background thread (or using asynchronous calls, if most of the time is spent talking to a web service or something similar). Then when the work is finished, it should marshal a call back into the UI thread to re-enable the button.
(If that's not the problem, please show a short but complete program which demonstrates the problem.)
EDIT: Thanks to Sarwar's comments, I understand why you're getting the multiple events being fired - they're being queued up (due to the UI thread being blocked), and only processed after the button is re-enabled. So yes, if you avoid doing lots of work on the UI thread, it can handle the clicks on the disabled button, ignore them, and then there won't be a backlog when the button is re-enabled.
EDIT: For anyone who is interested in trying this themselves, here's my test program demonstrating the problem:
using System;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks; // For the fix below
class Program
{
static void Main()
{
Button button = new Button { Text = "Click me" };
button.Click += HandleClick;
Form form = new Form { Controls = { button } };
Application.Run(form);
}
static void HandleClick(object sender, EventArgs e)
{
Console.WriteLine("In click handler");
Button button = (Button) sender;
button.Enabled = false;
Thread.Sleep(10000);
button.Enabled = true;
Console.WriteLine("Finishing click handler");
}
}
And the fix using C# 5's async handling:
static async void HandleClick(object sender, EventArgs e)
{
Console.WriteLine("In click handler");
Button button = (Button) sender;
button.Enabled = false;
// Delay by 10 seconds, but without blocking the UI thread
await TaskEx.Delay(10000);
button.Enabled = true;
Console.WriteLine("Finishing click handler");
}

We encountered this same problem and like Jon mentioned, it was because the UI was blocked by the work we did inside the click event. When the UI resumed, it registered all the clicks which had happened when the UI was blocked.
Here's an another answer from Stack Overflow which provides some code showing how you can fix the problem.

I solved this using Application.DoEvents() to empty the queue before re enabling the button
AppButton.Enabled = false;
...
do some work
...
Application.DoEvents();
AppButton.Enable = true;

Related

Is there a way to call a function after button is pressed?

So my problem is the following:
I have a button, which has a set background. If the button is pressed, I call the function, which is called check();
The problem is, that if the button is pressed, I will set the background to a certain picture, and if I call the fucntion check, and in that check, sometimes I delete the background.
So sometimes without even seeing the background it gets deleted. How can I improve it, so it gets deleted only like after 3-4 seconds?
private void button2_Click(object sender, EventArgs e)
{
Image img = Image.FromFile(#"C:\Users\nhorv\Downloads\javascript.jpg");
button2.BackgroundImage = img;
buttonList.Add(button2);
counter++;
check();
}
This is the button_click function, and this is the check() func:
void check()
{
if (counter == 2)
{
if (buttonList.Contains(button1) && buttonList.Contains(button6))
{
progressBar1.Increment(10);
}
.
.
.
else
{
buttonList[0].BackgroundImage = null;
buttonList[1].BackgroundImage = null;
}
buttonList.Clear();
counter = 0;
}
}
The buttons are stored in the buttonList list.
For the timing, I heard about: System.Threading.Thread.Sleep(1000); - for a 1 second delay.
Any help is really appreciated!
Edit:
The problem is with the buttons. If I call the button1_pressed function, it runs trough and only after that is the background changed. Unfortunately, there is the check function, which immediately deletes the background. So I have to somehow make the check function after the button press, so that the button can change the background, and only after that will I call check();
Use
System.Threading.Thread.Sleep(3000);
to pause the execution for 3 seconds and then delete the background based on the condition.
private void button2_Click(object sender, EventArgs e)
{
Image img = Image.FromFile(#"C:\Users\nhorv\Downloads\javascript.jpg");
button2.BackgroundImage = img;
buttonList.Add(button2);
System.Threading.Thread.Sleep(3000);
check();
}
So this is what I have understood from your code: you have button 2 with the event handler button2_Click associated on it.
Once you have clicked button 2, you set the background image to the button, add the BUTTON 2 to a list of button, increment a variable called counter by 1 and then call check function.
Check function, first of all, controls if counter is 2. If not, exit. Else, controls if button1 and button6, that I don't know where they're and if thery're added to to buttonList or not, are into the list or not. If they both are present, then increment the progressbar by 10. If one of them is not in the list, or both, you set the background immage of the first 2 buttons in the buttonList to null...
In the end, you clear the buttonList and set the counter to 0.
First of all, we should see the entire code in order to understand what's going on. Relying only on what I can see, start the programm in debug mode and check if button1 and button6 are in the list. In my opinion, you never add those 2 buttons to the list (is enough that only one of them is not present, since there's an and into the if), so the program will always delete the background image.
If I understand you correctly, you want to delay the check() function that deletes the background. You will need to create a thread that will do that:
async Task check()
{
if (counter == 2)
{
await Task.Delay(3000);
...<your other code>
}
}
Your button click then becomes:
private async void button2_Click(object sender, EventArgs e)
An you call your check method:
_ = check();
To create a small delay (so the player can check if they match, you should go for the async method)
But you need to prevent that, when the Task.Delay is running, a button is disabled. The await Delay ensures that the paint messages are handled, but also new buttons can be clicked.
Here an example:
private bool _buttonsEnabled = true;
private async void button2_Click(object sender, EventArgs e)
{
if(!_buttonsEnabled)
return;
_buttonsEnabled = false;
Image img = Image.FromFile(#"C:\Users\nhorv\Downloads\javascript.jpg");
button2.BackgroundImage = img;
buttonList.Add(button2);
counter++;
await check();
_buttonsEnabled = true;
}
private async Task check()
{
if (counter == 2)
{
if (buttonList.Contains(button1) && buttonList.Contains(button6))
{
progressBar1.Increment(10);
}
.
.
.
else
{
// wait for a second before clearing the background.
await Task.Delay(1000);
buttonList[0].BackgroundImage = null;
buttonList[1].BackgroundImage = null;
}
buttonList.Clear();
counter = 0;
}
}
You could set up a timer (see https://learn.microsoft.com/dotnet/api/system.windows.forms.timer) after the second button was pressed and reset the button images in the event handler of elapsed timer. (event is named Tick)

Create a submit button when I press Add, multiple events under one button

Any idea how I could make a click event create another button with different click event?
I have a WPF app to make using EF. So I'm stuck at the part where I need to press button "Add" which will freeze other buttons and then create another button "Submit" with code for adding data to the table. I have tried some advice from msdn, but it doesn't work. Here is the code (previously in XAML added a button named b1):
public partial class RoutedEventAddRemoveHandler {
void MakeButton(object sender, RoutedEventArgs e)
{
Button b2 = new Button();
b2.Content = "New Button";
// Associate event handler to the button. You can remove the event
// handler using "-=" syntax rather than "+=".
b2.Click += new RoutedEventHandler(Onb2Click);
root.Children.Insert(root.Children.Count, b2);
DockPanel.SetDock(b2, Dock.Top);
text1.Text = "Now click the second button...";
b1.IsEnabled = false;
}
void Onb2Click(object sender, RoutedEventArgs e)
{
text1.Text = "New Button (b2) Was Clicked!!";
}
I even tried the most obvious solution to simply create another button with click event directly in click event.
I would recommend an alternative approach and put the submitbutton in your xaml code right away but make it so that it is invisible and disabled.
Then in the event handler you simply have to make it visible and enable it.
Your event handler that handles the submit, the dynamic creation of the button, hooking it in the form and such can all be avoided and don't have to be done at runtime.
This will result in a lot better readable code and maintainable code than your original approach unless you have a very good reason for it.
I have done the following coding and it is working for me
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
Button oButton = new Button();
oButton.Name = "btnMessage";
oButton.Content = "Message Show";
oButton.Height = 50;
oButton.Width = 50;
oButton.Click += new RoutedEventHandler(oButton_Click);
//root is a stack panel
root.Children.Insert(root.Children.Count, oButton);
DockPanel.SetDock(oButton, Dock.Top);
}
void oButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello World !");
}

How to keep update user interface on Windows Phone apps responsively

I am trying to create an Calculator app on WP 8.1. I would like to create an Automation Button which perform automation click on other buttons of my app. For example, in this code, when I click the automation button, it will call the event of clicking other buttons such as:
private void Auto_Click(object sender, RoutedEventArgs e)
{
button3_Click(this, null);
button_addition_Click(this, null);
button7_Click(this, null);
button_result_Click(this, null);
}
I this example, I perform the operations is "3 + 7" and after that I press the "Equal" button.
The result created is right. But I have 1 issue, that is the showing of textbox's data don't change responsively in this method. But when I click the button3 independently, the showing of textbox's data changed instantly. The code button3_Click event is:
private void button3_Click(object sender, RoutedEventArgs e)
{
if (is_subtend(this.textbox.Text) == false)
this.textbox.Text += "3";
else
this.textbox.Text = "3";
}
So what can I do to change the showing of textbox's data in the automation_click event.
Thank you for your help.
The results are as expected. There are really no delays in between those Button Clicks, so it changes the text really fast, no time for the UI to update. If you want to see a "3" then... a "3+"... then a "3+7"... etc.. then implement a delay in-between the button clicks.
To see what I mean try this
using System.Threading.Tasks;
// don't forget the async
private async void Auto_Click(object sender, RoutedEventArgs e)
{
button3_Click(this, null);
await Task.Delay(TimeSpan.FromMilliseconds(250));
button_addition_Click(this, null);
await Task.Delay(TimeSpan.FromMilliseconds(250));
button7_Click(this, null);
await Task.Delay(TimeSpan.FromMilliseconds(250));
button_result_Click(this, null);
await Task.Delay(TimeSpan.FromMilliseconds(250));
}
Lower the 250 to a value you want for the speed you're looking for.

Why aren't UI components refreshed when updated from background thread?

I have a winform with some buttons that are updated in an event handler. The event is fired from a background thread, then the appearance is set via the Invoke method. The buttons will just get enabled or disabled. Something will happen at unpredicable times, though:
The button will not change appearance visually. When it should be enabled, it still looks like it's disabled
Clicking on the "disabled" button still fires the click event - as if its actually enabled underneath
After resizing or moving the form, the component's appearance is set correctly to enabled.
Only the components that are updated in this manner are affected. Other components on the form look/behave fine.
Here is how the button is getting updated in code:
public class Form1 :Form
{
void eventFromThread(object sender, CustomEventArgs e)
{
if(e.enable) RunOnUiThread(ShowEnabledView);
else RunOnUiThread(ShowDisabledView);
}
void ShowEnabledView()
{
button1.Enabled = true;
}
void ShowDisabledView()
{
button1.Enabled = false;
}
void RunOnUiThread(MethodInvoker method)
{
try
{
if(InvokeRequired)
{
Invoke(method);
}
else
method.Invoke();
}
catch(ObjectDisposedException)
{ return;}
catch(InvalidOperationException)
{return;}
}
}
I have tried forcing a refresh on the button, and it hasn't re-occurred yet, but its only been a couple of days. The issue just seems to pop up when it wants to, so I can't really be sure I'm fixing anything. Can anyone shed any light on this?
try calling
System.Windows.Forms.Application.DoEvents()
after you change the button's Enabled property

How to skip Validating after clicking on a Form's Cancel button

I use C#.
I have a Windows Form with an edit box and a Cancel button. The edit box has code in validating event. The code is executed every time the edit box loses focus. When I click on the Cancel button I just want to close the form. I don't want any validation for the edit box to be executed. How can this be accomplished?
Here is an important detail: if the validation fails, then
e.Cancel = true;
prevents from leaving the control.
But when a user clicks Cancel button, then the form should be closed no matter what. how can this be implemented?
If the validation occurs when the edit box loses focus, nothing about the the cancel button is going to stop that from happening.
However, if the failing validation is preventing the cancel button from doing its thing, set the CausesValidation property of the button to false.
Reference: Button.CausesValidation property
Obviously CausesValidation property of the button has to be set to false and then the validating event will never happen on its click. But this can fail if the parent control of the button has its CausesValidation Property set to true. Most of the time developers misses/forgets to change the CausesValidation property of the container control (like the panel control). Set that also to False. And that should do the trick.
I was having problems getting my form to close, since the validation of certain controls was stopping it. I had set the control.CausesValidation = false for the cancel button and all the parents of the cancel button. But still was having problems.
It seemed that if the user was in the middle of editing a field that was using validation and just decided to give up (leaving the field with an invalid input), the cancel button event was being fired but the window would not close down.
This was fixed by the following in the cancel button click event:
private void btnCancel_Click(object sender, EventArgs e)
{
// Stop the validation of any controls so the form can close.
AutoValidate = AutoValidate.Disable;
Close();
}
Set the CausesValidation property of the Cancel button to false.
Set the CausesValidation property to false.
None of these answers quite did the job, but the last answer from this thread does. Basically, you need to:
Insure that the Cancel button (if any) has .CausesValidation set to false
Override this virtual method.
protected override bool ProcessDialogKey(Keys keyData) {
if (keyData == Keys.Escape) {
this.AutoValidate = AutoValidate.Disable;
CancelButton.PerformClick();
this.AutoValidate = AutoValidate.Inherit;
return true;
}
return base.ProcessDialogKey(keyData);
}
I didn't really answer this, just pointing to the two guys who actually did.
Setting CausesValidation to false is the key, however this alone is not enough. If the buttons parent has CausesValidation set to true, the validating event will still get called. In one of my cases I had a cancel button on a panel on a form, so I had to set CausesValidation = false on the panel as well as the form. In the end I did this programatically as it was simpler than going through all the forms...
Control control = cancelButton;
while(control != null)
{
control.CausesValidation = false;
control = control.Parent;
}
In my case, in the form I set the property AutoValidate to EnableAllowFocusChange
By using Visual Studio wizard you can do it like that:
Judicious use of the Control.CausesValidation property will help you achieve what you want.
Just above the validation code on the edit box add:
if (btnCancel.focused)
{
return;
}
That should do it.
In complement of the answer of Daniel Schaffer: if the validation occurs when the edit box loses focus, you can forbid the button to activate to bypass local validation and exit anyway.
public class UnselectableButton : Button
{
public UnselectableButton()
{
this.SetStyle(ControlStyles.Selectable, false);
}
}
or if you use DevExpress:
this.simpleButtonCancel.AllowFocus = false;
Note that doing so will change the keyboard experience: the tab will focus anymore on the cancel button.
Maybe you want to use BackgroundWorker to give little bit delay, so you can decide whether validation should run or not. Here's the example of avoiding validation on form closing.
// The flag
private bool _isClosing = false;
// Action that avoids validation
protected override void OnClosing(CancelEventArgs e) {
_isClosing = true;
base.OnClosing(e);
}
// Validated event handler
private void txtControlToValidate_Validated(object sender, EventArgs e) {
_isClosing = false;
var worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync();
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
// Do validation on complete so you'll remain on same thread
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (!_isClosing)
DoValidationHere();
}
// Give a delay, I'm not sure this is necessary cause I tried to remove the Thread.Sleep and it was still working fine.
void worker_DoWork(object sender, DoWorkEventArgs e) {
Thread.Sleep(100);
}
This is an old question however I recently ran into this issue and solved it this way:
1st, we are loading a UserControl into a 'shell' Form that has a save and cancel button. The UserControl inherit an interface (like IEditView) that has functions for Save, Cancel, Validate and ToggleValidate.
In the shell form we used the mouse enter and mouse leave like so:
private void utbCancel_MouseEnter(object sender, EventArgs e)
{
((Interface.IEdit)tlpMain.Controls[1]).ToggleValidate();
}
private void utbCancel_MouseLeave(object sender, EventArgs e)
{
((Interface.IEdit)tlpMain.Controls[1]).ToggleValidate();
}
Then in ToggleValidate (Say a simple form with two controls...you can always just loop through a list if you want) we set the CausesValidation
public bool ToggleValidate()
{
uneCalcValue.CausesValidation = !uneCalcValue.CausesValidation;
txtDescription.CausesValidation = !txtDescription.CausesValidation;
return txtDescription.CausesValidation;
}
Hope this helps.
I found this thread today while investigating why my form would not close when a validation error occurred.
I tried the CausesValidation = false on the close button and on the form itself (X to close).
Nothing was working with this complex form.
While reading through the comments I spotted one that appears to work perfectly
on the form close event , not the close button (so it will fire when X is clicked also)
This did the trick.
AutoValidate = AutoValidate.Disable;
Create a bool:
bool doOnce;
Set it to false in your function and then:
if (doOnce == false)
{
e.cancel = true;
doOnce = true;
}
This means it will only run once and you should be able to cancel it. This worked for me anyways.
This work for me.
private void btnCancelar_MouseMove(object sender, MouseEventArgs e)
{
foreach (Control item in Form.ActiveForm.Controls)
{
item.CausesValidation = false;
}
}

Categories

Resources