WPF: событие мыши не запускается с мыши

У меня проблема с событиями ввода/выключения мыши. Когда кнопка мыши нажата и удерживается курсором внутри элемента управления, а затем курсор перемещается из элемента управления достаточно быстро, эти события не запускаются.

Не могли бы вы посоветовать мне, почему это происходит? Есть ли способ правильно получить эти события?

Пожалуйста, проверьте пример проекта, чтобы увидеть его в действии: https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip

Обновление. Я нашел ту же проблему здесь без ответа. Начинал там награду.

Ответы

Ответ 1

EDIT: после того, как Sisyphe правильно отметил, что поведение не работает для элементов с взаимодействием с мышью, я переписал код.

Поведение может быть привязано к окну или любому другому элементу FrameworkElement. По умолчанию все содержащиеся элементы будут отслеживаться для MouseLeave, а левая кнопка мыши - вниз, а обработчики выполняются. Поведение также может быть применено только к его ассоциированному элементу, установив MonitorSubControls="False".

Что такое поведение, в основном (подробнее см. комментарии в коде):

  • Только "активная", если нажата левая кнопка мыши.
  • Часы для изменения положения мыши изнутри на внешний элемент. В этом случае выполняются обработчики событий.

Известные ограничения (можно было бы решить с некоторыми дополнительными усилиями, я считаю, но не кажутся мне слишком важными):

  • Не выполняет обработчики для переходов к содержащемуся элементу ( "внутренние" границы)
  • Не гарантирует правильный порядок выполнения обработчиков.
  • Не разрешает это для медленных переходов к внешней стороне окна, e.LeftButton сообщается как выпущенный (ошибка?).
  • Я решил не использовать Win32-крючок и вместо этого использовать таймер, который не будет запускать больше, чем примерно каждые 0,15 секунды (несмотря на меньший интервал, дрифт часов?). Для быстрых движений мыши оцененные точки могут быть слишком далеко друг от друга и пропускать элемент, который просто перевернулся.

enter image description here

Этот script выводит результат ниже: с поведением, приложенным к окну, перемещаясь внутри оранжевого борта (листья 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 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;
}

Ответ 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);
}

Примечание:
Вам нужно закончить это в соответствии с вашей ситуацией. У меня только "фиктивный проводной" переход мыши на Enter и Leave и без какого-либо умного алгоритма там (т.е. generated enter/leave будет продолжать стрелять). То есть добавьте некоторый флаг, чтобы фактически сохранить state для ввода/выхода.

Также я измеряю, находится ли мышь в пределах "клиентских границ" окна. Вам нужно будет настроить это, если вам это нужно в отношении границ и т.д.

Также я забыл добавить очевидный - подключить новые события MouseDown="Window_MouseDown" MouseUp="Window_MouseUp"

Ответ 3

Это "нормальное" поведение. Захват мыши внутри обработчика MouseEnter.

Mouse.Capture(yourUIElement);

а затем отпустите его в MouseLeave,

Mouse.Capture(null);

Отредактировано: Больше объяснений. WPF не отслеживает движение мыши точно. Вы можете вывести это из того факта, что если вы захватили событие MouseMove, вы увидите, что он сообщает вам о событиях каждые 20 миллисекундных интервалов, а не о пиксельной точности.. больше похоже на 8 пикселей на событие.

Теперь это не так ужасно, но WPF также не сообщает о перемещении мыши за окном, если вам приходится перемещать мышь. Это поведение по умолчанию. Вы можете изменить его throuh Mouse.Capture как сказано.

Теперь вы можете себе представить, почему эта проблема происходит. Если вы можете переместить указатель мыши за пределы окна быстрее, чем отчет о переносе мыши, WPF по-прежнему считает, что он находится внутри приложения.

Ответ 4

ИЗМЕНИТЬ

В случае необходимости - я отредактировал в упрощенную оболочку для удобства использования (просто добавьте команды в свой вид-модель)

Подход # 2 - с помощью Global Mouse Hook для отслеживания перемещения мыши - остальное похоже на # 1.
На самом деле, это скорее пример того, как сделать глобальный крючок с С#.


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


Примечание. Глобальные мышиные крючки известны как проблемы производительности. И вы не можете использовать локальный (рекомендуется, но большую часть времени бесполезно), поскольку он не выходит за пределы движений мыши.

Также избегать помещать что-либо "тяжелое" внутри события - или что-нибудь, что "отскакивает" от него. На момент, когда вы можете потратить обработку события, существует ограничение, или ваш крючок будет удален, т.е. Перестанет работать. Если вам нужно сделать некоторую обработку из события, всплывает новый поток и вызывается обратно.
Мое любимое решение состоит в том, чтобы дать крючку собственный поток, а затем нужно вызвать события - но это выходит за рамки и немного сложнее (вам нужен "насос" и т.д.).

Что касается "почему" все это необходимо:
Мне не нравится спекулировать, но кажется, что события затухают - и критический "один" пропущен, когда "пересекает границу", что-то в этом роде. В любом случае, все это касается перемещения мыши независимо от того, как вы на это смотрите.