views:

1056

answers:

8

I need to be able to delay the event handlers for some controls (like a button) to be fired for example after 1 sec of the actual event (click event for example) .. is this possible by the .net framework ?

I use a timer and call my code from the timer's tick event as below but I am not sure if this is the best approach !

void onButtonClick( ..)
{
   timer1.Enabled = true;
}

void onTimerTick( ..)
{
   timer.Enabled = false; 

   CallMyCodeNow();
}
+2  A: 

Before coming to your question, just having read the summary bit from the main questions page, a timer was exactly what I was going to suggest.

This looks pretty clean to me. It means you can easily "cancel" the delayed event if you need to, by disabling the timer again, for example. It also does everything within the UI thread (but without reentrancy), which makes life a bit simpler than other alternatives might be.

Jon Skeet
A: 

You can use:

Thread.Sleep(1000);

That will pause the current Thread for one second. So I would do that...

Best Regards!

MRFerocius
That would cause the UI thread to hang, which is never a good thing really. Using a Timer will allow the message pump to process.
Ed Swangren
+7  A: 

Perhaps you could make a method that creates the timer?

void onButtonClick(object sender, EventArgs e)
{
    Delay(1000, (o,a) => MessageBox.Show("Test"));
}

static void Delay(int ms, EventHandler action)
{
    var tmp = new Timer {Interval = ms};
    tmp.Tick += new EventHandler((o, e) => tmp.Enabled = false);
    tmp.Tick += action;
    tmp.Enabled = true;
}
Bengt
Fixed a possible typo. Pleas roll back if I made a mistake.
strager
Rolled back, you can't use the same name for the lambda parameter.
Bengt
Ah, right. Sorry. xD
strager
There is a memory leak here. EventHandlers don't get unregistered.
Boris Lipschitz
@Boris: how to solve that memleak issue?
zerkms
@zerkms: I don't think you can do anything. however, see my solution added yesterday, which is based on System.Threading.Timer
Boris Lipschitz
but here - after timer triggered once and Enabled = false - wouldn't the GC collect it as unneeded?
zerkms
+1  A: 

If you're only doing this for one control, the timer approach will work fine. A more robust approach supporting multiple controls and types of events looks something like this:

class Event
{
   public DateTime StartTime { get; set; }
   public Action Method { get; set; }

   public Event(Action method)
   {
      Method = method;
      StartTime = DateTime.Now + TimeSpan.FromSeconds(1);
   }
}

Maintain a Queue<Event> in your form and have UI events that need to be delayed add them to the queue, e.g.:

void onButtonClick( ..)
{
   EventQueue.Enqueue(new Event(MethodToCall));
}

Make your timer tick 10 times a second or so, and have its Tick event handler look like this:

void onTimerTick()
{
   if (EventQueue.Any() && EventQueue.First().StartTime >= DateTime.Now)
   {
      Event e = EventQueue.Dequeue();
      e.Method;
   }
}
Robert Rossney
A: 

If you're looking for a more fancy solution, you may want to take a look at my Reactive LINQ project. The link doesn't show how to solve the particular problem you're having, but it should be possible to solve in quite an elegant style using the technique described there (in the whole 4-article series).

Tomas Petricek
A: 

I personally would spawn a new thread and then use Thread.Sleep inside of it. Assuming your event handler code is appropriately multi-thread compatible (which certainly helps with responsive UIs) this would probably be the best solution.

Domenic
A: 

My solution uses System.Threading.Timer:

public static class ExecuteWithDelay
{
    class TimerState
    {
        public Timer Timer;
    }

    public static Timer Do(Action action, int dueTime)
    {
        var state = new TimerState();
        state.Timer = new Timer(o =>
        {
            action();
            lock (o) // The locking should prevent the timer callback from trying to free the timer prior to the Timer field having been set.
            {
                ((TimerState)o).Timer.Dispose();
            }
        }, state, dueTime, -1);
        return state.Timer;
    }
}
Boris Lipschitz
A: 

For those limited to .NET 2.0, here is another take on Bengt's helpful solution:

/// <summary>
/// Executes the specified method in a delayed context by utilizing
/// a temporary timer.
/// </summary>
/// <param name="millisecondsToDelay">The milliseconds to delay.</param>
/// <param name="methodToExecute">The method to execute.</param>
public static void DelayedExecute(int millisecondsToDelay, MethodInvoker methodToExecute)
{
    Timer timer = new Timer();
    timer.Interval = millisecondsToDelay;
    timer.Tick += delegate
                      {
                          // This will be executed on a single (UI) thread, so lock is not necessary
                          // but multiple ticks may have been queued, so check for enabled.
                          if (timer.Enabled)
                          {
                              timer.Stop();

                              methodToExecute.Invoke();

                              timer.Dispose();
                          }
                      };

    timer.Start();
}
Steve Cadwallader