tags:

views:

593

answers:

7

Hi all,

Did some searches here & on the 'net and haven't found a good answer yet. What I'm trying to do is call a button twice within the same class in C#.

Here's my scenario -

I have a form with a button that says "Go". When I click it the 1st time, it runs through some 'for' loops (non-stop) to display a color range. At the same time I set the button1.Text properties to "Stop". I would like to be able to click the button a 2nd time and when that happens I would like the program to stop. Basically a stop-and-go button. I know how to do it with 2 button events, but would like to utilize 1 button.

Right now the only way to end the program is the X button on the form.

I've tried different things and haven't had much luck so far so wanted to ask the gurus here how to do it.

BTW, this is a modification of a Head First Labs C# book exercise.

Thanks!

~Allen

+3  A: 

Why not create two buttons, hide one when the other is visible? That should be a lot of easier to handle.

Or you can add a bool field to indicate which operation branch to execute.

Adrian Godong
Cool! Didn't know you could do that (hence me being a newb still :) ) I'll try this out!
Valien
Be careful going down the road of hidden controls. It can make working with the winforms designer, one of the biggest advantages of winforms, an exercise in agony when overused at design time.
Greg D
If you go this way, a simple thing to do is position both buttons to be viewable in the designer, then in your Form_Load event set btnStart.Location = btnStop.Location so they are in the same location in your executing form.
Patrick McDonald
Greg D: the possibilities are endless (you can use the same object but different event handler for instance), I'm just pointing out which is the easiest way to implement (code-wise). And of course, IMO a boolean field is the more proper way to do it.
Adrian Godong
@Patrick: You can do it, but it's still painfully non-scalable and (imho) for little or no real benefit.
Greg D
This does not solve one of the overall problems here.Which is that code is run within the GUI loop of the application.Which means when to for() loops are running, the GUI locks up and will not process other GUI events - such as the eventhandler of a Stop button.
nos
@noselasd You don't know if the OP had this locking problem (and it doesn't seem to be implied on the question).
Adrian Godong
+3  A: 

One simple solution would be to add a boolean member to your form that is, e.g., true when the button says "Go" and false when the button says "Stop".

Then, in your button's event handler, check that boolean value. If the value is true, then start your operation and set the value to false when you change the button's text to say "stop". Vice-versa for the other case. :)

There are other techniques that I might prefer if this were production code, perhaps including considering the design of the form more carefully, but as this is clearly a learning exercise I believe that a simple boolean flag indicating the current state of the form is just what you're looking for.

Note that I would strongly discourage you from checking the value of the button text to determine what state the object is in. Whenever possible, as a general rule of good design, you want your visual state to be "decoupled" from your underlying object's state. That is to say, your visual widgets can depend on your underlying objects, but your underlying objects should not depend on your visual widgets. If you tested the text of the button, your underlying logic would depend on your visual state and that would violate this general rule.

If your problem is related to the fact that you can't cancel the operation while it's being performed, you'll want to look into using a BackgroundWorker to perform your long-running activity.

Greg D
A: 

You could set the Tag property on the button to a boolean indicating whether the next action should be "Stop" or "Go", and reset it each time you click the button. It's an Object property, though, so you'll have to cast it to bool when you read it.

Chris Doggett
+1  A: 

Another option would be to check the current text on your button to determine what to do:

void btnStartStop_Click(Object sender, EventArgs e) 
{
    if (btnStartStop.Text == "Go")
    {
        btnStartStop.Text = "Stop";

        // Go code here

    }
    else 
    {
        btnStartStop.Text = "Go";

        // Stop code here

    }
}
Patrick McDonald
Please don't do this (reasoning given in my answer).
Greg D
I personally wouldn't, but it works and its simple
Patrick McDonald
In some cases that's OK... Just you should put the strings into the constant fields, so you wouldn't need to do a change in several places next time.
Yacoder
+7  A: 

You would need to use Multithreading (launch the process intensive code asynchronously in a separate thread), for instance, using the BackgroundWorker object in .NET 2+. This would be necessary because your UI will not respond to the user's click until the loop running in the Start method is completed. It is quite irrelevant if you use the same button or another one to toggle the process, because the processor is busy processing the loop.

The BackgroundWorker has a property called WorkerSupportsCancellation which needs to be true in this scenario. When the user clicks Stop you would invoke the CancelAsync method of the BackgroundWorker.

See MSDN for a good example. Also DreamInCode has a good tutorial which seems quite similar to your requirement.

Cerebrus
Good point! OP may have forgotten to implement this as well.
Adrian Godong
Ah, that sounds like a great idea. Again, I've not come to the BackgroundWorker info yet but will dig into it and see how that works. Thanks for the great input.
Valien
You're welcome, Valien! When you start delving into Multithreading, you will have progressed from the beginner stage to the intermediate stage of competency (Most devs consider proper understanding of Threads to be a milestone). Take it slowly and you should have no problem. :-)
Cerebrus
+1  A: 

Are you getting your second button click event? Put a breakpoint in your click handler and run your code. When you click the second time, do you ever hit your breakpoint?

If your loop is running continuously, and it is in your button click handler, then your loop is running in the UI thread. You probably don't get to "see" the second button click until after the loop is completed. In addition to the branch code that you see above, try either inserting a DoEvents in your loop processing (this is a place where your loop will temporarly give up control so that messages can be processed). Or, (better) have a look at the backgroundworker class -- do most of your processing in a different thread, so that you UI can remain responsive to button clicks.

JMarsch
DoEvents is bad, +1 for backgroundworker class though
Patrick McDonald
@Patrick I agree about DoEvents -- message loop reentrancy is generally not a good thing. It is however a quick way to determine that this is the problem, without requiring a lot of code restructuring. Then he can move to a better solution (background worker, threadpool, etc). Since he identified himself as a newbie, I didn't want to hit him with threads all at once.
JMarsch
+1  A: 

Cerebrus is right about using the Background Worker thread. However if you are doing a WPF app then it won't be able to update the UI directly. To get around this you can call Dispatcher.BeginInvoke on the main control/window.

Given code like:

Private Delegate Sub UpdateUIDelegate(<arguments>)

Private Sub CallUpdateUI(<arguments>)
  control.Dispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Background, New UpdateUIDelegate(AddressOf UpdateUI), <arguments>)
End Sub

Private Sub UpdateUI(<arguments>)
  'update the UI
End Sub

You can call CallUpdateUI from the Background Worker thread and it will get the main thread to perform UpdateUI.

John Donoghue
Just to be precise, the Invoke is necessary even if your not developing a WPF app. It is the cardinal principle of Win32 multithreading - Never update UI on a secondary thread. +1 for the accurate answer.
Cerebrus