Browse Source

Rewrite WindowImpl.Resize to use SetWindowPlacement (#14470)

x11_allowed_actions
Tom Edwards 3 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 public enum ShowWindowCommand
{ {
/// <summary>
/// Hides the window and activates another window.
/// </summary>
Hide = 0, 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, Normal = 1,
/// <summary>
/// Activates the window and displays it as a minimized window.
/// </summary>
ShowMinimized = 2, ShowMinimized = 2,
/// <summary>
/// Activates the window and displays it as a maximized window.
/// </summary>
Maximize = 3, 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, ShowNoActivate = 4,
/// <summary>
/// Activates the window and displays it in its current size and position.
/// </summary>
Show = 5, Show = 5,
/// <summary>
/// Minimizes the specified window and activates the next top-level window in the Z order.
/// </summary>
Minimize = 6, 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, 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, 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, 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, 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 ForceMinimize = 11
} }
@ -1160,6 +1200,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle, bool bMenu, uint dwExStyle); 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")] [DllImport("user32.dll")]
public static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint); 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); public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
[DllImport("user32.dll", SetLastError = true)] [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")] [DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
@ -1374,6 +1417,8 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags); public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool SetWindowPlacement(IntPtr hWnd, in WINDOWPLACEMENT windowPlacement);
[DllImport("user32.dll")]
public static extern bool SetFocus(IntPtr hWnd); public static extern bool SetFocus(IntPtr hWnd);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern IntPtr GetFocus(); public static extern IntPtr GetFocus();
@ -2240,6 +2285,14 @@ namespace Avalonia.Win32.Interop
public int dwHoverTime; public int dwHoverTime;
} }
[Flags]
public enum WindowPlacementFlags : uint
{
SetMinPosition = 0x0001,
RestoreToMaximized = 0x0002,
AsyncWindowPlacement = 0x0004,
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT public struct WINDOWPLACEMENT
{ {
@ -2254,7 +2307,7 @@ namespace Avalonia.Win32.Interop
/// <summary> /// <summary>
/// Specifies flags that control the position of the minimized window and the method by which the window is restored. /// Specifies flags that control the position of the minimized window and the method by which the window is restored.
/// </summary> /// </summary>
public int Flags; public WindowPlacementFlags Flags;
/// <summary> /// <summary>
/// The current show state of the window. /// 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 const string CursorHandleType = "HCURSOR";
public static readonly Version Windows10 = new Version(10, 0); 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 = new Version(6, 2);
public static readonly Version Windows8_1 = new Version(6, 3); public static readonly Version Windows8_1 = new Version(6, 3);
public static readonly Version Windows7 = new Version(6, 1); 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; _dpi = (uint)wParam >> 16;
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam); var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
_scaling = _dpi / 96.0; _scaling = _dpi / StandardDpi;
RefreshIcon(); RefreshIcon();
ScalingChanged?.Invoke(_scaling); ScalingChanged?.Invoke(_scaling);
@ -613,14 +613,6 @@ namespace Avalonia.Win32
{ {
var size = (SizeCommand)wParam; 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 var windowState = size switch
{ {
SizeCommand.Maximized => WindowState.Maximized, SizeCommand.Maximized => WindowState.Maximized,
@ -629,10 +621,20 @@ namespace Avalonia.Win32
_ => WindowState.Normal, _ => 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; var newWindowProperties = _windowProperties;
newWindowProperties.WindowState = windowState; newWindowProperties.WindowState = windowState;

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

@ -28,6 +28,7 @@ using Avalonia.Platform.Storage.FileIO;
using Avalonia.Threading; using Avalonia.Threading;
using static Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl; using static Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl;
using static Avalonia.Controls.Win32Properties; using static Avalonia.Controls.Win32Properties;
using Avalonia.Logging;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
@ -54,6 +55,11 @@ namespace Avalonia.Win32
{ WindowEdge.West, HitTestValues.HTLEFT } { 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 SavedWindowInfo _savedWindowInfo;
private bool _isFullScreenActive; private bool _isFullScreenActive;
private bool _isClientAreaExtended; private bool _isClientAreaExtended;
@ -287,8 +293,7 @@ namespace Avalonia.Win32
return WindowState.FullScreen; return WindowState.FullScreen;
} }
var placement = default(WINDOWPLACEMENT); GetWindowPlacement(_hwnd, out var placement);
GetWindowPlacement(_hwnd, ref placement);
return placement.ShowCmd switch return placement.ShowCmd switch
{ {
@ -559,29 +564,59 @@ namespace Avalonia.Win32
public void Resize(Size value, WindowResizeReason reason) public void Resize(Size value, WindowResizeReason reason)
{ {
if (WindowState != WindowState.Normal)
return;
int requestedClientWidth = (int)(value.Width * RenderScaling); int requestedClientWidth = (int)(value.Width * RenderScaling);
int requestedClientHeight = (int)(value.Height * 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 (_lastWindowState == WindowState.FullScreen)
if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height)
{ {
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); GetWindowPlacement(_hwnd, out var windowPlacement);
SetWindowPos(
_hwnd, var clientScreenOrigin = new POINT();
IntPtr.Zero, ClientToScreen(_hwnd, ref clientScreenOrigin);
0,
0, var requestedClientRect = new RECT
requestedClientWidth + (_isClientAreaExtended ? 0 : windowRect.Width - clientRect.Width), {
requestedClientHeight + (_isClientAreaExtended ? 0 : windowRect.Height - clientRect.Height), left = clientScreenOrigin.X,
SetWindowPosFlags.SWP_RESIZE); 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() public void Activate()
@ -913,7 +948,7 @@ namespace Avalonia.Win32
out _dpi, out _dpi,
out _) == 0) out _) == 0)
{ {
_scaling = _dpi / 96.0; _scaling = _dpi / StandardDpi;
} }
} }
} }
@ -1473,6 +1508,23 @@ namespace Avalonia.Win32
MF_BYCOMMAND | MF_ENABLED); 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 #if USE_MANAGED_DRAG
private Point ScreenToClient(Point point) private Point ScreenToClient(Point point)
{ {

Loading…
Cancel
Save