protected IntPtr HandleNCHitTest(IntPtr lParam) { // Because we still have the System Border (which technically extends beyond the actual window // into where the Drop shadows are), we can use DefWindowProc here to handle resizing, except // on the top. We'll handle that below var originalRet = Win32Interop.DefWindowProc(Hwnd, (uint)WM.NCHITTEST, IntPtr.Zero, lParam); if (originalRet != new IntPtr(1)) { return(originalRet); } // At this point, we know that the cursor is inside the client area so it // has to be either the little border at the top of our custom title bar, // the drag bar or something else in the XAML island. But the XAML Island // handles WM_NCHITTEST on its own so actually it cannot be the XAML // Island. Then it must be the drag bar or the little border at the top // which the user can use to move or resize the window. var point = PointToClient(PointFromLParam(lParam)); RECT rcWindow; Win32Interop.GetWindowRect(Hwnd, out rcWindow); // On the Top border, the resize handle overlaps with the Titlebar area, which matches // a typical Win32 window or modern app window var resizeBorderHeight = GetResizeHandleHeight(); bool isOnResizeBorder = point.Y < resizeBorderHeight; // Make sure the caption buttons still get precedence // This is where things get tricky too. On Win11, we still want to suppor the snap // layout feature when hovering over the Maximize button. Unfortunately no API exists // yet to call that manually if using a custom titlebar. But, if we return HT_MAXBUTTON // here, the pointer events no longer enter the window // See https://github.com/dotnet/wpf/issues/4825 for more on this... // To hack our way into making this work, we'll return HT_MAXBUTTON here, but manually // manage the state and handle stuff through the WM_NCLBUTTON... events // This only applies on Windows 11, Windows 10 will work normally b/c no snap layout thing if (_owner !.HitTestCaptionButtons(point)) { if (_isWindows11) { var result = _owner.HitTestMaximizeButton(point); if (result) { _fakingMaximizeButton = true; return(new IntPtr(9)); } } }
protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { switch ((WM)msg) { case WM.NCCALCSIZE: // Follows logic from how to extend window frame + WindowsTerminal + Firefox // Windows Terminal only handles WPARAM = TRUE & only adjusts the top of the // rgrc[0] RECT & gets the correct result // Firefox, on the other hand, handles BOTH times WM_NCCALCSIZE is called, // and modifies the RECT. // This particularly differs from the "built-in" method in Avalonia in that // I retain the SystemBorder & ability resize the window in the transparent // area over the drop shadows, meaning resize handles don't overlap the window if (wParam != IntPtr.Zero && _owner?.Window.CanResize == true) { var ncParams = Marshal.PtrToStructure <NCCALCSIZE_PARAMS>(lParam); var originalTop = ncParams.rgrc[0].top; var ret = Win32Interop.DefWindowProc(hWnd, (uint)WM.NCCALCSIZE, wParam, lParam); if (ret != IntPtr.Zero) { return(ret); } var newSize = ncParams.rgrc[0]; if (newSize.Width == ncParams.rgrc[1].Width && newSize.Height == ncParams.rgrc[1].Height) { Marshal.StructureToPtr(ncParams, lParam, true); return(IntPtr.Zero); } newSize.top = originalTop; if (WindowState == WindowState.Maximized || WindowState == WindowState.FullScreen) { //newSize.top += GetResizeHandleHeight(); } else { if (_owner != null) { newSize.left += 8; newSize.right -= 8; newSize.bottom -= 8; } } ncParams.rgrc[0] = newSize; Marshal.StructureToPtr(ncParams, lParam, true); return(IntPtr.Zero); } break; //case WM.NCHITTEST: // return HandleNCHitTest(lParam); case WM.SIZE: EnsureExtended(); if (_fakingMaximizeButton) { // Sometimes the effect can get stuck, so if we resize, clear it _owner.FakeMaximizePressed(false); _wasFakeMaximizeDown = false; } break; case WM.ACTIVATE: EnsureExtended(); break; case WM.NCMOUSEMOVE: if (_fakingMaximizeButton) { var point = PointToClient(PointFromLParam(lParam)); _owner.FakeMaximizeHover(_owner.HitTestMaximizeButton(point)); return(IntPtr.Zero); } break; case WM.NCLBUTTONDOWN: if (_fakingMaximizeButton) { var point = PointToClient(PointFromLParam(lParam)); _owner.FakeMaximizePressed(_owner.HitTestMaximizeButton(point)); _wasFakeMaximizeDown = true; // This is important. If we don't tell the System we've handled this, we'll get that // classic Win32 button showing when we mouse press, and that's not good return(IntPtr.Zero); } break; case WM.NCLBUTTONUP: if (_fakingMaximizeButton && _wasFakeMaximizeDown) { var point = PointToClient(PointFromLParam(lParam)); _owner.FakeMaximizePressed(false); _wasFakeMaximizeDown = false; _owner.FakeMaximizeClick(); return(IntPtr.Zero); } break; } return(base.WndProc(hWnd, msg, wParam, lParam)); }