2

I have event handler:

 private void Control_Scroll(object sender, ScrollEventArgs e)
 {
     UpdateAnnotations();
 }     

Now I wish to update annotations only if user stopped scrolling, like if since last scrolling event passed 100ms, then execute action, else discard it, as it won't matter anyway.

What would be the easiest/reusable way to do that, preferably some static method like public static void DelayedAction(Action action, TimeSpan delay).

Using .NET 4.0.

Giedrius
  • 8,430
  • 6
  • 50
  • 91

5 Answers5

3

See this answer to an Rx (Reactive Extensions) question. (You can use Observable.FromEvent to create an observable from an event.)

Community
  • 1
  • 1
Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
2

I would go with something like this

class MyClass
{
   private System.Timers.Timer _ScrollTimer;
   public MyClass()
   {
       _ScrollTimer= new System.Timers.Timer(100);
       _ScrollTimer.Elapsed += new ElapsedEventHandler(ScrollTimerElapsed);
   }

   private void ResetTimer()
   {
        _ScrollTimer.Stop();
        _ScrollTimer.Start();
   }

   private void Control_Scroll(object sender, ScrollEventArgs e, TimeSpan delay)
    {
        ResetTimer();
    }    

    private void ScrollTimerElapsed(object sender, ElapsedEventArgs e)
    {
        _ScrollTimer.Stop();
        UpdateAnnotations();           
    }
}

Every time the user scrolls, the timer gets reset and only when scrolling stops for 100ms the TimerElapsed gets fired and you can update your annotations.

Zaid Amir
  • 4,727
  • 6
  • 52
  • 101
2

I tried this with several controls on the form at the same time, and it is reusable by outside.

private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
    if (DelayedAction(100, sender))
        UpdateAnnotations();
}

Dictionary<object, Timer> timers = new Dictionary<object, Timer>();
bool DelayedAction(int delay, object o)
{
    if (timers.ContainsKey(o))
        return false;

    var timer = new Timer();
    timer.Interval = delay;
    timer.Tick += (s, e) =>
        {
            timer.Stop();
            timer.Dispose();
            lock(timers)
                timers.Remove(o);
        };

    lock(timers)
        timers.Add(o, timer);

    timer.Start();
    return true;
}

The dictionary is locked, because if a user cannot hit two controls at the same time, a timer might be inserted at the same time as another one is removed.

Larry
  • 17,605
  • 9
  • 77
  • 106
1

Could you not store the time the event was fired (DateTime.Now) and when ever it's called check how long it's been since the last time (e.g. DateTime.Now - lastExecutionTime > minTime)

** Update **

Or a more generic way based on your static helper idea:

public static void DelayedAction(Action action, TimeSpan delay)
{
    var delayedActionTimer = new Timer(x => action(), null, delay, TimeSpan.FromMilliseconds(-1));
}

Needs work obviously... for instance you could store the timer in a field and reset (change) the delay each time the user scrolls

JRoughan
  • 1,635
  • 12
  • 32
1

Try this class:

public class ActionHelper
{
    private static Dictionary<Delegate, System.Threading.Timer> timers =
        new Dictionary<Delegate, System.Threading.Timer>();

    private static object lockObject = new object();

    public static void DelayAction(Action action, TimeSpan delay)
    {
        lock (lockObject)
        {
            System.Threading.Timer timer;
            if (!timers.TryGetValue(action, out timer))
            {
                timer = new System.Threading.Timer(EventTimerCallback, action,
                    System.Threading.Timeout.Infinite,
                    System.Threading.Timeout.Infinite);
                timers.Add(action, timer);
            }
            timer.Change(delay, TimeSpan.FromMilliseconds(-1));
        }
    }

    public static void EventTimerCallback(object state)
    {
        var action = (Action)state;
        lock (lockObject)
        {
            var timer = timers[action];
            timers.Remove(action);
            timer.Dispose();
        }
        action();
    }
}

Features:

  • Thead safe
  • Supports multiple concurrent actions

Usage:

private void Control_Scroll(object sender, ScrollEventArgs e)
{
    ActionHelper.DelayAction(UpdateAnnotations, TimeSpan.FromSeconds(1));
}

Just be aware that the method is called in a separate thread. If you need to do UI work, you need to use Control.Invoke (WinForms) or Dispatcher.Invoke (WPF):

// The method is contained in a Form (winforms)
private void UpdateAnnotations()
{
    if (this.InvokeRequired)
        this.Invoke(new Action(UpdateAnnotations));
    else
    {
        MessageBox.Show("Method is called");
    }
}
Mohammad Dehghan
  • 17,853
  • 3
  • 55
  • 72
  • although I've chosen RX approach, yours is really nice and marked as an answer because for some people RX may be not acceptable solution. – Giedrius Feb 28 '13 at 13:56