C#/WPF合并连续的事件调用

C#/WPF merging consecutive event calls

Win2D 具有此功能 Invalidate(),调用时将重绘整个控件,并且在快速连续调用时,它们将合并为一个更新,其中包含先前调用的任何更改。我正在尝试在我自己的应用程序中重新创建它,但找不到与此描述完全匹配的框架。

比如说,每次我单击图表时,都会添加一条线。我想要发生的是,如果有人连续点击 100 次,它将等到点击完成后立即添加所有行,而不是一次添加一行。同样,调整 window 的大小应该只重绘图表一次,而不是每次触发事件时。

我试过使用 System.Reactive,但他们的大多数 merging/throttling 似乎都忽略了之前的调用,而且我无法在事件上使用订阅的 OnCompleted() 部分作为它永远不会 "complete".

有没有人有解决过这样的问题的经验?我正在研究使用计时器来创建一种延迟,但我想知道是否已经有一些东西可以像描述的那样工作。

这是我解决这个问题的方法。为了便于理解,我将所有内容都放在 Mainwindow.xaml.cs 中,但您可能希望将逻辑移至它自己的 class.

    private int clickCount;
    private DateTime lastClick;
    private System.Windows.Threading.DispatcherTimer clickEventTimer;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (clickEventTimer == null || !clickEventTimer.IsEnabled)
        {
            clickCount = 1;
            lastClick = DateTime.Now;
            clickEventTimer = new System.Windows.Threading.DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
            clickEventTimer.Tick += (timer, args) =>
            {
                if (DateTime.Now - lastClick < TimeSpan.FromSeconds(1))
                {
                    return;
                }
                else
                {
                    clickEventTimer.Stop();
                    MessageBox.Show($"Do stuff for the {clickCount.ToString()} click(s) you got.");
                }
            };
            clickEventTimer.Start();
        }
        else
        {
            clickCount++;
            lastClick = DateTime.Now;
        }
    }

I have tried using System.Reactive, but most of their merging/throttling seems to ignore the previous calls, and I can't use the OnCompleted() part of Subscribe on an event as it does not ever "complete".

我想你错过了一些,比如 Buffer 运算符:

Button button = new Button();

...

var subscription = button
    .ButtonClicks()
    .Buffer(TimeSpan.FromSeconds(0.5), 100) // Perform action  after 100 clicks or after half a second, whatever comes first
    .Select(buffer => buffer.Count)
    .Subscribe(clickCount =>
    {
        // Do something with clickCount
    })

帮手class:

public static class Extensions
{
    public static IObservable<EventPattern<RoutedEventArgs>> ButtonClicks(this Button control)
    {
        return Observable.FromEventPattern<RoutedEventHandler, RoutedEventArgs>(
                h => control.Click += h,
                h => control.Click -= h);
    }
}

申请结束时:

subscription.Dispose();

当然 Buffer 有一些重载:

Buffer(TimeSpan.FromSeconds(1)) -> 缓冲一秒钟,然后执行操作(与接受的答案相同的结果)

Buffer(100) -> 点击 100 次后执行操作

这在 Rx 中非常简单。这就是你需要的:

IObservable<long> query =
    source
        .Select(t => Observable.Timer(TimeSpan.FromMilliseconds(100.0)))
        .Switch();

因此,只要 source 是一个 IObservable<T>,它会在用户执行某个操作时产生一个值,那么这个可观察对象只会在 100.0 出现时产生一个值自最后一个值以来的毫秒不活动时间。

现在,您可能想要整理一下,以便查询生成原始值并编组回调度程序。这也很简单。

试试这个:

IObservable<T> query =
    source
        .Select(t =>
            Observable
                .Timer(TimeSpan.FromMilliseconds(100.0))
                .Select(_ => t))
        .Switch()
        .ObserveOnDispatcher();

简单。