Skip to content

Commit 92ab0e3

Browse files
Rewrite WindowImpl.Resize to use SetWindowPlacement
1 parent d6f5e7d commit 92ab0e3

File tree

4 files changed

+144
-33
lines changed

4 files changed

+144
-33
lines changed

src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,58 @@ public enum SizeCommand
105105

106106
public enum ShowWindowCommand
107107
{
108+
/// <summary>
109+
/// Hides the window and activates another window.
110+
/// </summary>
108111
Hide = 0,
112+
/// <summary>
113+
/// Activates and displays a window. If the window is minimized, maximized, or arranged, the system restores it to its original
114+
/// size and position. An application should specify this flag when displaying the window for the first time.
115+
/// </summary>
109116
Normal = 1,
117+
/// <summary>
118+
/// Activates the window and displays it as a minimized window.
119+
/// </summary>
110120
ShowMinimized = 2,
121+
/// <summary>
122+
/// Activates the window and displays it as a maximized window.
123+
/// </summary>
111124
Maximize = 3,
112-
ShowMaximized = 3,
125+
/// <inheritdoc cref="Maximize"/>
126+
ShowMaximized = Maximize,
127+
/// <summary>
128+
/// Displays a window in its most recent size and position. This value is similar to <see cref="Normal"/>, except that the window is not activated.
129+
/// </summary>
113130
ShowNoActivate = 4,
131+
/// <summary>
132+
/// Activates the window and displays it in its current size and position.
133+
/// </summary>
114134
Show = 5,
135+
/// <summary>
136+
/// Minimizes the specified window and activates the next top-level window in the Z order.
137+
/// </summary>
115138
Minimize = 6,
139+
/// <summary>
140+
/// Displays the window as a minimized window. This value is similar to <see cref="ShowMinimized"/>, except the window is not activated.
141+
/// </summary>
116142
ShowMinNoActive = 7,
143+
/// <summary>
144+
/// Displays the window in its current size and position. This value is similar to <see cref="Show"/>, except that the window is not activated.
145+
/// </summary>
117146
ShowNA = 8,
147+
/// <summary>
148+
/// Activates and displays the window. If the window is minimized, maximized, or arranged, the system restores it to its original size and position.
149+
/// An application should specify this flag when restoring a minimized window.
150+
/// </summary>
118151
Restore = 9,
152+
/// <summary>
153+
/// Sets the show state based on the <see cref="ShowWindowCommand"/> value specified in the STARTUPINFO structure passed to the CreateProcess function
154+
/// by the program that started the application.
155+
/// </summary>
119156
ShowDefault = 10,
157+
/// <summary>
158+
/// Minimizes a window, even if the thread that owns the window is not responding. This flag should only be used when minimizing windows from a different thread.
159+
/// </summary>
120160
ForceMinimize = 11
121161
}
122162

@@ -1160,6 +1200,9 @@ public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest,
11601200
[DllImport("user32.dll", SetLastError = true)]
11611201
public static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle, bool bMenu, uint dwExStyle);
11621202

1203+
[DllImport("user32.dll", SetLastError = true)]
1204+
public static extern bool AdjustWindowRectExForDpi(ref RECT lpRect, WindowStyles dwStyle, bool bMenu, WindowStyles dwExStyle, uint dpi);
1205+
11631206
[DllImport("user32.dll")]
11641207
public static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);
11651208

@@ -1287,7 +1330,7 @@ public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr handle)
12871330
public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
12881331

12891332
[DllImport("user32.dll", SetLastError = true)]
1290-
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
1333+
public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
12911334

12921335
[DllImport("user32.dll")]
12931336
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
@@ -1374,6 +1417,8 @@ public static extern IntPtr CreateIconFromResourceEx(byte* pbIconBits, uint cbIc
13741417
[DllImport("user32.dll")]
13751418
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);
13761419
[DllImport("user32.dll")]
1420+
public static extern bool SetWindowPlacement(IntPtr hWnd, in WINDOWPLACEMENT windowPlacement);
1421+
[DllImport("user32.dll")]
13771422
public static extern bool SetFocus(IntPtr hWnd);
13781423
[DllImport("user32.dll")]
13791424
public static extern IntPtr GetFocus();
@@ -2240,6 +2285,14 @@ public struct TRACKMOUSEEVENT
22402285
public int dwHoverTime;
22412286
}
22422287

2288+
[Flags]
2289+
public enum WindowPlacementFlags : uint
2290+
{
2291+
SetMinPosition = 0x0001,
2292+
RestoreToMaximized = 0x0002,
2293+
AsyncWindowPlacement = 0x0004,
2294+
}
2295+
22432296
[StructLayout(LayoutKind.Sequential)]
22442297
public struct WINDOWPLACEMENT
22452298
{
@@ -2254,7 +2307,7 @@ public struct WINDOWPLACEMENT
22542307
/// <summary>
22552308
/// Specifies flags that control the position of the minimized window and the method by which the window is restored.
22562309
/// </summary>
2257-
public int Flags;
2310+
public WindowPlacementFlags Flags;
22582311

22592312
/// <summary>
22602313
/// The current show state of the window.

src/Windows/Avalonia.Win32/PlatformConstants.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ internal static class PlatformConstants
88
public const string CursorHandleType = "HCURSOR";
99

1010
public static readonly Version Windows10 = new Version(10, 0);
11+
/// <summary>
12+
/// Windows 10 Anniversary Update
13+
/// </summary>
14+
public static readonly Version Windows10_1607 = new Version(10, 0, 1607);
1115
public static readonly Version Windows8 = new Version(6, 2);
1216
public static readonly Version Windows8_1 = new Version(6, 3);
1317
public static readonly Version Windows7 = new Version(6, 1);

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
131131
{
132132
_dpi = (uint)wParam >> 16;
133133
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
134-
_scaling = _dpi / 96.0;
134+
_scaling = _dpi / StandardDpi;
135135
RefreshIcon();
136136
ScalingChanged?.Invoke(_scaling);
137137

@@ -613,14 +613,6 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
613613
{
614614
var size = (SizeCommand)wParam;
615615

616-
if (Resized != null &&
617-
(size == SizeCommand.Restored ||
618-
size == SizeCommand.Maximized))
619-
{
620-
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
621-
Resized(clientSize / RenderScaling, _resizeReason);
622-
}
623-
624616
var windowState = size switch
625617
{
626618
SizeCommand.Maximized => WindowState.Maximized,
@@ -629,10 +621,20 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam,
629621
_ => WindowState.Normal,
630622
};
631623

632-
if (windowState != _lastWindowState)
624+
var stateChanged = windowState != _lastWindowState;
625+
_lastWindowState = windowState;
626+
627+
if (Resized != null &&
628+
(size == SizeCommand.Restored ||
629+
size == SizeCommand.Maximized))
633630
{
634-
_lastWindowState = windowState;
631+
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
632+
Resized(clientSize / RenderScaling, _resizeReason);
633+
}
635634

635+
636+
if (stateChanged)
637+
{
636638
var newWindowProperties = _windowProperties;
637639

638640
newWindowProperties.WindowState = windowState;

src/Windows/Avalonia.Win32/WindowImpl.cs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
using Avalonia.Threading;
2929
using static Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl;
3030
using static Avalonia.Controls.Platform.Win32SpecificOptions;
31+
using Avalonia.Logging;
3132

3233
namespace Avalonia.Win32
3334
{
@@ -54,6 +55,11 @@ internal partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindow
5455
{ WindowEdge.West, HitTestValues.HTLEFT }
5556
};
5657

58+
/// <summary>
59+
/// The Windows DPI which equates to a <see cref="RenderScaling"/> of 1.0.
60+
/// </summary>
61+
public const double StandardDpi = 96;
62+
5763
private SavedWindowInfo _savedWindowInfo;
5864
private bool _isFullScreenActive;
5965
private bool _isClientAreaExtended;
@@ -287,8 +293,7 @@ public WindowState WindowState
287293
return WindowState.FullScreen;
288294
}
289295

290-
var placement = default(WINDOWPLACEMENT);
291-
GetWindowPlacement(_hwnd, ref placement);
296+
GetWindowPlacement(_hwnd, out var placement);
292297

293298
return placement.ShowCmd switch
294299
{
@@ -559,29 +564,59 @@ public void SetMinMaxSize(Size minSize, Size maxSize)
559564

560565
public void Resize(Size value, WindowResizeReason reason)
561566
{
562-
if (WindowState != WindowState.Normal)
563-
return;
564-
565567
int requestedClientWidth = (int)(value.Width * RenderScaling);
566568
int requestedClientHeight = (int)(value.Height * RenderScaling);
567569

568-
GetClientRect(_hwnd, out var clientRect);
570+
GetClientRect(_hwnd, out var currentClientRect);
571+
if (currentClientRect.Width == requestedClientWidth && currentClientRect.Height == requestedClientHeight)
572+
{
573+
// Don't update our window position if the client size is already correct. This leads to Windows updating our
574+
// "normal position" (i.e. restored bounds) to match our maximised or areo snap size, which is incorrect behaviour.
575+
// We only want to proceed with this method if the new size is coming from Avalonia.
576+
return;
577+
}
569578

570-
// do comparison after scaling to avoid rounding issues
571-
if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height)
579+
if (_lastWindowState == WindowState.FullScreen)
572580
{
573-
GetWindowRect(_hwnd, out var windowRect);
581+
// Fullscreen mode is really a restored window without a frame filling the whole monitor.
582+
// It doesn't make sense to resize the window in this state, so ignore this request.
583+
Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)?.Log(this, "Ignoring resize event on fullscreen window.");
584+
return;
585+
}
574586

575-
using var scope = SetResizeReason(reason);
576-
SetWindowPos(
577-
_hwnd,
578-
IntPtr.Zero,
579-
0,
580-
0,
581-
requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width),
582-
requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height),
583-
SetWindowPosFlags.SWP_RESIZE);
587+
GetWindowPlacement(_hwnd, out var windowPlacement);
588+
589+
var clientScreenOrigin = new POINT();
590+
ClientToScreen(_hwnd, ref clientScreenOrigin);
591+
592+
var requestedClientRect = new RECT
593+
{
594+
left = clientScreenOrigin.X,
595+
right = clientScreenOrigin.X + requestedClientWidth,
596+
597+
top = clientScreenOrigin.Y,
598+
bottom = clientScreenOrigin.Y + requestedClientHeight,
599+
};
600+
601+
var requestedWindowRect = _isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect(requestedClientRect);
602+
603+
if (requestedWindowRect.Width == windowPlacement.NormalPosition.Width && requestedWindowRect.Height == windowPlacement.NormalPosition.Height)
604+
{
605+
return;
584606
}
607+
608+
windowPlacement.NormalPosition = requestedWindowRect;
609+
610+
windowPlacement.ShowCmd = _lastWindowState switch
611+
{
612+
WindowState.Minimized => ShowWindowCommand.ShowMinNoActive,
613+
WindowState.Maximized => ShowWindowCommand.ShowMaximized,
614+
WindowState.Normal => ShowWindowCommand.ShowNoActivate,
615+
_ => throw new NotImplementedException(),
616+
};
617+
618+
using var scope = SetResizeReason(reason);
619+
SetWindowPlacement(_hwnd, in windowPlacement);
585620
}
586621

587622
public void Activate()
@@ -913,7 +948,7 @@ private void CreateWindow()
913948
out _dpi,
914949
out _) == 0)
915950
{
916-
_scaling = _dpi / 96.0;
951+
_scaling = _dpi / StandardDpi;
917952
}
918953
}
919954
}
@@ -1473,6 +1508,23 @@ private static void EnableCloseButton(IntPtr hwnd)
14731508
MF_BYCOMMAND | MF_ENABLED);
14741509
}
14751510

1511+
private RECT ClientRectToWindowRect(RECT clientRect, WindowStyles? styleOverride = null, WindowStyles? extendedStyleOverride = null)
1512+
{
1513+
var style = styleOverride ?? GetStyle();
1514+
var extendedStyle = extendedStyleOverride ?? GetExtendedStyle();
1515+
1516+
var result = Win32Platform.WindowsVersion < PlatformConstants.Windows10_1607
1517+
? AdjustWindowRectEx(ref clientRect, (uint)style, false, (uint)extendedStyle)
1518+
: AdjustWindowRectExForDpi(ref clientRect, style, false, extendedStyle, (uint)(RenderScaling * StandardDpi));
1519+
1520+
if (!result)
1521+
{
1522+
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
1523+
}
1524+
1525+
return clientRect;
1526+
}
1527+
14761528
#if USE_MANAGED_DRAG
14771529
private Point ScreenToClient(Point point)
14781530
{

0 commit comments

Comments
 (0)