Throttle an Event Handler
Asked Answered
C

3

10

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?

Changeless answered 19/1, 2015 at 3:18 Comment(5)
Do you need it actually not to be called at all, or can you just do an immediate return in your method if the last call was too close to the current time?Devoirs
I wanted to throttle it so it doesn't burden the thread so much in case the user spams the mouse over everything (I have many UI elements that will have the same event handler); but I guess I can do that if it's more efficient than actually throttling it..Changeless
any mechanism of throttling is ultimately going to involve an if-statement and a choice to continue execution or abort. You can't really get around that.Devoirs
I suppose - I think I'll go with your suggestion since it's really simple.Changeless
I have edited your title. Please see, "Should questions include “tags” in their titles?", where the consensus is "no, they should not".Balder
L
11

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...

Layfield answered 19/1, 2015 at 3:46 Comment(3)
Why "Sample" instead of "Throttle"?Glucoprotein
I added a description for Throttle. Maybe Throttle is more what the OP wanted, but rather than guessing I just gave the description so that the end user can make their own decisionsLayfield
Doesn't work well. Observable will generate a lot og objects which are never disposed. Great for a local app, not suitable for scaling or production. RX is cancerNickelodeon
S
8

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!

Scarification answered 19/1, 2015 at 3:30 Comment(0)
B
3

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.

Bouley answered 19/1, 2015 at 11:46 Comment(3)
Used it for a mobile app, with no Timers and no nothing -silently fuming-. Thanks for the ready-made solution.Forfend
Difference with timer: dont fire the interval (e.g. 1 second) BEFORE an action, whilst this says dont fire the interval AFTER an action. Suppose you press at t=0, then t=2 and then t=2,1. Then t=0 will fire, t=2 will fire but t=2,1 will not fire (nor would t=2,2). Very often you'd want the last value to fire.Wilton
That's exactly the problem with this solution: The last value (or mouse position) won't be processed in many cases.Wise

© 2022 - 2024 — McMap. All rights reserved.