即使另一个控件已经捕获了鼠标,如何为控件获取MouseEvents

时间:2017-10-06 11:04:02

标签: c# wpf events mouseevent mousecapture

我正在使用从一个引脚连接到另一个引脚的线路连接器控件。典型的WPF解决方案是在用户开始拖动连接线时使用鼠标捕获。不幸的是,如果用户超过了有效的引脚,我需要一个鼠标悬停指示器。但是指示器永远不会显示,因为当我之前已经捕获鼠标时,目标引脚永远不会获得鼠标事件。

我写了一个轻量级的样本来显示我的问题:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    WindowState="Maximized"
    Title="MainWindow" Height="350" Width="525">
    <Canvas>
        <CheckBox x:Name="EnableMouseCapture" IsChecked="True" Content="Enable Mouse Capture" />
        <Rectangle x:Name="Test" Fill="Blue" Width="40" Height="40" Canvas.Left="200" Canvas.Top="200" />
        <Line x:Name="Line" Stroke="Black" StrokeThickness="1" IsHitTestVisible="False" />
    </Canvas>
</Window>

文件背后的代码:

using System;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            Test.MouseEnter += TestOnMouseEnter;
            Test.MouseLeave += TestOnMouseLeave;

            MouseDown += OnMouseDown;
        }

        private void TestOnMouseEnter(object sender, MouseEventArgs mouseEventArgs)
        {
            Console.WriteLine("(Test) MouseEnter");

            Test.Fill = Brushes.Coral;
        }

        private void TestOnMouseLeave(object sender, MouseEventArgs mouseEventArgs)
        {
            Console.WriteLine("(Test) MouseLeave");

            Test.Fill = Brushes.Blue;
        }

        private void OnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            Console.WriteLine("(Window) MouseMove");

            var pos = mouseEventArgs.GetPosition(this);
            Line.X2 = pos.X;
            Line.Y2 = pos.Y;
        }

        private void OnMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            Console.WriteLine("(Window) MouseDown");

            MouseUp += OnMouseUp;
            MouseMove += OnMouseMove;

            var pos = mouseButtonEventArgs.GetPosition(this);
            Line.X1 = pos.X;
            Line.Y1 = pos.Y;

            if (EnableMouseCapture.IsChecked == true)
            {
                CaptureMouse();
            }
        }

        private void OnMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            Console.WriteLine("(Window) MouseUp");

            ReleaseMouseCapture();

            MouseUp -= OnMouseUp;
            MouseMove -= OnMouseMove;
        }
    }
}

如果启用了Canvas上的鼠标捕获,则不会调用函数TestOnMouseEnterTestOnMouseLeave。如果禁用鼠标捕获,则会调用这两个函数。 我知道这是WPF的典型行为,但是有没有人知道我是如何获得通知的,即使另一个控件有捕获?

2 个答案:

答案 0 :(得分:1)

嗯,我理解的方式是MouseCapture可以使代码整齐,所以为什么不用鼠标捕获呢。

https://msdn.microsoft.com/en-us/library/ms771301.aspx

  

从msdn捕获鼠标:当一个对象捕获鼠标时,所有与鼠标相关的事件都会被视为具有鼠标捕获的对象执行该事件,即使鼠标指针位于另一个对象上也是如此。

What does it mean to "Capture the mouse" in WPF?

  

捕获鼠标对于拖动非常有用,因为只有捕获控件才会接收鼠标事件,直到释放为止。所有拖动代码都可以存在于一个控件中,而不是分布在多个控件上。

如果这样做会妨碍您的应用以您想要的方式运行,那么为什么不避免使用它呢?听起来像鼠标捕获的目的是打败你想要实现的目标。

如果你想看一下,我也发现了类似的问题:

How to fire MouseEnter for one object if another object has mousecapture?

编辑:我确实在这里有一个例子但它没有正常工作,基本上你需要手动点击测试,然后手动触发鼠标输入和鼠标离开。

答案 1 :(得分:1)

在评估了一些替代解决方案后,我找到了解决问题的另一种方法。它使用的是Win32 api。 所以有两种方法可以解决这类问题。 Colins方式更像WPF,但你需要手动模拟鼠标事件,这可能很麻烦。不幸的是,第二个解决方案使用非托管Win32钩子,但WPF鼠标事件系统无限制地工作。

这是我的示例代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;

namespace WpfApp1
{
    public static class NativeMouseHook
    {
        private static readonly Dictionary<MouseMessages, List<MouseEventHandler>> MouseHandlers = new Dictionary<MouseMessages, List<MouseEventHandler>>();
        private static readonly Dictionary<MouseMessages, List<MouseButtonEventHandler>> MouseButtonHandlers = new Dictionary<MouseMessages, List<MouseButtonEventHandler>>();
        private static readonly Dictionary<MouseMessages, List<MouseWheelEventHandler>> MouseWheelHandlers = new Dictionary<MouseMessages, List<MouseWheelEventHandler>>();

        public static void RegisterMouseHandler(MouseMessages mouseMessage, MouseEventHandler handler)
        {
            AddHandler(mouseMessage, MouseHandlers, handler);
            Start();
        }

        public static void UnregisterMouseHandler(MouseMessages mouseMessage, MouseEventHandler handler)
        {
            RemoveHandler(mouseMessage, MouseHandlers, handler);
            CheckAndStop();
        }

        public static void RegisterMouseHandler(MouseMessages mouseMessage, MouseButtonEventHandler handler)
        {
            AddHandler(mouseMessage, MouseButtonHandlers, handler);
            Start();
        }

        public static void UnregisterMouseHandler(MouseMessages mouseMessage, MouseButtonEventHandler handler)
        {
            RemoveHandler(mouseMessage, MouseButtonHandlers, handler);
            CheckAndStop();
        }

        public static void RegisterMouseHandler(MouseMessages mouseMessage, MouseWheelEventHandler handler)
        {
            AddHandler(mouseMessage, MouseWheelHandlers, handler);
            Start();
        }

        public static void UnregisterMouseHandler(MouseMessages mouseMessage, MouseWheelEventHandler handler)
        {
            RemoveHandler(mouseMessage, MouseWheelHandlers, handler);
            CheckAndStop();
        }

        private static void AddHandler<T>(MouseMessages mouseMessage, Dictionary<MouseMessages, List<T>> targetHandlerDictionary, T handler)
        {
            if (!targetHandlerDictionary.ContainsKey(mouseMessage))
            {
                targetHandlerDictionary.Add(mouseMessage, new List<T>());
            }

            targetHandlerDictionary[mouseMessage].Add(handler);
        }

        private static void RemoveHandler<T>(MouseMessages mouseMessage, Dictionary<MouseMessages, List<T>> targetHandlerDictionary, T handler)
        {
            if (targetHandlerDictionary.ContainsKey(mouseMessage))
            {
                var handlerList = targetHandlerDictionary[mouseMessage];
                handlerList.Remove(handler);

                if (handlerList.Count == 0)
                {
                    targetHandlerDictionary.Remove(mouseMessage);
                }
            }
        }

        private static void CheckAndStop()
        {
            if (MouseHandlers.Count == 0 && MouseButtonHandlers.Count == 0 && MouseWheelHandlers.Count == 0)
            {
                Stop();
            }
        }

        private static void Start()
        {
            if (_hookId == IntPtr.Zero)
            {
                _hookId = SetHook(Proc);
            }
        }

        private static void Stop()
        {
            if (_hookId != IntPtr.Zero)
            {
                UnhookWindowsHookEx(_hookId);
                _hookId = IntPtr.Zero;
            }
        }

        private static readonly LowLevelMouseProc Proc = HookCallback;
        private static IntPtr _hookId = IntPtr.Zero;

        private static IntPtr SetHook(LowLevelMouseProc proc)
        {
            using (var curProcess = Process.GetCurrentProcess())
            {
                using (var curModule = curProcess.MainModule)
                {
                    return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
                }
            }
        }

        private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                var hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));

                switch ((MouseMessages)wParam)
                {
                    case MouseMessages.WM_LBUTTONDOWN:
                        CallHandler(MouseMessages.WM_LBUTTONDOWN, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Left));
                        break;
                    case MouseMessages.WM_LBUTTONUP:
                        CallHandler(MouseMessages.WM_LBUTTONUP, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Left));
                        break;
                    case MouseMessages.WM_MOUSEMOVE:
                        CallHandler(MouseMessages.WM_MOUSEMOVE, MouseHandlers, new MouseEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time));
                        break;
                    case MouseMessages.WM_MOUSEWHEEL:
                        CallHandler(MouseMessages.WM_MOUSEWHEEL, MouseWheelHandlers, new MouseWheelEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, 0));
                        break;
                    case MouseMessages.WM_RBUTTONDOWN:
                        CallHandler(MouseMessages.WM_LBUTTONDOWN, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Right));
                        break;
                    case MouseMessages.WM_RBUTTONUP:
                        CallHandler(MouseMessages.WM_LBUTTONUP, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Right));
                        break;
                }
            }

            return CallNextHookEx(_hookId, nCode, wParam, lParam);
        }

        private static void CallHandler<T>(MouseMessages mouseMessage, Dictionary<MouseMessages, List<T>> targetHandlerDictionary, EventArgs args)
        {
            if (targetHandlerDictionary.ContainsKey(mouseMessage))
            {
                var handlerList = targetHandlerDictionary[mouseMessage];
                foreach (var handler in handlerList.Cast<Delegate>())
                {
                    handler.DynamicInvoke(null, args);
                }
            }
        }

        private const int WH_MOUSE_LL = 14;

        public enum MouseMessages
        {
            WM_LBUTTONDOWN = 0x0201,
            WM_LBUTTONUP = 0x0202,
            WM_MOUSEMOVE = 0x0200,
            WM_MOUSEWHEEL = 0x020A,
            WM_RBUTTONDOWN = 0x0204,
            WM_RBUTTONUP = 0x0205
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct MSLLHOOKSTRUCT
        {
            public POINT pt;
            public uint mouseData;
            public uint flags;
            public uint time;
            public IntPtr dwExtraInfo;
        }

        [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;
        };
        public static Point GetMousePosition()
        {
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);
            return new Point(w32Mouse.X, w32Mouse.Y);
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
    }
}

XAML:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        WindowState="Maximized"
        Title="MainWindow" Height="350" Width="525">
    <Canvas>
        <Rectangle x:Name="Test" Fill="Blue" Width="40" Height="40" Canvas.Left="200" Canvas.Top="200" />
        <Line x:Name="Line" Stroke="Black" StrokeThickness="1" IsHitTestVisible="False" />
    </Canvas>
</Window>

代码背后:

using System;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            Test.MouseEnter += TestOnMouseEnter;
            Test.MouseLeave += TestOnMouseLeave;

            MouseDown += OnMouseDown;
        }

        private void TestOnMouseEnter(object sender, MouseEventArgs mouseEventArgs)
        {
            Console.WriteLine("(Test) MouseEnter");

            Test.Fill = Brushes.Coral;
        }

        private void TestOnMouseLeave(object sender, MouseEventArgs mouseEventArgs)
        {
            Console.WriteLine("(Test) MouseLeave");

            Test.Fill = Brushes.Blue;
        }

        private void OnMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            Console.WriteLine("(Window) MouseMove");

            var pos = NativeMouseHook.GetMousePosition();
            Line.X2 = pos.X;
            Line.Y2 = pos.Y;
        }

        private void OnMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            Console.WriteLine("(Window) MouseDown");

            NativeMouseHook.RegisterMouseHandler(NativeMouseHook.MouseMessages.WM_MOUSEMOVE, (MouseEventHandler)OnMouseMove);
            NativeMouseHook.RegisterMouseHandler(NativeMouseHook.MouseMessages.WM_LBUTTONUP, OnMouseUp);

            var pos = mouseButtonEventArgs.GetPosition(this);
            Line.X1 = pos.X;
            Line.Y1 = pos.Y;
        }

        private void OnMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            Console.WriteLine("(Window) MouseUp");

            NativeMouseHook.UnregisterMouseHandler(NativeMouseHook.MouseMessages.WM_MOUSEMOVE, (MouseEventHandler)OnMouseMove);
            NativeMouseHook.UnregisterMouseHandler(NativeMouseHook.MouseMessages.WM_LBUTTONUP, OnMouseUp);
        }
    }
}