我正在使用从一个引脚连接到另一个引脚的线路连接器控件。典型的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上的鼠标捕获,则不会调用函数TestOnMouseEnter
和TestOnMouseLeave
。如果禁用鼠标捕获,则会调用这两个函数。
我知道这是WPF的典型行为,但是有没有人知道我是如何获得通知的,即使另一个控件有捕获?
答案 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);
}
}
}