WPF CaptureMouse不会在主窗口之外捕获鼠标事件

时间:2019-03-26 22:54:13

标签: wpf mouse

我创建了一个琐碎的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。

2 个答案:

答案 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);
}