Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -648,6 +645,8 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
Resized(clientSize / RenderScaling, _resizeReason);
}

if (IsWindowVisible(_hwnd) && !_shown)
_shown = true;

if (stateChanged)
{
Expand All @@ -666,11 +665,17 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,

if (_isClientAreaExtended)
{
UpdateExtendMargins();
ExtendClientArea();

ExtendClientAreaToDecorationsChanged?.Invoke(true);
}
}
else if (windowState == WindowState.Maximized && _isClientAreaExtended)
{
ExtendClientArea();

ExtendClientAreaToDecorationsChanged?.Invoke(true);
}

return IntPtr.Zero;
}
Expand All @@ -681,8 +686,7 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,

case WindowsMessage.WM_MOVE:
{
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff),
(short)(ToInt32(lParam) >> 16)));
PositionChanged?.Invoke(Position);
return IntPtr.Zero;
}

Expand Down
102 changes: 28 additions & 74 deletions src/Windows/Avalonia.Win32/WindowImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,14 +263,8 @@ public Size FrameSize
{
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;
}
}

Expand Down Expand Up @@ -510,6 +504,7 @@ private unsafe bool SetUseHostBackdropBrush(bool useHostBackdropBrush)
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 } :
Expand All @@ -520,15 +515,10 @@ public PixelPoint Position
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,
Expand All @@ -542,23 +532,6 @@ public PixelPoint Position

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;

Expand Down Expand Up @@ -594,25 +567,29 @@ public void Resize(Size value, WindowResizeReason reason)

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
Expand Down Expand Up @@ -1025,14 +1002,8 @@ private void SetFullScreen(bool fullscreen)
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();
Expand Down Expand Up @@ -1141,16 +1112,8 @@ private void ExtendClientArea()
_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();
Expand All @@ -1170,8 +1133,6 @@ private void ExtendClientArea()
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();

Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), WindowResizeReason.Layout);

unsafe
{
int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT;
Expand All @@ -1189,6 +1150,13 @@ private void ExtendClientArea()
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);
}

Expand Down Expand Up @@ -1454,7 +1422,7 @@ private void UpdateWindowProperties(WindowProperties newProperties, bool forceCh
{
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;
}
Expand Down Expand Up @@ -1483,8 +1451,6 @@ private void UpdateWindowProperties(WindowProperties newProperties, bool forceCh

if (!_isFullScreenActive && ((oldProperties.Decorations != newProperties.Decorations) || forceChanges))
{
var style = GetStyle();

var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0;

var margins = new MARGINS
Expand All @@ -1497,23 +1463,11 @@ private void UpdateWindowProperties(WindowProperties newProperties, bool forceCh

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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public WindowDecorationsTests(DefaultAppFixture fixture)
{
}

[PlatformFact(TestPlatforms.MacOS)] // TODO fix me on Windows
[Fact]
public void Window_Size_Should_Be_Consistent_Between_Toggles()
{
var window = Session.FindElementByAccessibilityId("MainWindow");
Expand Down
42 changes: 40 additions & 2 deletions tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,44 @@ public void Window_Has_Disabled_Maximize_Button_When_CanResize_Is_False(ShowWind
}
}

[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) };
Expand Down Expand Up @@ -391,8 +429,8 @@ private static void AssertCloseEnough(PixelPoint expected, PixelPoint actual)
}

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,
Expand Down
Loading