如何在WPF中检测鼠标滚轮事件已结束

6
当我滚动鼠标滚轮时,会触发多个MouseWheel事件。我正在使用这些事件来缩放一些图像。
我想在一系列MouseWheel事件结束时调用一个方法。如何知道它们何时结束?
到目前为止,这是我的实现方式:
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
    }
}

1
没有针对这种情况的内置事件。也许这可以帮助您:http://stackoverflow.com/questions/21234210/how-to-determine-when-the-scrollviewer-has-ended-scrolling - MUG4N
是的,鼠标滚轮在物理上没有开始或停止的概念-它是一个步进器。当它单击时,它会单击。这与所有其他元素不同,例如给出距离。 - TomTom
1
一个秒表不是一个计时器。启动一个新的秒表,然后立即检查它的ElapsedMilliseconds属性,将始终返回一个非常小的值,可能总是零。因此,你的“Do some stuff”将永远不会执行。请按照答案中所示的方法来做。 - Clemens
谢谢Clemens,我之前真的不知道StopWatch()的用法。 - Vahid
2个回答

11

实际上,由于大多数滚动是无休止的,因此没有特殊事件来通知使用者滚动结束。然而在您的情况下,您可以通过简单的计时器测试用户是否短时间停止滚动。

    //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();
    }

正如你所看到的,鼠标滚轮事件会启动计时器。如果在计时器触发之前发生新的鼠标滚轮事件,则会重新启动计时器。通过这种方式,只有在特定时间间隔内没有滚轮事件才会触发计时器。


谢谢 Didier,实际上我试着用 StopWatch() 实现,但它无法执行。 你能看一下我的代码吗? - Vahid
1
秒表基本上用于测量时间。在这种情况下,您的if语句紧跟Start()之后,因此经过的时间几乎为0。实际上,您无法在MouseWheel事件处理程序内测试MouseWheel结束。在这种情况下仍然可以使用秒表,但是您需要在某种重复的事件处理程序(如CompositionTarget.Rendering)中进行测试。 - Dmitry
非常感谢你,Didier。你帮了我大忙,你的代码实际上很好用。 - Vahid

3

以下是一种替代方法,允许您指定UI元素(如画布、窗口、控件等)来检测鼠标滚轮的移动和灵敏度,并设置超时时间(以毫秒为单位),在此之后滚轮被视为不活动状态(自定义停止事件被触发):

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;
            }
        }
    }
}

以下是使用示例:

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"); };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接