Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect MouseWheel event is ended in WPF

Tags:

c#

wpf

When I scroll mouse wheel several MouseWheel events are fired. And I'm using these events to scale some image.

I want to call a method the moment the series of MouseWheel events are ended. How can I know when they end?

Here is my implementation so far

private void ModelWindowBorder_MouseWheel(object sender, MouseWheelEventArgs e)
{

  intervaltimer = null;

  // Do stuff like zooming and etc

  CheckEventInterval()

}

private void CheckEventInterval()
{
    intervaltimer = new Stopwatch();
    intervaltimer .Start();
    if (intervaltimer.ElapsedMilliseconds > 50)
    {
        // Do some other stuff
    }
}
like image 669
Vahid Avatar asked Apr 09 '14 17:04

Vahid


2 Answers

Actually as mose wheel rotation is endless there is no special event to notify that the used ended scrolling. However in your case you can just test whether the user stopped scrolling for a short period of time. This can be done with a simple timer:

    //Use dispatcher timer to avoid problems when manipulating UI related objects
    DispatcherTimer timer;
    float someValue = 0;

    public MainWindow()
    {
        InitializeComponent();

        timer = new DispatcherTimer();
        timer.Tick += timer_Tick;
        timer.Interval = TimeSpan.FromMilliseconds(500 /*Adjust the interval*/);


        MouseWheel += MainWindow_MouseWheel;
    }

    void timer_Tick(object sender, EventArgs e)
    {
        //Prevent timer from looping
        (sender as DispatcherTimer).Stop();

        //Perform some action
        Console.WriteLine("Scrolling stopped (" + someValue + ")");

        //Reset for futher scrolling
        someValue = 0;
    }

    void MainWindow_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        //Accumulate some value
        someValue += e.Delta;

        timer.Stop();
        timer.Start();
    }

As you can see MouseWheel event will launch the timer. And if a new MouseWheel event occurs befor the timer fires it will restart the timer. In this way the timer will only fire if there is no wheel events for a specific interval.

like image 64
Dmitry Avatar answered Nov 03 '22 10:11

Dmitry


Here is an alternative approach, which allows you to specify UI element (e.g. canvas, window, control etc.) for which you'd like to detect mouse wheel movements and sensitivity, which is timeout given in milliseconds, after which wheel is considered as inactive (custom stop event is fired):

public sealed class MouseWheelMonitor : IDisposable
{
    private AutoResetEvent _resetMonitorEvent;
    private readonly Dispatcher _dispatcher;
    private readonly UIElement _canvas;
    private readonly int _sensitivity;

    private bool _disposed;
    private volatile bool _inactive;
    private volatile bool _stopped;

    public event EventHandler<MouseWheelEventArgs> MouseWheel;
    public event EventHandler<EventArgs> MouseWheelStarted;        
    public event EventHandler<EventArgs> MouseWheelStopped;

    public MouseWheelMonitor(UIElement canvas, int sensitivity)
    {
        _canvas = canvas;
        _canvas.MouseWheel += (s, e) => RaiseMouseWheel(e);

        _sensitivity = sensitivity;
        _dispatcher = Dispatcher.CurrentDispatcher;
        _resetMonitorEvent = new AutoResetEvent(false);

        _disposed = false;
        _inactive = true;
        _stopped = true;

        var monitor = new Thread(Monitor) {IsBackground = true};
        monitor.Start();
    }

    private void Monitor()
    {
        while (!_disposed)
        {
            if (_inactive) // if wheel is still inactive...
            {
                _resetMonitorEvent.WaitOne(_sensitivity/10); // ...wait negligibly small quantity of time...
                continue; // ...and check again
            }
            // otherwise, if wheel is active...
            _inactive = true; // ...purposely change the state to inactive
            _resetMonitorEvent.WaitOne(_sensitivity); // wait...
            if (_inactive) // ...and after specified time check if the state is still not re-activated inside mouse wheel event
                RaiseMouseWheelStopped();
        }
    }

    private void RaiseMouseWheel(MouseWheelEventArgs args)
    {
        if (_stopped)
            RaiseMouseWheelStarted();

        _inactive = false;
        if (MouseWheel != null)
            MouseWheel(_canvas, args);
    }

    private void RaiseMouseWheelStarted()
    {
        _stopped = false;
        if (MouseWheelStarted != null)
            MouseWheelStarted(_canvas, new EventArgs());
    }

    private void RaiseMouseWheelStopped()
    {
        _stopped = true;
        if (MouseWheelStopped != null)
            _dispatcher.Invoke(() => MouseWheelStopped(_canvas, new EventArgs())); // invoked on cached dispatcher for convenience (because fired from non-UI thread)
    }    

    public void Dispose()
    {
        if(!_disposed)
        {
            _disposed = true;
            DetachEventHandlers();
            if (_resetMonitorEvent != null)
            {
                _resetMonitorEvent.Close();
                _resetMonitorEvent = null;
            }
        }
    }

    private void DetachEventHandlers()
    {
        if (MouseWheel != null)
        {
            foreach (var handler in MouseWheel.GetInvocationList().Cast<EventHandler<MouseWheelEventArgs>>())
            {
                MouseWheel -= handler;
            }
        }
        if (MouseWheelStarted != null)
        {
            foreach (var handler in MouseWheelStarted.GetInvocationList().Cast<EventHandler<EventArgs>>())
            {
                MouseWheelStarted -= handler;
            }
        }
        if (MouseWheelStopped != null)
        {
            foreach (var handler in MouseWheelStopped.GetInvocationList().Cast<EventHandler<EventArgs>>())
            {
                MouseWheelStopped -= handler;
            }
        }
    }
}

And the usage sample:

var monitor = new MouseWheelMonitor(uiElement, 1000);
monitor.MouseWheel += (s, e) => { Debug.WriteLine("mouse wheel turned"); };
monitor.MouseWheelStarted += (s, e) => { Debug.WriteLine("mouse wheel started"); };
monitor.MouseWheelStopped += (s, e) => { Debug.WriteLine("mouse wheel stopped"); };
like image 24
jwaliszko Avatar answered Nov 03 '22 10:11

jwaliszko