【问题标题】:WPF: mouse leave event doesn't trigger with mouse downWPF:鼠标离开事件不会在鼠标按下时触发
【发布时间】:2013-04-04 21:57:53
【问题描述】:

我遇到了鼠标进入/离开事件的问题。当在控件内按住鼠标按钮并按住光标,然后光标以足够快的速度移出控件时,不会触发此事件。

你能告诉我为什么会这样吗?有什么方法可以正确获取这些事件吗?

请查看示例项目以查看它的实际效果:https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip

更新。我发现了同样的问题here 没有答案。在那里开始赏金。

【问题讨论】:

  • 此时我只能猜测,但我想说的是,当您单击时,WPF 会为按钮获取鼠标捕获,其工作方式略有不同。因此,按下时,您不会离开控件。但如果真的是这样的话,你说“移动得足够快”,我猜我走错了方向。
  • 我在 Windows-8 上,我尝试了您的示例。它工作正常。但是,如果我将 TextBox 添加到 StackPanel,在其中单击并移出,则不会触发事件,因为 TextBox 将 RoutedEvents 标记为已处理并且直到 MouseUp 才会释放。如果我然后添加一个侦听器来强制接收更新,比如text.AddHandler(MouseMoveEvent, new MouseEventHandler(Window_MouseMove), true);,我会收到鼠标移动调用。但不适用于 Enter 和 Leave。您可能需要在 MouseMove 中检查光标位置到窗口坐标,并在此类控件上显式调用 LostFocus。
  • 我可以重现这一点(示例应用程序对此非常有效,但我自己也对其进行了测试),并且速度确实很重要。 @Idsa:您的问题是否仅限于离开窗口,还是关于将控件留在窗口内?
  • @adabyron,当前 - 一个窗口内的控件。但是为窗口本身找到解决方案也会很有趣。
  • 问题为窗口再现。一个简单的解决方法是在低级别挂钩鼠标移动事件并根据窗口位置和大小检查位置。

标签: wpf events mouse


【解决方案1】:

编辑:在 Sisyphe 正确指出该行为不适用于具有鼠标交互的元素后,我重写了代码。

该行为可以附加到窗口或任何其他 FrameworkElement。默认情况下,当鼠标左键按下并执行处理程序时,将监视所有包含的元素的 MouseLeave。通过设置MonitorSubControls="False",该行为也可以仅应用于其关联元素。

该行为的作用,基本上是(有关更多详细信息,请参阅代码中的 cmets):

  • 只有在按下鼠标左键时才“激活”
  • 监视鼠标位置从元素内部到外部的变化。在这种情况下,执行事件处理程序。

已知的限制(我认为,都可以通过更多的努力来解决,但对我来说似乎不太重要):

  • 不执行转换到包含元素(“内部”边界)的处理程序
  • 不保证处理程序的正确执行顺序
  • 无法解决缓慢转换到窗口外部的问题,报告 e.LeftButton 已释放(错误?)。
  • 我决定不使用 Win32 挂钩,而是使用计时器,计时器不会超过大约每 0.15 秒触发一次(尽管设置的间隔更小,时钟漂移?)。对于快速的鼠标移动,评估点可能相距太远而错过刚刚掠过的元素。

此脚本产生以下输出: 将行为附加到窗口,在橙色边框内移动(在释放鼠标按钮的情况下将蓝色边框留在内边界:0),在橙色边框内按下鼠标左键并(快速)移出窗口执行离开处理程序 (1 - 4)。释放窗口外的鼠标按钮,移回 goldTextBox (5),在文本框中按下鼠标左键,离开(快或慢)窗口外再次执行正确的处理程序 (6 - 9)。

Xaml(示例):

<Window x:Class="WpfApplication1.MouseLeaveControlWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:beh="clr-namespace:WpfApplication1.Behavior"
            Title="MouseLeaveControlWindow" Height="300" Width="300" x:Name="window" MouseLeave="OnMouseLeave">
    <i:Interaction.Behaviors>
        <beh:MonitorMouseLeaveBehavior />
    </i:Interaction.Behaviors>
    <Grid x:Name="grid" MouseLeave="OnMouseLeave" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Border x:Name="blueBorder" MouseLeave="OnMouseLeave" Background="SteelBlue" Margin="50" Grid.RowSpan="2" />
        <Border x:Name="orangeBorder" MouseLeave="OnMouseLeave"  Background="DarkOrange" Margin="70, 70, 70, 20" />
        <TextBox x:Name="goldTextBox" MouseLeave="OnMouseLeave" Background="Gold" Margin="70, 20, 70, 70" Grid.Row="1" Text="I'm a TextBox" />
    </Grid>
</Window>

后面的代码(仅用于调试目的):

public partial class MouseLeaveControlWindow : Window
{
    public MouseLeaveControlWindow()
    {
        InitializeComponent();
    }

    private int i = 0;
    private void OnMouseLeave(object sender, MouseEventArgs e)
    {
        FrameworkElement fe = (FrameworkElement)sender;
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1}.", i, fe.Name)); i++;
        }
        else
        {
            System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1} (Released).", i, fe.Name)); i++;
        }
    }
}

MonitorMouseLeaveBehavior:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.ComponentModel;
using System.Windows.Media;
using WpfApplication1.Helpers;

namespace WpfApplication1.Behavior
{
    public class MonitorMouseLeaveBehavior : Behavior<FrameworkElement>
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetCursorPos(ref Win32Point pt);

        [StructLayout(LayoutKind.Sequential)]
        internal struct Win32Point
        {
            public Int32 X;
            public Int32 Y;
        };

        [DllImport("user32.dll")]
        public static extern short GetAsyncKeyState(UInt16 virtualKeyCode);

        private enum VK
        {
            LBUTTON = 0x01
        }

        private bool _tracking;
        private const int _interval = 1;
        private Timer _checkPosTimer = new Timer(_interval);
        private Dictionary<FrameworkElement, RoutedEventHandlerInfo[]> _leaveHandlersForElement = new Dictionary<FrameworkElement, RoutedEventHandlerInfo[]>();
        private Window _window;
        private Dictionary<FrameworkElement, Rect> _boundsByElement = new Dictionary<FrameworkElement, Rect>();
        private Dictionary<FrameworkElement, bool> _wasInside = new Dictionary<FrameworkElement, bool>();
        private List<FrameworkElement> _elements = new List<FrameworkElement>();


        /// <summary>
        /// If true, all subcontrols are monitored for the mouseleave event when left mousebutton is down.
        /// True by default.
        /// </summary>
        public bool MonitorSubControls { get { return (bool)GetValue(MonitorSubControlsProperty); } set { SetValue(MonitorSubControlsProperty, value); } }
        public static readonly DependencyProperty MonitorSubControlsProperty = DependencyProperty.Register("MonitorSubControls", typeof(bool), typeof(MonitorMouseLeaveBehavior), new PropertyMetadata(true, OnMonitorSubControlsChanged));

        private static void OnMonitorSubControlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonitorMouseLeaveBehavior beh = (MonitorMouseLeaveBehavior)d;
            beh.AddOrRemoveLogicalChildren((bool)e.NewValue);
        }

        /// <summary>
        /// Initial actions
        /// </summary>
        protected override void OnAttached()
        {
            _window = this.AssociatedObject is Window ? (Window)this.AssociatedObject : Window.GetWindow(this.AssociatedObject); // get window
            _window.SourceInitialized += (s, e) =>
            {
                this.AddOrRemoveLogicalChildren(this.MonitorSubControls); // get all monitored elements
                this.AttachHandlers(true); // attach mousedown and sizechanged handlers
                this.GetAllBounds(); // determine bounds of all elements
                _checkPosTimer.Elapsed += (s1, e1) => Dispatcher.BeginInvoke((Action)(() => { CheckPosition(); }));
            };
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            this.AttachHandlers(false);
            base.OnDetaching();
        }

        /// <summary>
        /// Starts or stops monitoring of the AssociatedObject's logical children.
        /// </summary>
        /// <param name="add"></param>
        private void AddOrRemoveLogicalChildren(bool add)
        {
            if (_window != null && _window.IsInitialized)
            {
                AddOrRemoveSizeChangedHandlers(false);
                _elements.Clear();
                if (add)
                    _elements.AddRange(VisualHelper.FindLogicalChildren<FrameworkElement>(this.AssociatedObject));
                _elements.Add(this.AssociatedObject);
                AddOrRemoveSizeChangedHandlers(true);
            }
        }

        /// <summary>
        /// Attaches/detaches size changed handlers to the monitored elements
        /// </summary>
        /// <param name="add"></param>
        private void AddOrRemoveSizeChangedHandlers(bool add)
        {
            foreach (var element in _elements)
            {
                element.SizeChanged -= element_SizeChanged;
                if (add) element.SizeChanged += element_SizeChanged;
            }
        }

        /// <summary>
        /// Adjusts the stored bounds to the changed size
        /// </summary>
        void element_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            FrameworkElement fe = sender as FrameworkElement;
            if (fe != null)
                GetBounds(fe);
        }

        /// <summary>
        /// Attaches/Detaches MouseLeftButtonDown and SizeChanged handlers 
        /// </summary>
        /// <param name="attach">true: attach, false: detach</param>
        private void AttachHandlers(bool attach)
        {
            AddOrRemoveSizeChangedHandlers(attach);

            if (attach)
                _window.PreviewMouseLeftButtonDown += window_PreviewMouseLeftButtonDown;
            else // detach
                _window.PreviewMouseLeftButtonDown -= window_PreviewMouseLeftButtonDown;
        }

        /// <summary>
        /// Gets the bounds for all monitored elements
        /// </summary>
        private void GetAllBounds()
        {
            _boundsByElement.Clear();
            foreach (var element in _elements)
                GetBounds(element);
        }

        /// <summary>
        /// Gets the bounds of the control, which are used to check if the mouse position
        /// is located within. Note that this only covers rectangular control shapes.
        /// </summary>
        private void GetBounds(FrameworkElement element)
        {
            Point p1 = new Point(0, 0);
            Point p2 = new Point(element.ActualWidth, element.ActualHeight);
            p1 = element.TransformToVisual(_window).Transform(p1);
            p2 = element.TransformToVisual(_window).Transform(p2);

            if (element == _window) // window bounds need to account for the border
            {
                var titleHeight = SystemParameters.WindowCaptionHeight + 2 * SystemParameters.ResizeFrameHorizontalBorderHeight; //  not sure about that one
                var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
                p1.Offset(-verticalBorderWidth, -titleHeight);
                p2.Offset(-verticalBorderWidth, -titleHeight);
            }

            Rect bounds = new Rect(p1, p2);

            if (_boundsByElement.ContainsKey(element))
                _boundsByElement[element] = bounds;
            else
                _boundsByElement.Add(element, bounds);
        }

        /// <summary>
        /// For all monitored elements, detach the MouseLeave event handlers and store them locally,
        /// to be executed manually.
        /// </summary>
        private void RerouteLeaveHandlers()
        {
            foreach (var element in _elements)
            {
                if (!_leaveHandlersForElement.ContainsKey(element))
                {
                    var handlers = ReflectionHelper.GetRoutedEventHandlers(element, UIElement.MouseLeaveEvent);
                    if (handlers != null)
                    {
                        _leaveHandlersForElement.Add(element, handlers);
                        foreach (var handler in handlers)
                            element.MouseLeave -= (MouseEventHandler)handler.Handler; // detach handlers
                    }
                }
            }
        }

        /// <summary>
        /// Reattach all leave handlers that were detached in window_PreviewMouseLeftButtonDown.
        /// </summary>
        private void ReattachLeaveHandlers()
        {
            foreach (var kvp in _leaveHandlersForElement)
            {
                FrameworkElement fe = kvp.Key;
                foreach (var handler in kvp.Value)
                {
                    if (handler.Handler is MouseEventHandler)
                        fe.MouseLeave += (MouseEventHandler)handler.Handler;
                }
            }

            _leaveHandlersForElement.Clear();
        }

        /// <summary>
        /// Checks if the mouse position is inside the bounds of the elements
        /// If there is a transition from inside to outside, the leave event handlers are executed
        /// </summary>
        private void DetermineIsInside()
        {
            Point p = _window.PointFromScreen(GetMousePosition());
            foreach (var element in _elements)
            {
                if (_boundsByElement.ContainsKey(element))
                {
                    bool isInside = _boundsByElement[element].Contains(p);
                    bool wasInside = _wasInside.ContainsKey(element) && _wasInside[element];

                    if (wasInside && !isInside)
                        ExecuteLeaveHandlers(element);

                    if (_wasInside.ContainsKey(element))
                        _wasInside[element] = isInside;
                    else
                        _wasInside.Add(element, isInside);
                }
            }
        }

        /// <summary>
        /// Gets the mouse position relative to the screen
        /// </summary>
        public static Point GetMousePosition()
        {
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);
            return new Point(w32Mouse.X, w32Mouse.Y);
        }

        /// <summary>
        /// Gets the mouse button state. MouseEventArgs.LeftButton is notoriously unreliable.
        /// </summary>
        private bool IsMouseLeftButtonPressed()
        {
            short leftMouseKeyState = GetAsyncKeyState((ushort)VK.LBUTTON);
            bool ispressed = leftMouseKeyState < 0;

            return ispressed;
        }

        /// <summary>
        /// Executes the leave handlers that were attached to the controls.
        /// They have been detached previously by this behavior (see window_PreviewMouseLeftButtonDown), to prevent double execution.
        /// After mouseup, they are reattached (see CheckPosition)
        /// </summary>
        private void ExecuteLeaveHandlers(FrameworkElement fe)
        {
            MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
            MouseEventArgs mouseEvent = new MouseEventArgs(mouseDev, 0) { RoutedEvent = Control.MouseLeaveEvent };

            if (_leaveHandlersForElement.ContainsKey(fe))
            {
                foreach (var handler in _leaveHandlersForElement[fe])
                {
                    if (handler.Handler is MouseEventHandler)
                        ((MouseEventHandler)handler.Handler).Invoke(fe, mouseEvent);
                }
            }
        }

        /// <summary>
        /// Sets the mouse capture (events outside the window are still directed to it),
        /// and tells the behavior to watch out for a missed leave event
        /// </summary>
        private void window_PreviewMouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("--- left mousebutton down ---"); // todo remove

            this.RerouteLeaveHandlers();
            _tracking = true;
            _checkPosTimer.Start();
        }

        /// <summary>
        /// Uses the _tracking field as well as left mouse button state to determine if either 
        /// leave event handlers should be executed, or monitoring should be stopped.
        /// </summary>
        private void CheckPosition()
        {
            if (_tracking)
            {
                if (IsMouseLeftButtonPressed())
                {
                    this.DetermineIsInside();
                }
                else
                {
                    _wasInside.Clear();
                    _tracking = false;
                    _checkPosTimer.Stop();
                    System.Diagnostics.Debug.WriteLine("--- left mousebutton up ---"); // todo remove

                    // invoking ReattachLeaveHandlers() immediately would rethrow MouseLeave for top grid/window 
                    // if both a) mouse is outside window and b) mouse moves. Wait with reattach until mouse is inside window again and moves.
                    _window.MouseMove += ReattachHandler; 
                }
            }
        }

        /// <summary>
        /// Handles the first _window.MouseMove event after left mouse button was released,
        /// and reattaches the MouseLeaveHandlers. Detaches itself to be executed only once.
        /// </summary>
        private void ReattachHandler(object sender, MouseEventArgs e)
        {
            ReattachLeaveHandlers();
            _window.MouseMove -= ReattachHandler; // only once
        }
    }
}

VisualHelper.FindLogicalChildren, ReflectionHelper.GetRoutedEventHandlers:

public static List<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
{
    List<T> children = new List<T>();
    foreach (var child in LogicalTreeHelper.GetChildren(obj))
    {
        if (child != null)
        {
            if (child is T)
                children.Add((T)child);

            if (child is DependencyObject)
                children.AddRange(FindLogicalChildren<T>((DependencyObject)child)); // recursive
        }
    }
    return children;
}
/// <summary>
/// Gets the list of routed event handlers subscribed to the specified routed event.
/// </summary>
/// <param name="element">The UI element on which the event is defined.</param>
/// <param name="routedEvent">The routed event for which to retrieve the event handlers.</param>
/// <returns>The list of subscribed routed event handlers.</returns>
public static RoutedEventHandlerInfo[] GetRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
{
    var routedEventHandlers = default(RoutedEventHandlerInfo[]);
    // Get the EventHandlersStore instance which holds event handlers for the specified element.
    // The EventHandlersStore class is declared as internal.
    var eventHandlersStoreProperty = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
    object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);

    if (eventHandlersStore != null)
    {
        // Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance 
        // for getting an array of the subscribed event handlers.
        var getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        routedEventHandlers = (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(eventHandlersStore, new object[] { routedEvent });
    }
    return routedEventHandlers;
}

【讨论】:

  • 部分为我工作。只要 MouseDown 是在没有任何鼠标交互的控件上制作的,就可以了。但是,如果您单击诸如 TextBox 或 TabItem 之类的控件,问题仍然存在(这一定是由于依赖于 MouseCapture 的解决方法)
  • 是的,不幸的是,这是真的。我不确定它与 Capture 有什么关系,我猜它更多的是关于 Focus,但肯定有问题。实际上不止一个,因为一个很容易补救,通过使用 PreviewMouseLeftButtonDown 而不是 MouseLeftButtonDown。我稍后再报告。
  • 我已经编辑了帖子以适应鼠标交互控件。该行为现在还监视它所附加到的元素的所有逻辑子元素。
【解决方案2】:

方法 #1 - 如果您了解具体细节,它仍然是有效的方法(作为纯托管解决方案)。
(可以将捕获交给特定控件以避免出现问题,但我没有尝试过)

这应该可以帮助您获取事件(“固定”事件)。

关键是在窗口外跟踪鼠标移动(并且仅在鼠标按下时)。

为此,您需要执行capture(但与建议的略有不同,因为它不起作用 - 改为向下/向上)。

private void Window_MouseDown(object sender, MouseEventArgs e)
{
    this.CaptureMouse();
}
private void Window_MouseUp(object sender, MouseEventArgs e)
{
    this.ReleaseMouseCapture();
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
    test1.Content = "Mouse left";
}
private void Window_MouseEnter(object sender, MouseEventArgs e)
{
    test1.Content = "Mouse entered";
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
    if (Mouse.Captured == this)
    {
        if (!this.IsMouseInBounds(e))
            Window_MouseLeave(sender, e);
        else
            Window_MouseEnter(sender, e);
    }
    test2.Content = e.GetPosition(this).ToString();
}
private bool IsMouseInBounds(MouseEventArgs e)
{
    var client = ((FrameworkElement)this.Content);
    Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
    return bounds.Contains(e.GetPosition(this));
}
private Point GetRealPosition(Point mousePoint)
{
    return Application.Current.MainWindow.PointFromScreen(mousePoint);
}

注意:
您需要根据您的情况完成此操作。我只是将鼠标“虚拟连线”到EnterLeave 并且那里没有任何智能算法(即generated 进入/离开将继续触发)。 IE。添加一些标志以正确保存进入/离开的state

我还在测量鼠标是否在窗口的“客户端边界”内。如果您需要在边界等方面进行调整,则需要进行调整。

另外我忘了添加明显的 - 连接新事件MouseDown="Window_MouseDown" MouseUp="Window_MouseUp"

【讨论】:

    【解决方案3】:

    这是“正常”行为。在 MouseEnter 处理程序中捕获鼠标。

    Mouse.Capture(yourUIElement);
    

    然后在 MouseLeave 中释放它,

    Mouse.Capture(null);
    

    已编辑:更多解释。 WPF 不会精确地跟踪鼠标移动。您可以从以下事实中推断出,如果您捕获 MouseMove 事件,您可以看到它每隔 20 毫秒报告一次事件,而不是按像素精度。更像是每个事件 8 个像素。

    现在这并没有那么可怕,但是如果您碰巧移动了鼠标,WPF 也不会报告窗口外的鼠标移动。这是默认行为。您可以通过 Mouse.Capture 进行更改。

    现在,您可以想象为什么会出现这个问题。如果您可以将鼠标移到窗口外的速度比鼠标移动报告发生的速度快,那么 WPF 仍然认为它在应用程序内部。

    【讨论】:

    • 这怎么可能是正常行为?鼠标移到窗口外的速度会影响 MouseLeave 事件的触发(如果太快,它不会触发。)对我来说,这是正常的。
    • 正常情况下我的意思是,这不是错误。它的工作原理就像它的设计一样。我添加了一点解释
    【解决方案4】:

    编辑

    如果您需要它 - 我在 一个简化的包装器中进行了编辑以方便使用(只需在您的 视图模型)

    方法 #2 - 使用全局鼠标挂钩来跟踪鼠标移动 - 其余部分类似于 #1。
    实际上,这更多是关于如何从 C# 执行全局挂钩的示例。


    在 XAML 中,您可以连接所有 3 个或一个、两个事件

    my:Hooks.EnterCommand="{Binding EnterCommand}"
    my:Hooks.LeaveCommand="{Binding LeaveCommand}"
    my:Hooks.MouseMoveCommand="{Binding MoveCommand}"
    

    在您的视图模型中定义命令

    RelayCommand _enterCommand;
    public RelayCommand EnterCommand
    {
        get
        {
            return _enterCommand ?? (_enterCommand = new RelayCommand(param =>
            {
                var point = (Point)param;
                test1.Content = "Mouse entered";
                // test2.Content = point.ToString();
            },
            param => true));
        }
    }
    

    以及附加的属性('nice' wrapper)...

    public static class Hooks
    {
        private static Dictionary<ContentControl, Action> _hash = new Dictionary<ContentControl, Action>();
    
        #region MouseMoveCommand
    
        public static ICommand GetMouseMoveCommand(ContentControl control) { return (ICommand)control.GetValue(MouseMoveCommandProperty); }
        public static void SetMouseMoveCommand(ContentControl control, ICommand value) { control.SetValue(MouseMoveCommandProperty, value); }
        public static readonly DependencyProperty MouseMoveCommandProperty =
            DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnMouseMoveCommandChanged));
        static void OnMouseMoveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            ContentControl control = depObj as ContentControl;
            if (control != null && e.NewValue is ICommand)
                SetupMouseMove(control);
        }
        static void Instance_MouseMoveLL(object sender, WinHook.MouseLLMessageArgs e)
        {
        }
        static void OnAutoGeneratingColumn(ICommand command, object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (command.CanExecute(e)) command.Execute(e);
        }
    
        #endregion
    
        #region EnterCommand
    
        public static ICommand GetEnterCommand(ContentControl control) { return (ICommand)control.GetValue(EnterCommandProperty); }
        public static void SetEnterCommand(ContentControl control, ICommand value) { control.SetValue(EnterCommandProperty, value); }
        public static readonly DependencyProperty EnterCommandProperty =
            DependencyProperty.RegisterAttached("EnterCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnEnterCommandChanged));
        static void OnEnterCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            ContentControl control = depObj as ContentControl;
            if (control != null && e.NewValue is ICommand)
                SetupMouseMove(control);
        }
    
        #endregion
    
        #region LeaveCommand
    
        public static ICommand GetLeaveCommand(ContentControl control) { return (ICommand)control.GetValue(LeaveCommandProperty); }
        public static void SetLeaveCommand(ContentControl control, ICommand value) { control.SetValue(LeaveCommandProperty, value); }
        public static readonly DependencyProperty LeaveCommandProperty =
            DependencyProperty.RegisterAttached("LeaveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnLeaveCommandChanged));
        static void OnLeaveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            ContentControl control = depObj as ContentControl;
            if (control != null && e.NewValue is ICommand)
                SetupMouseMove(control);
        }
    
        #endregion
    
        static void SetupMouseMove(ContentControl control)
        {
            Action onmove;
            if (_hash.TryGetValue(control, out onmove) == false)
            {
                onmove = () =>
                {
                    var entered = false;
                    var moveCommand = control.GetValue(Hooks.MouseMoveCommandProperty) as ICommand;
                    var enterCommand = control.GetValue(Hooks.EnterCommandProperty) as ICommand;
                    var leaveCommand = control.GetValue(Hooks.LeaveCommandProperty) as ICommand;
    
                    // hook is invoked on the 'caller thread' (i.e. your GUI one) so it's safe
                    // don't forget to unhook and dispose / release it, handle unsubscribe for events
                    WinHook.Instance.MouseMoveLL += (s, e) =>
                    {
                        Point point = control.PointFromScreen(new Point(e.Message.Pt.X, e.Message.Pt.Y));
    
                        if (moveCommand != null && moveCommand.CanExecute(point))
                            moveCommand.Execute(point);
    
                        var newEntered = control.IsMouseInBounds(point); // don't use 'IsMouseOver'
                        if (newEntered != entered)
                        {
                            entered = newEntered;
                            if (entered)
                            {
                                if (enterCommand != null && enterCommand.CanExecute(point))
                                    enterCommand.Execute(point);
                            }
                            else
                            {
                                if (leaveCommand != null && leaveCommand.CanExecute(point))
                                    leaveCommand.Execute(point);
                            }
                        }
                    };
                };
                control.Loaded += (s, e) => onmove();
                _hash[control] = onmove;
            }
        }
        private static bool IsMouseInBounds(this ContentControl control, Point point)
        {
            var client = ((FrameworkElement)control.Content);
            Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
            return bounds.Contains(point);
        }
    }
    

    您可以使用文章中的HookManager

    或最小的钩子代码(注意需要适当的 IDisoposable、异常处理等):

    public sealed class WinHook : IDisposable
    {
        public static readonly WinHook Instance = new WinHook();
    
        public const int WH_MOUSE_LL = 14;
        public const uint WM_MOUSEMOVE = 0x0200;
    
        public delegate void MouseLLMessageHandler(object sender, MouseLLMessageArgs e);
        public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
    
        [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
        public static extern int GetCurrentThreadId();
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern bool UnhookWindowsHookEx(int idHook);
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
    
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
    
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public class MouseLLHookStruct
        {
            public POINT Pt;
            public uint mouseData;
            public uint flags;
            public uint time;
            public uint dwExtraInfo;
        }
    
        public class MouseLLMessageArgs : EventArgs
        {
            public bool IsProcessed { get; set; }
            public MouseLLHookStruct Message { get; private set; }
            public MouseLLMessageArgs(MouseLLHookStruct message) { this.Message = message; }
        }
    
        static IntPtr GetModuleHandle()
        {
            using (Process process = Process.GetCurrentProcess())
            using (ProcessModule module = process.MainModule)
                return GetModuleHandle(module.ModuleName);
        }
    
        public event MouseLLMessageHandler MouseMoveLL;
    
        int _hLLMouseHook = 0;
        HookProc LLMouseHook;
    
        private WinHook()
        {
            IntPtr hModule = GetModuleHandle();
            LLMouseHook = LowLevelMouseProc;
            _hLLMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LLMouseHook, hModule, 0);
            if (_hLLMouseHook == 0) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
        }
    
        public void Release()
        {
            if (_hLLMouseHook == 0) return;
            int hhook = _hLLMouseHook;
            _hLLMouseHook = 0;
            bool ret = UnhookWindowsHookEx(hhook);
            if (ret == false) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
        }
    
        public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && lParam.ToInt32() > 0
                && wParam.ToInt32() == (int)WM_MOUSEMOVE)
            {
                MouseLLHookStruct msg = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
                MouseLLMessageArgs args = new MouseLLMessageArgs(msg);
                if (MouseMoveLL != null)
                    MouseMoveLL(this, args);
                if (args.IsProcessed)
                    return -1; // return 1;
            }
            return CallNextHookEx(_hLLMouseHook, nCode, wParam, lParam);
        }
        // implement IDisposable properly and call `Release` for unmanaged resources / hook
        public void Dispose() { }
    }
    


    注意: 全局鼠标挂钩因性能问题而臭名昭著。而且你不能使用本地的(推荐但大多数时候没用) - 因为它不会让鼠标移动。

    另外避免在事件中放置任何“沉重”的东西 - 或任何从中“产生”的东西。实际上,您可以花在处理事件上的时间是有限制的——否则您的钩子将被移除,即停止工作。如果您需要对事件进行一些处理,请弹出一个新线程并调用回来。
    我最喜欢的解决方案实际上是给钩子自己的线程,然后需要调用事件——但这超出了范围并且有点复杂(你需要一个“泵”等)。

    至于“为什么”这一切都是必要的:
    我不喜欢投机,但似乎事件被限制了——当“越过边界”时,关键的“一个”被错过了,类似的事情。无论如何,这都是关于鼠标移动的,不管你怎么看。

    【讨论】:

    • 我在一个简单的attached properties 包装器中进行了编辑以方便使用
    猜你喜欢
    • 2013-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 1970-01-01
    • 2017-12-29
    • 2016-03-15
    • 2018-04-09
    相关资源
    最近更新 更多