WPF:鼠标离开事件不会在鼠标按下时触发

时间:2013-04-12 11:32:04

标签: wpf events mouse

我遇到鼠标进入/离开事件的问题。按下鼠标按钮并用光标在控件内保持,然后光标快速移出控件,此事件不会触发。

你能告诉我为什么会这样吗?有没有办法正确地获得这些活动?

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

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

4 个答案:

答案 0 :(得分:7)

编辑:在Sisyphe正确地注意到该行为对于具有鼠标交互的元素不起作用之后,我已经重写了代码。

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

,行为也可以仅应用于其关联元素

行为的作用,基本上(请参阅代码中的注释以获取更多详细信息):

  • 如果按下鼠标左键,则仅“有效”
  • 用于鼠标位置的手表从元素的内部更改为外部。在这种情况下,执行事件处理程序。

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

  • 不执行过渡到包含元素的处理程序(“内部”边界)
  • 不保证处理程序的正确执行顺序
  • 如果缓慢转换到窗口外,不能解决这个问题,e.LeftButton报告为已发布(bug?)。
  • 我决定不使用Win32钩子,而是使用一个定时器,它不会每隔0.15秒发射一次(尽管间隔设置较小,时钟漂移?)。对于快速鼠标移动,评估点可能相距太远而错过了刚刚翻过来的元素。

enter image description here

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

enter image description here

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

答案 1 :(得分:3)

方法#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进入/离开将继续发射)。即添加一些标志以实际保存输入/离开的state

此外,我正在测量鼠标是否在Window的“客户端范围”内。如果你需要边框等,你需要调整它。

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

答案 2 :(得分:2)

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

Mouse.Capture(yourUIElement);

然后在MouseLeave中发布它,

Mouse.Capture(null);

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

现在这并不是那么可怕,但如果碰巧移动鼠标,WPF也不会报告窗外的鼠标移动。这是默认行为。你可以通过Mouse.Capture来改变它。

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

答案 3 :(得分:2)

修改

  

如果您需要它 - 我在编辑了一个简化的包装器以便于使用(只需添加命令即可   视图模型)

方法#2 - 使用Global Mouse Hook跟踪鼠标移动 - 其余类似于#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));
    }
}

附加属性('漂亮'包装器)......

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() { }
}


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

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

至于'为什么'这一切都是必要的:
我不喜欢猜测,但似乎事件受到限制 - 当'越过边界'时,错过了关键的'一个',就像那样。无论如何,无论你怎么看它都是关于鼠标移动的。