Avoid UI buttons to run function twice after fast click - c#

How do I avoid my UI buttons to run functions twice if clicked (accidentally) twice or more in a short time?
I have a UI button that calls a StartGame function in my game script. I attached the script to the button in the inspector. The function sets a few variables in order for the game to start.
If I accidentally click the button twice really quickly, the start function is executed twice and thus it screws up my variables. How do I avoid that?
I am thinking of splitting up the function so that it checks if it's already started, but that seems like a bit complex. Isn't there a standard setting in Unity UI or some standard code I missed? Tried to google but not much luck.
Simplified example of my code:
public void StartGame() {
MenuAnimator.SetBool ("Startbutton", false); // animates my start game panel off the screen.
lives = lives - 1;
Do Rest of Code();
}
In this case, clicking the UI menu Start Game button multiple times causes two or more lives to be deducted.

There's no need for a lock, just have a boolean that flips each click and the button only triggers a response when the bool is true.
first click -> true -> response.
second click -> false -> return.

What about something like this? This will prevent double clicks that happen within 1 second (or however many you want)
bool buttonLocked;
System.Timers.Timer t = new System.Timers.Timer(1000); //however many milliseconds
t.Elapsed += new EventHandler(resetFlag);
private void button_clicked(object sender, EventArgs e){
if(!buttonLocked){
// Handle Click
buttonLocked= true;
t.Enabled = true;
}
}
private void resetFlag(){
buttonLocked = false;
t.Enabled = false;
}
Or you could probably just use whether or not the timer is enabled as a flag assuming the timer is properly initialized. I like having a separate flag though for readability.
Full disclosure- I'm at a bar typing this on my phone so it's untested but I think that should work

Due the fact that I canno't comment, here is my answer.
You can lock the variable.
First create a lock
private readonly object _locker = new object();
In your lock property set/get
get { lock (_locker) { return this.myVar; } }
set { lock (_locker) { this.myVar = value; } }
Now you can lock this MyVar variable using
public void MethodA()
{
lock(_locker)
{
if(myVar == 1)
myVar = 0;
}
}
This is mostly used with multi-threading but It should do the job for you.

Related

Why is UI unresponsive while being programmatically controlled?

I hand-rolled a MVC-style implementation of a game that I want to autoplay. By "autoplay" I mean that the buttons that normally a user would click while playing I want a controller to automatically initiate. That way I can watch the game play itself for quality control reasons. This particular game has a lot of code, so instead of providing it as an example I've created a silly HelloWorld example using the same approach.
Before I provide the example, here is my issue: everything you see below is functional, and "works"; except for one thing: I'm unable to shut-off the autoplay because the UI becomes unresponsive and the button to turn it off won't respond to a click event.
First create a .Net 4.6.1 winforms project in a solution. (.net version probably doesn't matter as long as it is >= 4.5). Create a Form that looks like this:
In the code behind, copy paste this: (change names as needed to compile)
using System;
using System.Threading;
using System.Windows.Forms;
namespace WinformsExample
{
public partial class HelloWorldView : Form
{
private readonly HelloWorldController MyHelloWorldController;
public HelloWorldView()
{
InitializeComponent();
MyHelloWorldController = new HelloWorldController();
}
private void button1_Click(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequested();
if (MyHelloWorldController.IsAutomated)
{
Thread.Sleep(2000);
button1.PerformClick();
}
}
private void HelloWorldView_Load(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequestedEvent += OnHelloWorldRequested;
}
private void OnHelloWorldRequested(HelloWorldParameters parameters)
{
textBox1.Text += parameters.HelloWorldString + Environment.NewLine;
textBox1.Update();
}
private void button2_Click(object sender, EventArgs e)
{
MyHelloWorldController.IsAutomated = !MyHelloWorldController.IsAutomated;
if (MyHelloWorldController.IsAutomated)
{
button2.Text = "hello world - is on";
button2.Update();
button1.PerformClick();
}
else
{
button2.Text = "hello world - is off";
button2.Update();
}
}
}
}
And create a class titled HelloWorldController.cs and copy paste this in to it:
namespace WinformsExample
{
public class HelloWorldParameters
{
public string HelloWorldString { get; set; }
}
public delegate void HelloWorldEventHandler(HelloWorldParameters parameters);
public class HelloWorldController
{
private readonly HelloWorldParameters _parameters;
public event HelloWorldEventHandler HelloWorldRequestedEvent;
public bool IsAutomated { get; set; }
public HelloWorldController()
{
_parameters = new HelloWorldParameters();
}
public void HelloWorldRequested()
{
_parameters.HelloWorldString = "Hello world!!";
if (HelloWorldRequestedEvent != null)
HelloWorldRequestedEvent(_parameters);
}
}
}
...go ahead and rename things if you need to. Now build the program. Click the first button. You will see "hello world". Now click the second button, you will see "hello world" printed every 2 seconds.
The way I thought this would work is that by clicking button2 a second time, that it would stop the autoplay. However, the UI is unresponsive and the button click event never happens.
What is going on here that is causing the UI to be unresponsive and how can I fix it so that I get the intended behavior?
*UPDATE - HERE IS THE SOLUTION *
Keep everything the same as above except for HelloWorldView.cs. Remove the call to Thread.Sleep(). Drag and drop a timer from the toolbox to the design surface. You will see an icon on the bottom of the designer surface labeled
timer1
Copy paste the following code in to HelloWorldView.cs. Compile and execute. If everything is correct you should be able to turn on and off the "hello world" display by clicking the button at any time - the UI stays responsive.
using System;
using System.Windows.Forms;
namespace WinformsExample
{
public partial class HelloWorldView : Form
{
private readonly HelloWorldController MyHelloWorldController;
public HelloWorldView()
{
InitializeComponent();
MyHelloWorldController = new HelloWorldController();
}
private void onTimerTick(object sender, EventArgs e)
{
button1.PerformClick();
}
private void OnHelloWorldRequested(HelloWorldParameters parameters)
{
textBox1.Text += parameters.HelloWorldString + Environment.NewLine;
textBox1.Update();
}
private void HelloWorldView_Load(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequestedEvent += OnHelloWorldRequested;
}
private void button1_Click(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequested();
}
private void button2_Click(object sender, EventArgs e)
{
MyHelloWorldController.IsAutomated = !MyHelloWorldController.IsAutomated;
if (MyHelloWorldController.IsAutomated)
{
button2.Text = "hello world - is on";
button2.Update();
timer1.Interval = 2000;
timer1.Tick += onTimerTick;
timer1.Start();
}
else
{
timer1.Stop();
button2.Text = "hello world - is off";
button2.Update();
}
}
}
}
WinForms uses a single message pump thread (called the UI thread). (If you are unfamiliar with the concept you should research Windows messages and Windows message pump).
Thread.Sleep causes the currently executing thread the sleep, or pause, for a time. This sleep/pause is like death to the thread - it is aware of nothing and unable to do anything.
As the currently executing thread in a WinForms app is usually the UI thread - Thread.Sleep will cause the UI to become unresponsive because it is no longer able to pump messages.
An alternative design would be to use a form-based Timer. Place your game playing code in the Timer's Tick event.
What is going on here that is causing the UI to be unresponsive and how can I fix it so that I get the intended behavior?
There are essentially two reasons why your app becomes unresponsive.
1. Thread.Sleep() in UI thread
GUI applications on Windows are generally driven by messages (mouse clicks; keyboard; screen drawing) posted to it which are placed on a queue. The UI thread processes these messages one by one dispatching the message to the appropriate handler. In this way it is known as the Message Pump. If during processing one of these messages too much time elapses, then the UI will appear to freeze. Event handlers should be as fast as possible.
During your click handlers you are using Thread.Sleep(2000); which will prevent the UI thread from updating the UI of your application, in essence simulating an event handler that takes far too long to process an event. It is perhaps no different to say performing a lengthy database or WCF operation on the UI thread, hence why people tend to put such calls on a separate thread or task.
Recommend you remove the Thread.Sleep and replace it with a timer as others have indicated.
2. Infinite Recursive Loop on button1 handler
When you click button2 for the first time, the click handler for button2 is invoked where automation is enabled. You then simulate button1 being clicked via button1.PerformClick();.
During the call to button1.PerformClick, the click handler for button1 button1_Click() is invoked. It is there that you sleep for 2 seconds (which isn't healthy for the UI) but the secondary problem is that you immediately call button1.PerformClick(); from inside the button1 click handler, in essence setting up an infinite recursive loop.
If you were to remove the Thread.Sleep(2000) your app will eventually lead to a StackOverflowException. Your code as it stands now (even with the sleep) will still overflow, it's just that it will take much longer to become apparent.
Again, consider replacing it with a timer.
3. Exclusivity
It's important to note that ignoring the stack fault for a moment, the design is such that your app can't do anything else whilst this infinite loop is running. So if your game had other buttons to click; scores to display; sound effects to play; all from the point of view of the button2 handler, most likely it will never happen because it is too busy exclusively processing button1.
Conclusion
Keep UI responsive: Avoid Thread.Sleep() in your code
Avoid recursion: Don't use PerformClick() for a button whilst you are inside the click handler for said button
Your "Thread.Sleep()" call puts the UI thread to sleep. Use a Timer instead. Then terminate the Timer on the second press. (You could also do this with Tasks, if you want to use another thread you need to make the 2 threads communicate in someway so that the UI thread is the only one actually updating the UI)
Desktop applications have a so called UI thread. It's basically an infinite loop which keeps checking if something happened, such as a mouse click, and redraws the window if needed. Coding in WinAPI you would need to write this loop yourself, WinForms and other UI frameworks hide it away. But your click handler is called from inside this loop. So if your code takes too much time - like, because you call Thread.Sleep inside - the loop will not continue and will not be able to process anything that is happening to the application. This why long-running processes need to take place on a separate thread.
As others have said, you are blocking the UI thread with the Thread.Sleep and recursive button1.PerformClick(); call. You have to let the UI run as freely as possible and let it go idle quickly.
So, just for the fun of it I have rewritten your code to do just that. I've also implemented it with Microsoft's Reactive Extensions (Rx) - just NuGet "Rx-WinForms" to get the bits. Rx allows you to do some very funky things that you can't easily do with events.
Here's your form now:
public partial class HelloWorldView : Form
{
private readonly HelloWorldController MyHelloWorldController =
new HelloWorldController("Hello world!!", TimeSpan.FromSeconds(1.0));
public HelloWorldView()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
MyHelloWorldController.Messages
.ObserveOn(this)
.Subscribe(message =>
{
textBox1.Text += message + Environment.NewLine;
});
MyHelloWorldController.IsAutomateds
.ObserveOn(this)
.Subscribe(isAutomated =>
{
button2.Text = "hello world - is " + (isAutomated ? "on" : "off");
});
}
private void button1_Click(object sender, EventArgs e)
{
MyHelloWorldController.Trigger();
}
private void button2_Click(object sender, EventArgs e)
{
MyHelloWorldController.IsAutomated = !MyHelloWorldController.IsAutomated;
}
}
You'll notice that I've simplified down the UI. It really does as little as possible to update itself and to notify the HelloWorldController of its actions.
The worst part of the code are the two .Subscribe calls in Form1_Load. These are simply looking at the two observables (Rx's version of events if you like) and makes sure the events are run on the UI thread with the .ObserveOn(this) call, and then they subscribe to values produced from the HelloWorldController.
The UI is simply updating itself from the controller and telling the controller what it is doing. There is virtually no logic being performed in the UI. This is how it should be with any MVC-style coding.
Now the HelloWorldController is where the fun is.
It starts off pretty simply:
private string _message;
private TimeSpan _automatedPeriod;
public HelloWorldController(string Message, TimeSpan automatedPeriod)
{
_message = Message;
_automatedPeriod = automatedPeriod;
}
This is basically the information about what message to send to the UI and how often when the controller is automating the values.
It then tracks whether it is automated or not:
private bool _isAutomated = false;
Now it contains the Rx observables - these are like the events you were using.
private Subject<string> _messages = new Subject<string>();
public IObservable<string> Messages { get { return _messages.AsObservable(); } }
private Subject<bool> _isAutomateds = new Subject<bool>();
public IObservable<bool> IsAutomateds { get { return _isAutomateds.AsObservable(); } }
private SerialDisposable _serialSubscription = new SerialDisposable();
In Rx an IObservable<T> is something I can subscribe to to get a series of values - just like an event. The Subject<T> is something that I can manually push values into, but it also can be an IObservable<T> that can be subscribed to. It's the pair of these that lets me raise events. Think of the Subject<string> to be the equivalent of the HelloWorldRequested method in your code and the IObservable<string> to be the equivalent of the HelloWorldRequestedEvent event.
If I call _messages.OnNext("Hello") then any subscribers to IObservable<string> Messages would get a "Hello" sent to them. Just like an event.
IsAutomated looks like this:
public bool IsAutomated
{
get { return _isAutomated; }
set
{
_isAutomated = value;
_isAutomateds.OnNext(value);
if (_isAutomated)
{
this.Trigger();
}
}
}
So it does its job of updating its own internal state, but it also calls _isAutomateds.OnNext(value) to push out the updates to any subscribers of IObservable<bool> IsAutomateds. It also works out if it needs to trigger the controller to produce messages with the this.Trigger() call.
Finally the Trigger method looks like this:
public void Trigger()
{
if (_isAutomated)
{
_serialSubscription.Disposable =
Observable
.Interval(_automatedPeriod)
.StartWith(0)
.TakeUntil(_isAutomateds.Where(x => x == false))
.Subscribe(n => _messages.OnNext(_message));
}
else
{
_messages.OnNext(_message);
}
}
The easy part of this is when the _isAutomated is false then it simply sends one message out via the _messages.OnNext(_message) call.
When _isAutomated is true it uses some of the coolness of Rx to set up effectively a timer to produce values every TimeSpan _automatedPeriod. From your code you wanted every 2 seconds so the TimeSpan would be TimeSpan.FromSeconds(2.0).
Observable.Interval(_automatedPeriod) defines a timer that begins producing values after the first period of time and then every period of time between.
So the .StartWith(0) says that it should immediately produce a value when it is subscribed to.
The .TakeUntil(_isAutomateds.Where(x => x == false)) is the best part here - it says that it will take the values from the the Observable.Interval(_automatedPeriod).StartWith(0) and stop when it gets a value from _isAutomateds.Where(x => x == false) - in other words when the IsAutomated is set to false.
The .Subscribe(n => _messages.OnNext(_message)); simply pushes a value to the _messages subject so that all subscribers of IObservable<string> Messages gets their messages.
Just put all of the HelloWorldController I've given you in public class HelloWorldController { ... } and you're good to go.
The works I think like it should and shows how lightweight the UI code can be.
I hope you find this worth playing with.
You'll need to add these using's to the top of your code to get all of the code to compile:
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;

C# Random number generator stuck in loop

I just started programming after taking a long break and am running into a little issue.
I am using VS2013 Desktop and am creating this as a GUI program.
My problem is that I created a working random number generator when the method used to call the logic method runs once. The number(s) gets generated, text updated and all is good. When it goes into a loop, it doesn't update the text property of the object I'm modifying until it finishes the entire loop or gets broken. The program basically hangs when I run it when the loop gets executed and I have to force it to close.
At the moment I would like to set the generator to run infinitely in the background until I press another button to stop it.
I am new to programming and this probably has all sorts of issues with it so I would be grateful for any feedback on structure and other practices if anything is out of order as well.
Here is the code:
Form1.cs
// Global
bool boolLooper;
// Setting up the random number generator
private string RandomNumber()
{
RandomNumber rndNumber = new RandomNumber();
string strRandNumber = Convert.ToString(rndNumber.RandomInt(1000, 9999999));
return strRandNumber;
}
// TEST - used in buttonclick event
private void TextUpdates()
{
while (BoolLooper == true)
{
txtID1.Text = RandomNumber();
//txtName1.Text = RandomNumber();
//txtSize1.Text = RandomNumber();
//txtKey1.Text = RandomNumber();
//txtType1.Text = RandomNumber();
}
}
//-----------------------------
// Form - Button Clicks
//-----------------------------
// Button - Activate
private void btnActivate_Click(object sender, EventArgs e)
{
BoolLooper = true;
TextUpdates();
//// Update text once
//txtID1.Text = RandomNumber();
//txtName1.Text = RandomNumber();
//txtSize1.Text = RandomNumber();
//txtKey1.Text = RandomNumber();
//txtType1.Text = RandomNumber();
}
// Button - Stop/Deactivate
private void btnDeactivate_Click(object sender, EventArgs e)
{
BoolLooper = false;
}
//-----------------------------
// Properties
//-----------------------------
public bool BoolLooper
{
get { return boolLooper; }
set { boolLooper = value; }
}
RandomNumber.cs
private static readonly Random intRandom = new Random();
private static readonly object syncLock = new object();
public int RandomInt(int minNum, int maxNum)
{
lock (syncLock)
{
// synchronize
return intRandom.Next(minNum, maxNum);
}
}
For the RandomNumber class, I found a great post on this site found here which I will give credit to it's author: https://stackoverflow.com/a/768001
You're running this code on the same thread as the UI. Since it's single-threaded, the UI can't respond because it's busy running your loop. You'll want to off-load this to a separate thread or in some way as a separate asynchronous operation. That thread/operation would then just need to tell the UI of updates when it has them.
A simple example of this would be to use a BackgroundWorker object.
Note in the example on that page where the BackgroundWorker exposes an event which can be used to update UI elements:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = (e.ProgressPercentage.ToString() + "%");
}
There are other possible approaches as well. You could create a Thread manually and try to synchronize it manually, but that comes with other potential issues as well. And there really isn't a need to get that complex here.
Do you need the TextBox to be constantly updating? Or just updating every once in a while? If there's some discernible time period between updates (one second?) then you can use a Timer to schedule the code to take place periodically. The structure is similar to the BackgroundWorker in that there's an event exposed which would be used to update the UI.
All your code is being executed on the UI thread. So you're stuck in your while loop, and the form isn't responding to the button click (which sets your while loop flag back to false). This is what we call a blocking call. It's blocking the UI from continuing.
Typically in situations like this, you would want to look into threading. However, based on your code. I'd look into a timer, and have it tick every second or so. They're very easy to implement and you can remove the complexity of your while loop and just execute the random number generation and the assigning it to your UI controls. (This also makes it so that you don't have to marshal from a background thread back onto your UI thread.)
For more information on a timer:
System.Windows.Forms.Timer
You basically need to run each call to generate a new number asynchronously. Using the .NET Framework, there are several ways to achieve that, but I prefer to use the Task class. You could do something like this:
public Task RunAsynchronously(Action method)
{
return Task.Factory.StartNew(method);
}
...
RunAsynchronously(() => MethodThatGeneratesRandomNumber());
Each time this is called, the method execution will run asynchronously.
if you are using .NET 4.5, update the TextUpdates method to use the async/await call like in the example below
private async void TextUpdates()
{
await Task.Run(() =>
{
while (BoolLooper)
{
txtID1.Invoke((MethodInvoker)(() => txtID1.Text = RandomNumber()));
//txtName1.Text = RandomNumber();
//txtSize1.Text = RandomNumber();
//txtKey1.Text = RandomNumber();
//txtType1.Text = RandomNumber();
}
});
}
You are creating new instance of a RandomNumber class each time. Just make it a member of your class. Like :
// Global
bool boolLooper;
//Random number generator
RandomNumber rndNumber = new RandomNumber();
and don't need to create new instance in method RandomNumber , just change it to this:
private string RandomNumber()
{
string strRandNumber = Convert.ToString(rndNumber.RandomInt(1000, 9999999));
return strRandNumber;
}
UPDATE: I've read a bit about Application.DoEvents() after comment, so use Invokes, await calls of Tasks, others, but not this.

Reentrant Timer in Windows Service

I want to build a windows Service, which should execute different methods at different times. Its not about accuracy at all.
Im using a system.timers.timer, and regulate the different methods to be executed within the Eventhandler-method with counters. Thats working allright that far.
All of the methods are accessing a COM-port, making it neccessary to grant acceess-rights to only one method at a time. But since the methods can take some time to finish, the timer might tick again and want to execute another method while the COM-port is still being occupied. In this case, the event can and should just be dismissed.
Simplified down to one method, my elapsedEventHandler-method looks something like the following (try-catch and the different methods excluded here)
Note: While this is running perfectly on my Win7 x64, it struggles on a Win7 x86 machine with pretty much the very same software installed, whenever the method to be executed takes a long time. The timer wont tick any more, no Exception is thrown. Nothing! my question now is: Am I doing the part with access-control and the timer right, so that i can focus on other things? Im just not that familiar with timers and especially threading
private static int m_synchPoint=0;
private System.Timers.Timer timerForData = null;
public MyNewService()
{
timerForData = new System.Timers.Timer();
timerForData.Interval = 3000;
timerForData.Elapsed += new ElapsedEventHandler(Timer_tick);
}
//Initialize all the timers, and start them
protected override void OnStart(string[] args)
{
timerForData.AutoReset = true;
timerForData.Enabled = true;
timerForData.Start();
}
//Event-handled method
private void Timer_tick(object sender, System.Timers.ElapsedEventArgs e)
{
////safe to perform event - no other thread is running the event?
if (System.Threading.Interlocked.CompareExchange(ref m_synchPoint, 1, 0) == 0)
{
//via different else-ifs basically always this is happening here, except switching aMethod,bMethod...
processedevent++;
Thread workerThread = new Thread(aMethod);
workerThread.Start();
workerThread.Join();
m_synchPoint=0;
}
else
{
//Just dismiss the event
skippedevent++;
}
}
Thank you very much in advance!
Any help is greatly appreciated!
I would recommend using System.Threading.Timer for this functionality. You can disable the timer when it executes, process your data, then re-enable the timer.
EDIT:
I think it makes more sense to use System.Threading.Timer because there isn't really a reason you need to drop the timer on a design surface, which is pretty much the only reason to use System.Timers.Timer. I really wish MS would remove it anyways, it's wrapping System.Threading.Timer which isn't all that difficult to use in the first place.
Yes, you do risk a problem with re-entrancy which is why I specified to change the timeout toTimeout.Infinite. You won't have this re-entrancy problem if you construct the timer with Timeout.Infinite.
public class MyClass
{
private System.Threading.Timer _MyTimer;
public MyClass()
{
_MyTimer = new Timer(OnElapsed, null, 0, Timeout.Infinite);
}
public void OnElapsed(object state)
{
_MyTimer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("I'm working");
_MyTimer.Change(1000, Timeout.Infinite);
}
}
If you want just skip method invocation while previous method didn't finish just use Monitor.TryEnter(lockObject) before calling your method.
EDIT:
Here's an example -
public class OneCallAtATimeClass
{
private object syncObject;
public TimerExample()
{
syncObject = new object();
}
public void CalledFromTimer()
{
if (Monitor.TryEnter(syncObject);)
{
try
{
InternalImplementation();
}
finally
{
Monitor.Exit(syncObject);
}
}
}
private void InternalImplementation()
{
//Do some logic here
}
}
You can try this:
When the timer fires, disable the timer.
When the task is complete, re-enable the timer...possibly in the Finally clause.
You correctly use CompareExchange to test and set the m_synchPoint field when doing the initial check. You incorrectly use direct assignment to reset the value to 0 at the end of the method. You should use Interlocked.Exchange instead to reset the value to 0. As a side note, you should also change m_synchPoint to an instance field -- it should not be static.

Stop loop in class from another class

So I have two event handlers button1_Click() and button2_Click()
In button1_Click() I have something running like this:
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
//DoStuff
}
But button2_Click is supposed to be a stop button, and stop button1 early.
How do I go about this?
Thanks for the help. I saw this article here about it, but couldn't get it to work.
Windows.Forms answer
The least sophisticated method is this:
private bool m_stop;
private void button1_Click (object s, EventArgs ea)
{
try
{
// Don't forget to disable all controls except the ones you want a user to be able to click while your method executes.
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
Application.DoEvents ();
// DoEvents lets other events fire. When they are done, resume.
if (m_stop)
break;
//DoStuff
}
finally
{
// Enable the controls you disabled before.
}
}
private void button2_Click (object s, EventArgs ea)
{
m_stop = true;
}
It has the distinct advantage of letting you execute button1_Click on the UI thread, still lets the UI respond to your stop button.
It has a disadvantage that you must protect against reentrancy. What happens if they click your button1 while button1_click is already executing!?!?
Edit: Another way I have used is to use a Timer instead of a loop. Then, the stop method just stops the timer.
As much as I understood, correct me if I'm wrong, you're on single thread.
Wired, but you can check for single boolean value inside the your While loop, just as post suggested.
May be to make life easier (may be this is what "couldn't get it to work" means) is inside loop call
1) Windows Forms: Application.DoEvents()
2) WPF (little bit more tricky) : DoEvents in WPF
This to make breathe system.
You need to start the process inside the button1 in new thread, and when you press the button2 flag a local variable to false to stop the loop. like:
using System.Threading;
private volatile bool _requestStop = false;
private readonly object _oneExecuteLocker = new object();
private void OnButton1Click(ojbect sender, EventArgs e)
{
new Thread(() =>
{
if (Monitor.TryEnter(_oneExecuteLocker))
{//if we are here that is means the code is not already running..
try
{
while (!_requestStop)
{
//DoStuff
}
}
finally
{
Monitor.Exit(_oneExecuteLocker);
}
}
}){ IsBackground = true }.Start();
}
private void OnButton2Click(object sender, EventArgs e)
{
_requestStop = true;
}
Notes:
When ever you want to update a UI control inside the newly created thread you should use contorl.Invoke(/*the code get/set or call method in the UI*/).
The Monitro.Enter is just to be sure that your code will not executed multiple time per click if it already running.

Events being sent to disabled control

I have a problem that I'd like some advice on. I have a button in my GUI that starts a complicated setup sequence (to connect to a analogue to digital converter and start logging data from an echo sounder). Once it is finished setting up, the button changes from START to STOP and has the expected behaviour. What I was experiencing is that during the long operation, if the user clicked on the button again (even though it was disabled) the event would still be sent to the button once it was reenabled. The only way I've found to make this work properly is to call Application.DoEvents() before enabling the button again. All I really want to do is swallow up the events destined for my button, so DoEvents() seems a bit heavy handed. Since people seem to be unanimously against calling DoEvents() I'm hoping that the bright minds here can help me come up with an alternative solution. Note I haven't tried my demo code but it follows my real code closely, excepting the really long methods.
Is there an alternative way to accomplish this?
Is it safe(ish) to call DoEvents() from the completion portion of the background worker?
public class Form1 : Form
{
BackgroundWorker worker;
Button startButton;
bool state;
public Form1() {
state = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(StartSequence);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ToggleButton);
startButton = new Button();
startButton.Text = "START";
startButton.Click += new System.EventHandler(StartClicked);
this.Controls.Add(startButton);
}
private void StartClicked( object sender, EventArgs e ) {
startButton.Enabled = false;
worker.RunWorkerAsync( !state );
}
private void StartSequence( object sender, DoWorkEventArgs e ) {
bool onState = (bool) e.Argument;
if ( onState ) {
RunReallyLongStartupSequence();
}
else {
RunReallyLongStopSequence();
}
state = onState;
}
private void ToggleButton( object sender, RunWorkerCompletedEventArgs e ) {
startButton.Text = state ? "STOP" : "START";
// THIS IS WHAT I AM WORRIED ABOUT!
Application.DoEvents();
startButton.Enabled = true;
}
}
Application.DoEvents() isn't heavy-handed, and I don't think programmers are unanimously opposed to DoEvents in all cases. DoEvents has a bad reputation because it has traditionally been used as a magical hack fix for badly-written code. In your case, it is the proper and normal way to deal with your situation.
In Windows, the situation you describe (where clicks on disabled buttons are applied when the buttons are re-enabled) is actually normal, expected behavior for a control. However, this does not mean that it is always desirable behavior from a programmer's standpoint, and if you have users that are prone to clicking away on disabled buttons, then the simplest way is to use DoEvents, which is nothing more than a method telling the form to go ahead and process any events it has queued up. Use it proudly!
First of all, I do not find DoEvents() bad at all, especially when it comes to gui, you can't imagine how many times it has helped me , and if you create things like progress bars, it is pretty much mandatory if you want to see it update, all I'm saying is that I don't really get why it is considered a bad command.
On the topic though, what I usually do in such situations is not disable the control at all
I use something similar to the following
public Class myForm :Form
{
private bool _working = false;
public myForm()
{
_working = true;
//code here
this.btnDoStuff.Click += new System.EventHandler(DoStuffClick);
_working = false;
}
private void DoStuffClick(object sender, EventArgs e)
{
if (_working) return;
_working = true;
DoStuff();
_working = false;
}
private void DoStuff()
{
//your code goes here
}
}
I find that the above helps me when it comes to allowing the user to do only one thing at a time, and if I want to be able to allow the user to do multiple stuff( for example while the command executes , to be able to press other buttons) I usually put the DoStuff() code to execute in a different thread
In case you were wandering, the reason I use an extra method (DoStuff()) to perform the actions, is that sometimes I need to execute code form other methods in one method, and if the _working flag is set to true, I can't call DoStuffClick(null , new EventArgs()) as it will not do anything
I hope I helped
p.s. yes I know it's been two years, but I only joined stackoverflow the other day (:

Categories

Resources