Browse Source

Win32 - Fix Window Frame Size and Position issues. (#16608)

* add tests

* win32 - ensure frame size and position doesn't change for client operations

* remove forced position setting

---------

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
pull/16705/head
Emmanuel Hansen 2 years ago
committed by GitHub
parent
commit
854078067d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 16
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  2. 102
      src/Windows/Avalonia.Win32/WindowImpl.cs
  3. 2
      tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs
  4. 42
      tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

16
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -1,17 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Win32.Automation; using Avalonia.Win32.Automation;
using Avalonia.Win32.Input; using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using Avalonia.Win32.Interop.Automation; using Avalonia.Win32.Interop.Automation;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -643,6 +640,8 @@ namespace Avalonia.Win32
Resized(clientSize / RenderScaling, _resizeReason); Resized(clientSize / RenderScaling, _resizeReason);
} }
if (IsWindowVisible(_hwnd) && !_shown)
_shown = true;
if (stateChanged) if (stateChanged)
{ {
@ -661,11 +660,17 @@ namespace Avalonia.Win32
if (_isClientAreaExtended) if (_isClientAreaExtended)
{ {
UpdateExtendMargins(); ExtendClientArea();
ExtendClientAreaToDecorationsChanged?.Invoke(true); ExtendClientAreaToDecorationsChanged?.Invoke(true);
} }
} }
else if (windowState == WindowState.Maximized && _isClientAreaExtended)
{
ExtendClientArea();
ExtendClientAreaToDecorationsChanged?.Invoke(true);
}
return IntPtr.Zero; return IntPtr.Zero;
} }
@ -676,8 +681,7 @@ namespace Avalonia.Win32
case WindowsMessage.WM_MOVE: case WindowsMessage.WM_MOVE:
{ {
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), PositionChanged?.Invoke(Position);
(short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero; return IntPtr.Zero;
} }

102
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -263,14 +263,8 @@ namespace Avalonia.Win32
{ {
get get
{ {
if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) GetWindowRect(_hwnd, out var rcWindow);
{ return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling;
GetWindowRect(_hwnd, out var rcWindow);
return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling;
}
DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf<RECT>());
return new Size(rect.Width, rect.Height) / RenderScaling;
} }
} }
@ -510,6 +504,7 @@ namespace Avalonia.Win32
var result = DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); var result = DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int));
return result == 0; return result == 0;
} }
public IEnumerable<object> Surfaces public IEnumerable<object> Surfaces
=> _glSurface is null ? => _glSurface is null ?
new object[] { Handle, _framebuffer } : new object[] { Handle, _framebuffer } :
@ -520,15 +515,10 @@ namespace Avalonia.Win32
get get
{ {
GetWindowRect(_hwnd, out var rc); GetWindowRect(_hwnd, out var rc);
return new PixelPoint(rc.left, rc.top);
var border = HiddenBorderSize;
return new PixelPoint(rc.left + border.Width, rc.top + border.Height);
} }
set set
{ {
var border = HiddenBorderSize;
value = new PixelPoint(value.X - border.Width, value.Y - border.Height);
SetWindowPos( SetWindowPos(
Handle.Handle, Handle.Handle,
IntPtr.Zero, IntPtr.Zero,
@ -542,23 +532,6 @@ namespace Avalonia.Win32
private bool HasFullDecorations => _windowProperties.Decorations == SystemDecorations.Full; private bool HasFullDecorations => _windowProperties.Decorations == SystemDecorations.Full;
private PixelSize HiddenBorderSize
{
get
{
// Windows 10 and 11 add a 7 pixel invisible border on the left/right/bottom of windows for resizing
if (Win32Platform.WindowsVersion.Major < 10 || !HasFullDecorations || GetStyle().HasFlag(WindowStyles.WS_POPUP))
{
return PixelSize.Empty;
}
DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var clientRect, Marshal.SizeOf<RECT>());
GetWindowRect(_hwnd, out var frameRect);
var borderWidth = GetSystemMetrics(SystemMetric.SM_CXBORDER);
return new PixelSize(clientRect.left - frameRect.left - borderWidth, 0);
}
}
public void Move(PixelPoint point) => Position = point; public void Move(PixelPoint point) => Position = point;
@ -594,25 +567,29 @@ namespace Avalonia.Win32
GetWindowPlacement(_hwnd, out var windowPlacement); GetWindowPlacement(_hwnd, out var windowPlacement);
var clientScreenOrigin = new POINT();
ClientToScreen(_hwnd, ref clientScreenOrigin);
var requestedClientRect = new RECT var requestedClientRect = new RECT
{ {
left = clientScreenOrigin.X, left = 0,
right = clientScreenOrigin.X + requestedClientWidth, top = 0,
right = requestedClientWidth,
top = clientScreenOrigin.Y, bottom = requestedClientHeight
bottom = clientScreenOrigin.Y + requestedClientHeight,
}; };
var requestedWindowRect = _isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect(requestedClientRect); var requestedWindowRect = _isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect(requestedClientRect);
var windowWidth = requestedWindowRect.Width;
var windowHeight = requestedWindowRect.Height;
if (requestedWindowRect.Width == windowPlacement.NormalPosition.Width && requestedWindowRect.Height == windowPlacement.NormalPosition.Height) if (windowWidth == windowPlacement.NormalPosition.Width && windowHeight == windowPlacement.NormalPosition.Height)
{ {
return; return;
} }
var position = Position;
requestedWindowRect.left = position.X;
requestedWindowRect.top = position.Y;
requestedWindowRect.right = position.X + windowWidth;
requestedWindowRect.bottom = position.Y + windowHeight;
windowPlacement.NormalPosition = requestedWindowRect; windowPlacement.NormalPosition = requestedWindowRect;
windowPlacement.ShowCmd = !_shown ? ShowWindowCommand.Hide : _lastWindowState switch windowPlacement.ShowCmd = !_shown ? ShowWindowCommand.Hide : _lastWindowState switch
@ -1025,14 +1002,8 @@ namespace Avalonia.Win32
if (fullscreen) if (fullscreen)
{ {
GetWindowRect(_hwnd, out var windowRect); GetWindowRect(_hwnd, out var windowRect);
GetClientRect(_hwnd, out var clientRect);
clientRect.left += windowRect.left;
clientRect.right += windowRect.left;
clientRect.top += windowRect.top;
clientRect.bottom += windowRect.top;
_savedWindowInfo.WindowRect = clientRect; _savedWindowInfo.WindowRect = windowRect;
var current = GetStyle(); var current = GetStyle();
var currentEx = GetExtendedStyle(); var currentEx = GetExtendedStyle();
@ -1141,16 +1112,8 @@ namespace Avalonia.Win32
_isClientAreaExtended = false; _isClientAreaExtended = false;
return; return;
} }
GetClientRect(_hwnd, out var rcClient);
GetWindowRect(_hwnd, out var rcWindow); GetWindowRect(_hwnd, out var rcWindow);
// Inform the application of the frame change.
SetWindowPos(_hwnd,
IntPtr.Zero,
rcWindow.left, rcWindow.top,
rcClient.Width, rcClient.Height,
SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOACTIVATE);
if (_isClientAreaExtended && WindowState != WindowState.FullScreen) if (_isClientAreaExtended && WindowState != WindowState.FullScreen)
{ {
var margins = UpdateExtendMargins(); var margins = UpdateExtendMargins();
@ -1170,8 +1133,6 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness(); _offScreenMargin = new Thickness();
_extendedMargins = new Thickness(); _extendedMargins = new Thickness();
Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), WindowResizeReason.Layout);
unsafe unsafe
{ {
int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT; int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT;
@ -1189,6 +1150,13 @@ namespace Avalonia.Win32
DisableCloseButton(_hwnd); DisableCloseButton(_hwnd);
} }
// Inform the application of the frame change.
SetWindowPos(_hwnd,
IntPtr.Zero,
rcWindow.left, rcWindow.top,
0, 0,
SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOSIZE);
ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended);
} }
@ -1454,7 +1422,7 @@ namespace Avalonia.Win32
{ {
style &= ~(fullDecorationFlags | WindowStyles.WS_THICKFRAME); style &= ~(fullDecorationFlags | WindowStyles.WS_THICKFRAME);
if (newProperties.Decorations == SystemDecorations.BorderOnly && newProperties.WindowState != WindowState.Maximized) if (newProperties.Decorations == SystemDecorations.BorderOnly && newProperties.WindowState != WindowState.Maximized && newProperties.IsResizable)
{ {
style |= WindowStyles.WS_THICKFRAME | WindowStyles.WS_BORDER; style |= WindowStyles.WS_THICKFRAME | WindowStyles.WS_BORDER;
} }
@ -1483,8 +1451,6 @@ namespace Avalonia.Win32
if (!_isFullScreenActive && ((oldProperties.Decorations != newProperties.Decorations) || forceChanges)) if (!_isFullScreenActive && ((oldProperties.Decorations != newProperties.Decorations) || forceChanges))
{ {
var style = GetStyle();
var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0; var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0;
var margins = new MARGINS var margins = new MARGINS
@ -1497,23 +1463,11 @@ namespace Avalonia.Win32
DwmExtendFrameIntoClientArea(_hwnd, ref margins); DwmExtendFrameIntoClientArea(_hwnd, ref margins);
if (_shown || forceChanges) if (_shown || forceChanges)
{ {
GetClientRect(_hwnd, out var oldClientRect); SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0 ,0,
var oldClientRectOrigin = new POINT();
ClientToScreen(_hwnd, ref oldClientRectOrigin);
oldClientRect.Offset(oldClientRectOrigin);
var newRect = oldClientRect;
if (newProperties.Decorations == SystemDecorations.Full)
{
AdjustWindowRectEx(ref newRect, (uint)style, false, (uint)GetExtendedStyle());
}
SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height,
SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOMOVE |
SetWindowPosFlags.SWP_FRAMECHANGED); SetWindowPosFlags.SWP_FRAMECHANGED);
} }
} }

2
tests/Avalonia.IntegrationTests.Appium/WindowDecorationsTests.cs

@ -12,7 +12,7 @@ public class WindowDecorationsTests : TestBase, IDisposable
{ {
} }
[PlatformFact(TestPlatforms.MacOS)] // TODO fix me on Windows [Fact]
public void Window_Size_Should_Be_Consistent_Between_Toggles() public void Window_Size_Should_Be_Consistent_Between_Toggles()
{ {
var window = Session.FindElementByAccessibilityId("MainWindow"); var window = Session.FindElementByAccessibilityId("MainWindow");

42
tests/Avalonia.IntegrationTests.Appium/WindowTests.cs

@ -311,6 +311,44 @@ namespace Avalonia.IntegrationTests.Appium
} }
} }
[Fact]
public void Changing_SystemDecorations_Should_Not_Change_Frame_Size_And_Position()
{
using (OpenWindow(null, ShowWindowMode.NonOwned, WindowStartupLocation.Manual))
{
var info = GetWindowInfo();
Session.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
Session.FindElementByAccessibilityId("SystemDecorationsNone").SendClick();
var updatedInfo = GetWindowInfo();
Assert.Equal(info.FrameSize, updatedInfo.FrameSize);
Assert.Equal(info.Position, updatedInfo.Position);
Session.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
Session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
updatedInfo = GetWindowInfo();
Assert.Equal(info.FrameSize, updatedInfo.FrameSize);
Assert.Equal(info.Position, updatedInfo.Position);
}
}
[Fact]
public void Changing_WindowState_Should_Not_Change_Frame_Size_And_Position()
{
using (OpenWindow())
{
var info = GetWindowInfo();
Session.FindElementByAccessibilityId("CurrentWindowState").SendClick();
Session.FindElementByAccessibilityId("WindowStateMaximized").SendClick();
Session.FindElementByAccessibilityId("CurrentWindowState").SendClick();
Session.FindElementByAccessibilityId("WindowStateNormal").SendClick();
var updatedInfo = GetWindowInfo();
Assert.Equal(info.FrameSize, updatedInfo.FrameSize);
Assert.Equal(info.Position, updatedInfo.Position);
}
}
public static TheoryData<Size?, ShowWindowMode, WindowStartupLocation, bool> StartupLocationData() public static TheoryData<Size?, ShowWindowMode, WindowStartupLocation, bool> StartupLocationData()
{ {
var sizes = new Size?[] { null, new Size(400, 300) }; var sizes = new Size?[] { null, new Size(400, 300) };
@ -391,8 +429,8 @@ namespace Avalonia.IntegrationTests.Appium
} }
private IDisposable OpenWindow( private IDisposable OpenWindow(
Size? size, Size? size = null,
ShowWindowMode mode, ShowWindowMode mode = ShowWindowMode.NonOwned,
WindowStartupLocation location = WindowStartupLocation.Manual, WindowStartupLocation location = WindowStartupLocation.Manual,
WindowState state = Controls.WindowState.Normal, WindowState state = Controls.WindowState.Normal,
bool canResize = true, bool canResize = true,

Loading…
Cancel
Save