-1

I have a WPF app with a MouseWheel event. The operations in this event is quite heavy. So, I would like to execute this event only when the user has stopped scrolling (i.e.: if he doesn't scroll for a given amount of time).

In JS, this is quite easy, I can just put the setTimout in a var and then do a clearTimeout on that var if another scroll happened before the execution of that setTimeout (this is quite useful for auto-completion for instance).

How can I achieve that in c#?

Sébastien
  • 1,667
  • 3
  • 20
  • 31
  • 2
    Stop and restart a DispatcherTimer whenever the event occurs. Do the final event action on the timer's Tick handler. – Clemens Jan 29 '21 at 19:32
  • 1
    The search term to use for this is "Debounce". For example, https://weblog.west-wind.com/posts/2017/jul/02/debouncing-and-throttling-dispatcher-events or https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.bindingbase.delay?view=net-5.0 – Mitch Jan 29 '21 at 20:01
  • Why have you tagged the question with the 'multithreading' tag? Do you want to execute the heavy operations on a different thread than the UI thread? – Theodor Zoulias Jan 29 '21 at 20:11
  • @Clemens, that's perfect, thanks, can you add your comment as a response, so I can accept it? – Sébastien Jan 29 '21 at 21:05
  • @TheodorZoulias, no, just because timer related operations are usualy bound to a new thread... – Sébastien Jan 29 '21 at 21:06

2 Answers2

1

This is quite easy using Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive.Windows.Threading (for WPF) and add using System.Reactive.Linq; - then you can do this:

IObservable<EventPattern<MouseWheelEventArgs>> query =
    Observable
        .FromEventPattern<MouseWheelEventHandler, MouseWheelEventArgs>(
            h => ui.MouseWheel += h, h => ui.MouseWheel -= h)
        .Throttle(TimeSpan.FromMilliseconds(250.0))
        .ObserveOnDispatcher();

IDisposable subscription =
    query
        .Subscribe(x =>
        {
            /* run expensive code */
        });

The docs say this about Throttle:

Ignores the values from an observable sequence which are followed by another value before due time with the specified source and dueTime.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
-2

Something like the following might suit your needs

public class perRxTickBuffer<T>
{
    private readonly Subject<T> _innerSubject = new Subject<T>();

    public perRxTickBuffer(TimeSpan? interval = null)
    {
        if (interval == null)
        {
            interval = TimeSpan.FromSeconds(1);
        }

        Output = _innerSubject.Sample(interval.Value);
    }

    public void Tick(T item)
    {
        _innerSubject.OnNext(item);
    }

    public IObservable<T> Output { get; }
}

Create an instance where T is the event args type for your event.

Set an appropriate timespan value - perhaps 1/4 second for your case.

Just call Tick() from you event handler, and then subscribe to the Output observable for a regulated flow of 'events'.

Peregrine
  • 4,287
  • 3
  • 17
  • 34