我创建了一个琐碎的WPF应用程序,尝试捕获鼠标,但是在鼠标离开窗口后,它停止获取mousemove事件。奇怪的是,我确实在窗口外遇到了mouseup事件。
我尝试了几种捕鼠器变体,但是没有任何效果。我也曾尝试观察MouseLost事件,但是当鼠标移到窗口外时我看不到它。当我释放鼠标按钮时,它就可以看到它。
这是我的MainWindow类。只要鼠标在窗口中,我都会得到鼠标移动事件,但是如果单击鼠标并将其拖出窗口,我将停止获取移动事件。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MouseDown += MainWindow_MouseDown;
MouseUp += MainWindow_MouseUp;
MouseMove += MainWindow_MouseMove;
LostMouseCapture += MainWindow_LostMouseCapture;
}
private void MainWindow_LostMouseCapture(object sender, MouseEventArgs e)
{
Debug.WriteLine("Lost Mouse");
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("P: " + Mouse.GetPosition(this));
}
private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Releasing");
ReleaseMouseCapture();
}
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Capturing");
CaptureMouse();
// This does not work either: Mouse.Capture(this, CaptureMode.SubTree);
}
}
我希望看到所有的mousemove事件,以便可以拖动窗口,但我只会在外部看到mouseup事件,并且只有在光标位于窗口内部时才会发生mousemoves。
答案 0 :(得分:2)
经过研究,我对这个问题有一个答案。 WPF应用程序离开应用程序窗口时看不到鼠标,因此,如果要进行一些自定义拖动行为,则必须使用interrop来全局捕获鼠标。我创建了以下类来启用DPI感知窗口,以便在任何WPF窗口的多个监视器之间拖动:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
[assembly: DisableDpiAwareness]
namespace Talisman
{
// --------------------------------------------------------------------------
/// <summary>
/// Enables dragging of a WPF window in a way that is per-monitor DPI sensitive.
///
/// HOW TO USE
/// Add a DraggingLogic member variable and put this code in your window constructor:
/// _draggingLogic = new DraggingLogic(this);
///
/// If you want to do special things when the window moves or when it is clicked:
/// _draggingLogic.OnPositionChanged += (xm, ym) => {/* whatever you want here */};
/// _draggingLogic.OnClick += () => {/* whatever you want here */};
///
/// </summary>
// --------------------------------------------------------------------------
public class DraggingLogic
{
public event Action<double, double> OnPositionChanged;
public event Action OnClick;
/// <summary>
/// Factor to convert Horizontal screen coordinates
/// </summary>
public double DpiCorrectionX { get; set; }
/// <summary>
/// Factor to convertVertical screen coordinates
/// </summary>
public double DpiCorrectionY { get; set; }
public double WpfDpiX { get; set; }
public double WpfDpiY { get; set; }
#region INTERROP - Mouse interaction
private static int _mouseHookHandle;
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
private static HookProc _mouseDelegate;
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_MOUSEMOVE = 0x0200;
[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", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string name);
#endregion
#region INTERROP - DPI
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
[DllImport("Shcore.dll")]
private static extern IntPtr SetProcessDpiAwareness([In]DpiAwareness dpiAwareness);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
public enum DpiAwareness
{
Unaware = 0,
System = 1,
PerMonitor = 2,
}
#endregion
Screen _currentScreen;
Window _dragMe;
bool _dragging = false;
double _dragDelta = 0;
Point _lastMousePosition;
Point _mouseStickyPosition;
// --------------------------------------------------------------------------
/// <summary>
/// Get resource text using a loose naming scheme
/// </summary>
// --------------------------------------------------------------------------
public DraggingLogic(Window dragme)
{
var result = SetProcessDpiAwareness(DpiAwareness.PerMonitor);
dragme.MouseDown += HandleMouseDown;
dragme.MouseMove += HandleMouseMove;
dragme.MouseUp += HandleMouseUp;
dragme.Loaded += Dragme_Loaded;
_dragMe = dragme;
}
// --------------------------------------------------------------------------
/// <summary>
/// Dragme_Loaded - can't find DPI until the window is loaded
/// </summary>
// --------------------------------------------------------------------------
private void Dragme_Loaded(object sender, RoutedEventArgs e)
{
var source = PresentationSource.FromVisual(_dragMe);
WpfDpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
WpfDpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
}
// --------------------------------------------------------------------------
/// <summary>
/// Figure out scaling for the DPI on a certain monitor
/// </summary>
// --------------------------------------------------------------------------
public void CalculateDpiScaleFactors(Screen screen, DpiType dpiType)
{
var point = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var monitor = MonitorFromPoint(point, 2/*MONITOR_DEFAULTTONEAREST*/);
Debug.WriteLine($"Monitor: {monitor}");
var result = GetDpiForMonitor(monitor, dpiType, out var monitorDpiX, out var monitorDpiY);
if(result != IntPtr.Zero)
{
monitorDpiX = monitorDpiY = 96;
}
DpiCorrectionX = 96.0 / monitorDpiX;
DpiCorrectionY = 96.0 / monitorDpiY;
}
// --------------------------------------------------------------------------
/// <summary>
/// Mouse Down
/// </summary>
// --------------------------------------------------------------------------
private void HandleMouseDown(object sender, MouseButtonEventArgs e)
{
var window = sender as Window;
if (e.LeftButton == MouseButtonState.Pressed)
{
_dragging = true;
_dragDelta = 0;
_mouseStickyPosition = Mouse.GetPosition(window);
_lastMousePosition = window.PointToScreen(Mouse.GetPosition(window));
_currentScreen = GetScreenFromPoint(_lastMousePosition);
CalculateDpiScaleFactors(_currentScreen, DpiType.Effective);
CaptureGlobalMouse();
e.Handled = true;
}
}
// --------------------------------------------------------------------------
/// <summary>
/// Mouse Move
/// </summary>
// --------------------------------------------------------------------------
private void HandleMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (_dragging)
{
e.Handled = true;
}
}
// --------------------------------------------------------------------------
/// <summary>
/// HandleGlobalMouseMove
/// </summary>
// --------------------------------------------------------------------------
private void HandleGlobalMouseMove(Point mouseLocation)
{
var newPosition = mouseLocation; // This arrives without DPI correction
var screen = GetScreenFromPoint(newPosition);
// We need to do some fix up when we drag to another screen because
// the DPI on the other screen could be different
if(screen != null && screen.DeviceName != _currentScreen.DeviceName)
{
CalculateDpiScaleFactors(screen, DpiType.Effective);
_lastMousePosition = newPosition;
// Move the window to match the mouse position
_dragMe.Left = (newPosition.X - _mouseStickyPosition.X)* DpiCorrectionX;
_dragMe.Top = (newPosition.Y - _mouseStickyPosition.Y)* DpiCorrectionY;
_currentScreen = screen;
}
var xMove = (newPosition.X - _lastMousePosition.X)* DpiCorrectionX;
var yMove = (newPosition.Y - _lastMousePosition.Y)* DpiCorrectionY;
_dragMe.Left += xMove;
_dragMe.Top += yMove;
_dragDelta += (_lastMousePosition - newPosition).Length;
_lastMousePosition = newPosition;
OnPositionChanged?.Invoke(xMove, yMove);
}
// --------------------------------------------------------------------------
/// <summary>
/// GetScreenFromPoint - return the screen from a raw point (presumably mouse coordinate)
/// </summary>
// --------------------------------------------------------------------------
public Screen GetScreenFromPoint(Point point)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.ContainsPoint(point.X, point.Y)) return screen;
}
return null;
}
// --------------------------------------------------------------------------
/// <summary>
/// Mouse Up
/// </summary>
// --------------------------------------------------------------------------
private void HandleMouseUp(object sender, MouseButtonEventArgs e)
{
if (_dragging)
{
var window = sender as Window;
// if the user didn't actually drag, then we want to treat this as a click
if (_dragDelta < 3)
{
OnClick?.Invoke();
}
_dragging = false;
ReleaseGlobalMouse();
if(e != null) e.Handled = true;
}
}
// --------------------------------------------------------------------------
/// <summary>
/// MouseHookProc- allows us to handle global mouse events
/// </summary>
// --------------------------------------------------------------------------
private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
switch (wParam)
{
case WM_LBUTTONUP: HandleMouseUp(this, null); break;
case WM_MOUSEMOVE:
{
var mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
HandleGlobalMouseMove(new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
break;
}
}
}
return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
}
// --------------------------------------------------------------------------
/// <summary>
/// CaptureGlobalMouse
/// </summary>
// --------------------------------------------------------------------------
private void CaptureGlobalMouse()
{
if (_mouseHookHandle == 0)
{
_mouseDelegate = MouseHookProc;
_mouseHookHandle = SetWindowsHookEx(WH_MOUSE_LL,
_mouseDelegate,
GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
0);
if (_mouseHookHandle == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
// --------------------------------------------------------------------------
/// <summary>
/// ReleaseGlobalMouse
/// </summary>
// --------------------------------------------------------------------------
private void ReleaseGlobalMouse()
{
if (_mouseHookHandle != 0)
{
int result = UnhookWindowsHookEx(_mouseHookHandle);
_mouseHookHandle = 0;
_mouseDelegate = null;
if (result == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
}
答案 1 :(得分:0)
您必须在对象本身上使用鼠标捕获,因此在 MouseDown 中您需要 ((IInputElement)sender).CaptureMouse()
和 MouseUp ((IInputElement)sender).ReleaseMouseCapture()
。
或者,您也可以使用 MainWindow.CaptureMouse()
。
private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Releasing");
((IInputElement)sender).ReleaseMouseCapture()
}
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Capturing");
((IInputElement)sender).CaptureMouse()
// This does not work either: Mouse.Capture(this, CaptureMode.SubTree);
}