tags:

views:

1628

answers:

4

I need to be able to disable a button for 1.5 seconds at a time for an application I'm writing. An image is displayed, a user clicks a button, and then another image is displayed. I need to make sure that the user doesn't click the button again too quickly.

So, when the image is displayed, I call this function:

    //when a new image is displayed, start the timer and disable the 'done' button
    //for 1.5 seconds, to force people to stop pressing next so quickly
    System.Timers.Timer mTimer;
    void TimerStart() {
        Done.IsEnabled = false;

        mTimer = new System.Timers.Timer();
        mTimer.Interval = 1500;
        mTimer.Start();
        mTimer.Elapsed += new System.Timers.ElapsedEventHandler(TimerEnd);
    }

The TimerEnd code looks like:

    void TimerEnd(object sender, EventArgs eArgs) {
        if (sender == mTimer){
            Done.IsEnabled = true;
            mTimer.Stop();
        }
    }

The 'Done.IsEnabled' line gets hit, but the button is not reenabled and the timer doesn't stop firing. What am I doing wrong here? If it matters, this is a WPF app.

A: 

The timer event is raised on a different thread. When working with the winforms controls, you need to make sure you Invoke them from the same thread where they were called.

Joel Coehoorn
WPF, not winforms.
mmr
still applies to WPF
Abhijeet Patel
+2  A: 

Use DispatcherTimer instead

DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(someInterval);
timer.Tick += new EventHandler(someEventHandler);
timer.Start();

private void someEventHandler(Object sender, EventArgs args)
{
//some operations
//if you want this event handler executed for just once
// DispatcherTimer thisTimer = (DispatcherTimer)sender;
// thisTimer.Stop();
}
Sorantis
Good call. Thanks.
mmr
+2  A: 

Basically you are trying to debounce the button, to prevent too quick clicks. Rather than use a timer save the previous click time in millis, if the button is clicked again within a short time ignore the next event.

whatnick
An interesting idea, and certainly a reasonable solution as well.
mmr
A: 

When working with WPF there is no guarantee that updates made to UI controls on non-UI threads will work as expected. In many cases you will get an exception when you do this.

In your Timer elapsed handler you need to use the BeginInvoke/EndInvoke paradigm and put your button enabling logic in there to ensure that this code runs on the UI thread instead of Begin/End Invoke

There is a SynchnornizationContext available as well which can be accessed by calling SynchronizationContext.Current . You'll need to cache this before you make the timer call since SynchronizationContext.Current will be null in non-UI threads.

This link talks about this as well.

Abhijeet Patel
WPF, not winforms.
mmr
Regardless. The Begin/Invoke part still applies. The WindowsFormsSynchronizationContext.Send doesn't apply. I'll edit my answer
Abhijeet Patel
@Abhijeet-- maybe, but when I use the timer code in the accepted answer without invoking or using any other threading code, it works. Therefore, I think your answer is either wrong, or doesn't apply.
mmr
@MMR:The accepted solution is definitely the right approach when using timers.That said the DispatcherTimer handles the routing of work to UI thread(such as when updating controls).If you were to use child threads (which a regular Timer does implicitly) as you have you would still be responsible for performing the marshaling yourself.Check out "Using the Dispatcher" section on MSDN:http://msdn.microsoft.com/en-us/magazine/cc163328.aspx#S3Also check out http://code.dortikum.net/2008/08/06/timer-vs-dispatchertimer-in-wpf/I would do my homework before pointing out that an answer is wrong!
Abhijeet Patel