diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index ae6b88f565..7b132f3857 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -105,18 +105,58 @@ namespace Avalonia.Win32.Interop
public enum ShowWindowCommand
{
+ ///
+ /// Hides the window and activates another window.
+ ///
Hide = 0,
+ ///
+ /// Activates and displays a window. If the window is minimized, maximized, or arranged, the system restores it to its original
+ /// size and position. An application should specify this flag when displaying the window for the first time.
+ ///
Normal = 1,
+ ///
+ /// Activates the window and displays it as a minimized window.
+ ///
ShowMinimized = 2,
+ ///
+ /// Activates the window and displays it as a maximized window.
+ ///
Maximize = 3,
- ShowMaximized = 3,
+ ///
+ ShowMaximized = Maximize,
+ ///
+ /// Displays a window in its most recent size and position. This value is similar to , except that the window is not activated.
+ ///
ShowNoActivate = 4,
+ ///
+ /// Activates the window and displays it in its current size and position.
+ ///
Show = 5,
+ ///
+ /// Minimizes the specified window and activates the next top-level window in the Z order.
+ ///
Minimize = 6,
+ ///
+ /// Displays the window as a minimized window. This value is similar to , except the window is not activated.
+ ///
ShowMinNoActive = 7,
+ ///
+ /// Displays the window in its current size and position. This value is similar to , except that the window is not activated.
+ ///
ShowNA = 8,
+ ///
+ /// Activates and displays the window. If the window is minimized, maximized, or arranged, the system restores it to its original size and position.
+ /// An application should specify this flag when restoring a minimized window.
+ ///
Restore = 9,
+ ///
+ /// Sets the show state based on the value specified in the STARTUPINFO structure passed to the CreateProcess function
+ /// by the program that started the application.
+ ///
ShowDefault = 10,
+ ///
+ /// 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.
+ ///
ForceMinimize = 11
}
@@ -1160,6 +1200,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll", SetLastError = true)]
public static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle, bool bMenu, uint dwExStyle);
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool AdjustWindowRectExForDpi(ref RECT lpRect, WindowStyles dwStyle, bool bMenu, WindowStyles dwExStyle, uint dpi);
+
[DllImport("user32.dll")]
public static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);
@@ -1287,7 +1330,7 @@ namespace Avalonia.Win32.Interop
public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
[DllImport("user32.dll", SetLastError = true)]
- public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
+ public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
@@ -1374,6 +1417,8 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll")]
+ public static extern bool SetWindowPlacement(IntPtr hWnd, in WINDOWPLACEMENT windowPlacement);
+ [DllImport("user32.dll")]
public static extern bool SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetFocus();
@@ -2240,6 +2285,14 @@ namespace Avalonia.Win32.Interop
public int dwHoverTime;
}
+ [Flags]
+ public enum WindowPlacementFlags : uint
+ {
+ SetMinPosition = 0x0001,
+ RestoreToMaximized = 0x0002,
+ AsyncWindowPlacement = 0x0004,
+ }
+
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
@@ -2254,7 +2307,7 @@ namespace Avalonia.Win32.Interop
///
/// Specifies flags that control the position of the minimized window and the method by which the window is restored.
///
- public int Flags;
+ public WindowPlacementFlags Flags;
///
/// The current show state of the window.
diff --git a/src/Windows/Avalonia.Win32/PlatformConstants.cs b/src/Windows/Avalonia.Win32/PlatformConstants.cs
index c638314c4d..9f5f152199 100644
--- a/src/Windows/Avalonia.Win32/PlatformConstants.cs
+++ b/src/Windows/Avalonia.Win32/PlatformConstants.cs
@@ -8,6 +8,10 @@ namespace Avalonia.Win32
public const string CursorHandleType = "HCURSOR";
public static readonly Version Windows10 = new Version(10, 0);
+ ///
+ /// Windows 10 Anniversary Update
+ ///
+ public static readonly Version Windows10_1607 = new Version(10, 0, 1607);
public static readonly Version Windows8 = new Version(6, 2);
public static readonly Version Windows8_1 = new Version(6, 3);
public static readonly Version Windows7 = new Version(6, 1);
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index d164b7ca73..0070581e1d 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -131,7 +131,7 @@ namespace Avalonia.Win32
{
_dpi = (uint)wParam >> 16;
var newDisplayRect = Marshal.PtrToStructure(lParam);
- _scaling = _dpi / 96.0;
+ _scaling = _dpi / StandardDpi;
RefreshIcon();
ScalingChanged?.Invoke(_scaling);
@@ -613,14 +613,6 @@ namespace Avalonia.Win32
{
var size = (SizeCommand)wParam;
- if (Resized != null &&
- (size == SizeCommand.Restored ||
- size == SizeCommand.Maximized))
- {
- var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
- Resized(clientSize / RenderScaling, _resizeReason);
- }
-
var windowState = size switch
{
SizeCommand.Maximized => WindowState.Maximized,
@@ -629,10 +621,20 @@ namespace Avalonia.Win32
_ => WindowState.Normal,
};
- if (windowState != _lastWindowState)
+ var stateChanged = windowState != _lastWindowState;
+ _lastWindowState = windowState;
+
+ if (Resized != null &&
+ (size == SizeCommand.Restored ||
+ size == SizeCommand.Maximized))
{
- _lastWindowState = windowState;
+ var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
+ Resized(clientSize / RenderScaling, _resizeReason);
+ }
+
+ if (stateChanged)
+ {
var newWindowProperties = _windowProperties;
newWindowProperties.WindowState = windowState;
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index f2b536dc71..fe65a881c6 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -28,6 +28,7 @@ using Avalonia.Platform.Storage.FileIO;
using Avalonia.Threading;
using static Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl;
using static Avalonia.Controls.Platform.Win32SpecificOptions;
+using Avalonia.Logging;
namespace Avalonia.Win32
{
@@ -54,6 +55,11 @@ namespace Avalonia.Win32
{ WindowEdge.West, HitTestValues.HTLEFT }
};
+ ///
+ /// The Windows DPI which equates to a of 1.0.
+ ///
+ public const double StandardDpi = 96;
+
private SavedWindowInfo _savedWindowInfo;
private bool _isFullScreenActive;
private bool _isClientAreaExtended;
@@ -287,8 +293,7 @@ namespace Avalonia.Win32
return WindowState.FullScreen;
}
- var placement = default(WINDOWPLACEMENT);
- GetWindowPlacement(_hwnd, ref placement);
+ GetWindowPlacement(_hwnd, out var placement);
return placement.ShowCmd switch
{
@@ -559,29 +564,59 @@ namespace Avalonia.Win32
public void Resize(Size value, WindowResizeReason reason)
{
- if (WindowState != WindowState.Normal)
- return;
-
int requestedClientWidth = (int)(value.Width * RenderScaling);
int requestedClientHeight = (int)(value.Height * RenderScaling);
- GetClientRect(_hwnd, out var clientRect);
+ GetClientRect(_hwnd, out var currentClientRect);
+ if (currentClientRect.Width == requestedClientWidth && currentClientRect.Height == requestedClientHeight)
+ {
+ // Don't update our window position if the client size is already correct. This leads to Windows updating our
+ // "normal position" (i.e. restored bounds) to match our maximised or areo snap size, which is incorrect behaviour.
+ // We only want to proceed with this method if the new size is coming from Avalonia.
+ return;
+ }
- // do comparison after scaling to avoid rounding issues
- if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height)
+ if (_lastWindowState == WindowState.FullScreen)
{
- GetWindowRect(_hwnd, out var windowRect);
+ // Fullscreen mode is really a restored window without a frame filling the whole monitor.
+ // It doesn't make sense to resize the window in this state, so ignore this request.
+ Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)?.Log(this, "Ignoring resize event on fullscreen window.");
+ return;
+ }
- using var scope = SetResizeReason(reason);
- SetWindowPos(
- _hwnd,
- IntPtr.Zero,
- 0,
- 0,
- requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width),
- requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height),
- SetWindowPosFlags.SWP_RESIZE);
+ 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,
+ };
+
+ var requestedWindowRect = _isClientAreaExtended ? requestedClientRect : ClientRectToWindowRect(requestedClientRect);
+
+ if (requestedWindowRect.Width == windowPlacement.NormalPosition.Width && requestedWindowRect.Height == windowPlacement.NormalPosition.Height)
+ {
+ return;
}
+
+ windowPlacement.NormalPosition = requestedWindowRect;
+
+ windowPlacement.ShowCmd = _lastWindowState switch
+ {
+ WindowState.Minimized => ShowWindowCommand.ShowMinNoActive,
+ WindowState.Maximized => ShowWindowCommand.ShowMaximized,
+ WindowState.Normal => ShowWindowCommand.ShowNoActivate,
+ _ => throw new NotImplementedException(),
+ };
+
+ using var scope = SetResizeReason(reason);
+ SetWindowPlacement(_hwnd, in windowPlacement);
}
public void Activate()
@@ -913,7 +948,7 @@ namespace Avalonia.Win32
out _dpi,
out _) == 0)
{
- _scaling = _dpi / 96.0;
+ _scaling = _dpi / StandardDpi;
}
}
}
@@ -1473,6 +1508,23 @@ namespace Avalonia.Win32
MF_BYCOMMAND | MF_ENABLED);
}
+ private RECT ClientRectToWindowRect(RECT clientRect, WindowStyles? styleOverride = null, WindowStyles? extendedStyleOverride = null)
+ {
+ var style = styleOverride ?? GetStyle();
+ var extendedStyle = extendedStyleOverride ?? GetExtendedStyle();
+
+ var result = Win32Platform.WindowsVersion < PlatformConstants.Windows10_1607
+ ? AdjustWindowRectEx(ref clientRect, (uint)style, false, (uint)extendedStyle)
+ : AdjustWindowRectExForDpi(ref clientRect, style, false, extendedStyle, (uint)(RenderScaling * StandardDpi));
+
+ if (!result)
+ {
+ Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
+ }
+
+ return clientRect;
+ }
+
#if USE_MANAGED_DRAG
private Point ScreenToClient(Point point)
{