Browse Source

Rewrite WindowImpl.Resize to use SetWindowPlacement (#14470)

x11_allowed_actions
Tom Edwards 2 years ago
committed by GitHub
parent
commit
8730b66cae
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 59
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  2. 4
      src/Windows/Avalonia.Win32/PlatformConstants.cs
  3. 24
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  4. 90
      src/Windows/Avalonia.Win32/WindowImpl.cs

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

@ -105,18 +105,58 @@ namespace Avalonia.Win32.Interop
public enum ShowWindowCommand
{
/// <summary>
/// Hides the window and activates another window.
/// </summary>
Hide = 0,
/// <summary>
/// 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.
/// </summary>
Normal = 1,
/// <summary>
/// Activates the window and displays it as a minimized window.
/// </summary>
ShowMinimized = 2,
/// <summary>
/// Activates the window and displays it as a maximized window.
/// </summary>
Maximize = 3,
ShowMaximized = 3,
/// <inheritdoc cref="Maximize"/>
ShowMaximized = Maximize,
/// <summary>
/// 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.
/// </summary>
ShowNoActivate = 4,
/// <summary>
/// Activates the window and displays it in its current size and position.
/// </summary>
Show = 5,
/// <summary>
/// Minimizes the specified window and activates the next top-level window in the Z order.
/// </summary>
Minimize = 6,
/// <summary>
/// Displays the window as a minimized window. This value is similar to <see cref="ShowMinimized"/>, except the window is not activated.
/// </summary>
ShowMinNoActive = 7,
/// <summary>
/// Displays the window in its current size and position. This value is similar to <see cref="Show"/>, except that the window is not activated.
/// </summary>
ShowNA = 8,
/// <summary>
/// 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.
/// </summary>
Restore = 9,
/// <summary>
/// Sets the show state based on the <see cref="ShowWindowCommand"/> value specified in the STARTUPINFO structure passed to the CreateProcess function
/// by the program that started the application.
/// </summary>
ShowDefault = 10,
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// Specifies flags that control the position of the minimized window and the method by which the window is restored.
/// </summary>
public int Flags;
public WindowPlacementFlags Flags;
/// <summary>
/// The current show state of the window.

4
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);
/// <summary>
/// Windows 10 Anniversary Update
/// </summary>
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);

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

@ -131,7 +131,7 @@ namespace Avalonia.Win32
{
_dpi = (uint)wParam >> 16;
var newDisplayRect = Marshal.PtrToStructure<RECT>(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;

90
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.Win32Properties;
using Avalonia.Logging;
namespace Avalonia.Win32
{
@ -54,6 +55,11 @@ namespace Avalonia.Win32
{ WindowEdge.West, HitTestValues.HTLEFT }
};
/// <summary>
/// The Windows DPI which equates to a <see cref="RenderScaling"/> of 1.0.
/// </summary>
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)
{

Loading…
Cancel
Save