【问题标题】:Throttle an Event Handler限制事件处理程序
【发布时间】:2015-03-17 00:54:08
【问题描述】:

在我的 WPF 应用程序中,我有一个事件处理程序,它在我的 UI 元素的 MouseEnter 事件上被调用:

myUiElement.MouseEnter += myEventHandler

我想限制 myEventHandler 使其每秒不会被多次调用。我怎样才能做到这一点? Rx 是最好的方法吗?我正在使用 .NET 4.0,如果它有所作为的话。

另外,我需要确保 MouseLeave 事件总是在下一个 MouseEnter 事件之前被调用;我需要自己管理吗?或者框架是否已经设计为总是在下一个 MouseEnter 事件之前调用 MouseLeave 事件?如果我在这些事件处理程序中有异步代码怎么办?

【问题讨论】:

  • 您是否需要它实际上根本不被调用,或者如果最后一次调用太接近当前时间,您是否可以在您的方法中立即返回?
  • 我想限制它,这样它就不会给线程带来太多负担,以防用户在所有东西上乱扔鼠标(我有许多 UI 元素将具有相同的事件处理程序);但我想我可以做到这一点,如果它比实际限制它更有效..
  • 任何限制机制最终都将涉及 if 语句和继续执行或中止的选择。你无法真正解决这个问题。
  • 我想 - 我想我会接受你的建议,因为它真的很简单。
  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。

标签: c# wpf throttling


【解决方案1】:

使用 Rx,您想使用 the Sample method 或 Throttle。

这样的东西应该可以工作(未经测试):

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

Sample 和 Throttle 之间的区别在于,Sample 将每 1 秒取一个值,无论何时取最后一个值,而 Throttle 将取一个值,然后再等待 1 秒再取另一个值。

这可能取决于您要拍摄的内容...

【讨论】:

  • 为什么选择“Sample”而不是“Throttle”?
  • 我为 Throttle 添加了描述。也许 Throttle 更像是 OP 想要的,但我并没有猜测我只是给出了描述,以便最终用户可以做出自己的决定
  • 效果不佳。 Observable 将生成许多从未被释放的对象。非常适合本地应用程序,不适合扩展或生产。 RX 是癌症
【解决方案2】:

您可以使用响应式扩展,但您也可以使用计时器轻松完成此操作。

设置一个标志和一个计时器。当计时器滴答事件触发时,将标志设置为 false,禁用计时器,然后为您的事件运行代码。然后,在您的控件事件处理程序中,如果设置了标志,则跳过处理程序代码。

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
}

响应式扩展引入了额外的依赖,但它们有点有趣。有兴趣就去吧!

【讨论】:

    【解决方案3】:

    另一种方法是使用私有字段来跟踪上次鼠标事件发生的“时间”,并且仅在该时间超过一秒前才继续处理。

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

    这可确保“事情”至少相隔一秒钟完成,这正是我认为您所要求的。

    【讨论】:

    • 将它用于移动应用程序,没有计时器,也没有任何东西——默默地冒烟——。感谢您提供现成的解决方案。
    • 与计时器的区别:不要在动作之前触发间隔(例如 1 秒),而这表示不要在动作之后触发间隔。假设您按t=0,然后按t=2,然后按t=2,1。然后t=0 会触发,t=2 会触发但t=2,1 不会触发(t=2,2 也不会触发)。很多时候,您希望触发最后一个值。
    • 这正是这个解决方案的问题:很多情况下最后一个值(或鼠标位置)不会被处理。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    相关资源
    最近更新 更多