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

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

@ -263,14 +263,8 @@ namespace Avalonia.Win32
{
get
{
if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled)
{
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;
GetWindowRect(_hwnd, out var rcWindow);
return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling;
}
}
@ -510,6 +504,7 @@ namespace Avalonia.Win32
var result = DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int));
return result == 0;
}
public IEnumerable<object> Surfaces
=> _glSurface is null ?
new object[] { Handle, _framebuffer } :
@ -520,15 +515,10 @@ namespace Avalonia.Win32
get
{
GetWindowRect(_hwnd, out var rc);
var border = HiddenBorderSize;
return new PixelPoint(rc.left + border.Width, rc.top + border.Height);
return new PixelPoint(rc.left, rc.top);
}
set
{
var border = HiddenBorderSize;
value = new PixelPoint(value.X - border.Width, value.Y - border.Height);
SetWindowPos(
Handle.Handle,
IntPtr.Zero,
@ -542,23 +532,6 @@ namespace Avalonia.Win32
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;
@ -594,25 +567,29 @@ namespace Avalonia.Win32
GetWindowPlacement(_hwnd, out var windowPlacement);
var clientScreenOrigin = new POINT();
ClientToScreen(_hwnd, ref clientScreenOrigin);
var requestedClientRect = new RECT
{
left = clientScreenOrigin.X,
right = clientScreenOrigin.X + requestedClientWidth,
top = clientScreenOrigin.Y,
bottom = clientScreenOrigin.Y + requestedClientHeight,
left = 0,
top = 0,
right = requestedClientWidth,
bottom = requestedClientHeight
};
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;
}
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.ShowCmd = !_shown ? ShowWindowCommand.Hide : _lastWindowState switch
@ -1025,14 +1002,8 @@ namespace Avalonia.Win32
if (fullscreen)
{
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 currentEx = GetExtendedStyle();
@ -1141,16 +1112,8 @@ namespace Avalonia.Win32
_isClientAreaExtended = false;
return;
}
GetClientRect(_hwnd, out var rcClient);
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)
{
var margins = UpdateExtendMargins();
@ -1170,8 +1133,6 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();
Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), WindowResizeReason.Layout);
unsafe
{
int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT;
@ -1189,6 +1150,13 @@ namespace Avalonia.Win32
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);
}
@ -1454,7 +1422,7 @@ namespace Avalonia.Win32
{
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;
}
@ -1483,8 +1451,6 @@ namespace Avalonia.Win32
if (!_isFullScreenActive && ((oldProperties.Decorations != newProperties.Decorations) || forceChanges))
{
var style = GetStyle();
var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0;
var margins = new MARGINS
@ -1497,23 +1463,11 @@ namespace Avalonia.Win32
DwmExtendFrameIntoClientArea(_hwnd, ref margins);
if (_shown || forceChanges)
{
GetClientRect(_hwnd, out var oldClientRect);
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,
SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0 ,0,
SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOMOVE |
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()
{
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()
{
var sizes = new Size?[] { null, new Size(400, 300) };
@ -391,8 +429,8 @@ namespace Avalonia.IntegrationTests.Appium
}
private IDisposable OpenWindow(
Size? size,
ShowWindowMode mode,
Size? size = null,
ShowWindowMode mode = ShowWindowMode.NonOwned,
WindowStartupLocation location = WindowStartupLocation.Manual,
WindowState state = Controls.WindowState.Normal,
bool canResize = true,

Loading…
Cancel
Save