Drake
Drake

Reputation: 2703

Throttle an Event Handler

In my WPF application, I have an event handler that gets called on the MouseEnter event of my UI element:

myUiElement.MouseEnter += myEventHandler

I would like to throttle myEventHandler so it doesn't get called more than once every second. How can I do this? Is Rx the best approach just for this? I'm using .NET 4.0 if it makes a difference.

Also, I need to make sure that the MouseLeave event always gets called before the next MouseEnter event; do I need to manage this on my own? Or is the framework already designed so that MouseLeave events will always be called before the next MouseEnter event? What if I have asynchronous code in these event handlers?

Upvotes: 10

Views: 8861

Answers (3)

Justin Pihony
Justin Pihony

Reputation: 67065

Using Rx, you want to use the Sample method or Throttle.

Something like this should work (untested):

Observable
  .FromEventPattern<TextChangedEventArgs>(myUiElement, "MouseEnter")
  .Sample(TimeSpan.FromSeconds(1))
  .Subscribe(x => ... Do Stuff Here ...);

The difference between Sample and Throttle is that Sample will take a value every 1 second no matter when the last value was taken, whereas Throttle will take a value and then wait another 1 second before taking another.

It probably depends on what you are shooting for...

Upvotes: 11

kindasimple
kindasimple

Reputation: 2427

You could use reactive extensions, but you could accomplish this just as easily with a timer.

Set a flag along with a Timer. When the timer tick event fires, set the flag to false, disable the timer, and run the code for your event. Then, in your control event handlers, have the handler code skipped if the flag is set.

bool flag;
DispatcherTimer timer;

public constructor()
{
    timer = new DispatcherTimer();
    timer.Interval = TimeSpan.FromSeconds(1);
    timer.Tick += (s,e) => {
        flag = false;
        timer.Stop()
        DoThrottledEvent();
    }
}

void mouse_enter(object sender, MouseEventArgs args)
{
    if(!flag)
    {
        flag = true;
        timer.Start();
    }
}

void DoThrottledEvent()
{
    //code for event here
}

Reactive extensions introduces an extra dependency, but they are a bit of fun. If you are interested, go for it!

Upvotes: 8

Steven Rands
Steven Rands

Reputation: 5421

Another approach would be to use a private field to keep track of the "time" when the last mouse event occurred, and only continue processing if that time was more than one second ago.

DateTime _lastMouseEventTime = DateTime.UtcNow;

void OnMouseEnter(object sender, MouseEventArgs e)
{
    DateTime now = DateTime.UtcNow;
    if (now.Subtract(_lastMouseEventTime).TotalSeconds >= 1)
    {
        // do stuff...
    }
    _lastMouseEventTime = now;
}

This ensures that "stuff" gets done at least one second apart, which is what I think you were asking for.

Upvotes: 3

Related Questions