Search code examples
winui-3windows-app-sdk

How to detect the WM_MOUSEMOVE event on my main window in WINUI 3 application


I got a solution here on Stack Overflow for maintaining my windows aspect ratio when the user resizes and it works great. I'm trying to use the wndproc function from that to detect a WM_MOUSEMOVE message when inside the client area of my main window. Not sure why my modification does not catch WM_MOUSEMOVE events when I move the mouse around my window.

I added this to the wndproc function ...

const int WM_MOUSEMOVE = 0x0200;
if (msg == WM_MOUSEMOVE)
{
   return null; // never reaches this
}

resulting MainWindow.xaml.cs ...

namespace MainWindowTest
{
    /// <summary>
    /// An empty window that can be used on its own or navigated to within a Frame.
    /// </summary>
    /// 



    public sealed partial class MainWindow : Window
    {
        private readonly WindowProcedureHook _hook;

        public MainWindow()
        {
            this.InitializeComponent();

            // hook the window procedure
            _hook = new WindowProcedureHook(this, WndProc);
            //ExtendsContentIntoTitleBar = true;
            GetAppWindowAndPresenter();
            _presenter.IsMaximizable = true;
            _presenter.IsMinimizable = true;
            _apw.Title = "Title";
            //_apw.TitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu;
        }

        public void GetAppWindowAndPresenter()
        {
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            WindowId myWndId = Win32Interop.GetWindowIdFromWindow(hWnd);
            _apw = AppWindow.GetFromWindowId(myWndId);
            _presenter = _apw.Presenter as OverlappedPresenter;
        }
        private AppWindow _apw;
        private OverlappedPresenter _presenter;

        private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
        {
        }



        private IntPtr? WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
        {
            // handle the WM_SIZING message
            const int WM_SIZING = 0x0214;

             
            **const int WM_MOUSEMOVE = 0x0200;
            if (msg == WM_MOUSEMOVE)
            {
                return null; // never reaches this breakpoint
            }**
            

            if (msg == WM_SIZING)
            {


                // get sizing rect
                var rc = Marshal.PtrToStructure<RECT>(lParam);

                // if coming from left/right only, we adjust height
                const int WMSZ_LEFT = 1;
                const int WMSZ_RIGHT = 2;
                if (wParam.ToInt64() == WMSZ_LEFT || wParam.ToInt64() == WMSZ_RIGHT)
                {
                    rc.height = rc.width * 9 / 16;
                }
                else
                {
                    rc.width = rc.height * 16 / 9;
                }

                // put it back, say we handled it
                Marshal.StructureToPtr(rc, lParam, false);
                return new IntPtr(1);
            }

            return null; // unhandled, let Windows do the job
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;

            public int width { get => right - left; set => right = left + value; }
            public int height { get => bottom - top; set => bottom = top + value; }
        }
    }

    // a utility class to hook window procedure and handle messages, or not
    public sealed class WindowProcedureHook
    {
        private readonly IntPtr _prevProc;
        private readonly WNDPROC _wndProc;
        private readonly Func<IntPtr, int, IntPtr, IntPtr, IntPtr?> _callback;

        public WindowProcedureHook(Window window, Func<IntPtr, int, IntPtr, IntPtr, IntPtr?> callback)
        {
            ArgumentNullException.ThrowIfNull(window);
            ArgumentNullException.ThrowIfNull(callback);

            _wndProc = WndProc;
            var handle = WinRT.Interop.WindowNative.GetWindowHandle(window);
            _callback = callback;

            const int GWLP_WNDPROC = -4;
            _prevProc = GetWindowLong(handle, GWLP_WNDPROC);
            SetWindowLong(handle, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(_wndProc));
        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) => _callback(hwnd, msg, wParam, lParam) ?? CallWindowProc(_prevProc, hwnd, msg, wParam, lParam);
        private delegate IntPtr WNDPROC(IntPtr handle, int msg, IntPtr wParam, IntPtr lParam);

        private static IntPtr GetWindowLong(IntPtr handle, int index) =>
            IntPtr.Size == 8 ? GetWindowLongPtrW(handle, index) : (IntPtr)GetWindowLongW(handle, index);

        private static IntPtr SetWindowLong(IntPtr handle, int index, IntPtr newLong) =>
            IntPtr.Size == 8 ? SetWindowLongPtrW(handle, index, newLong) : (IntPtr)SetWindowLongW(handle, index, newLong.ToInt32());

        [DllImport("user32")]
        private static extern IntPtr CallWindowProc(IntPtr prevWndProc, IntPtr handle, int msg, IntPtr wParam, IntPtr lParam);

        // note: WinUI3 windows are unicode, so we only use the "W" versions

        [DllImport("user32")]
        private static extern IntPtr GetWindowLongPtrW(IntPtr hWnd, int nIndex);

        [DllImport("user32")]
        private static extern int GetWindowLongW(IntPtr hWnd, int nIndex);

        [DllImport("user32")]
        private static extern int SetWindowLongW(IntPtr hWnd, int nIndexn, int dwNewLong);

        [DllImport("user32")]
        private static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    }

}

Solution

  • It turns out that Window.Content covers the window which results the window doesn't receive mouse messages. Use UIElement.Pointer* events instead. see https://github.com/castorix/WinUI3_Transparent/blob/86f5fff5cde4784422c6551b030c2b147e4b11f3/MainWindow.xaml.cs#L233 for an example.