Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,67 @@ await CreateHandlerAndAddToWindow<IWindowHandler>(mainPage, async (handler) =>
});
}

[Fact]
public async Task WindowsBoundsWhenMaximized()
{
SetupBuilder();
var mainPage = new NavigationPage(new ContentPage());

await CreateHandlerAndAddToWindow<IWindowHandler>(mainPage, async (handler) =>
{
var appWindowPlatform = handler.PlatformView.GetAppWindow();
Assert.NotNull(appWindowPlatform?.Presenter);
var presenter = Assert.IsType<OverlappedPresenter>(appWindowPlatform.Presenter);

// maximize window
presenter.Maximize();
var appWindow = handler.PlatformView.GetWindow();
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
await AssertEventually(() => appWindow.X == 0);
await AssertEventually(() => appWindow.Y == 0);
// Verify width and height are non-zero (regression: Height was reported as 0 when maximized)
await AssertEventually(() => appWindow.Width > 0);
await AssertEventually(() => appWindow.Height > 0);
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
});
}

[Fact]
public async Task WindowsYAndHeightCorrectWhenClosingMaximizedWindow()
{
SetupBuilder();
var mainPage = new NavigationPage(new ContentPage());

double destroyingY = double.NaN;
double destroyingHeight = double.NaN;

await CreateHandlerAndAddToWindow<IWindowHandler>(mainPage, async (handler) =>
{
var window = handler.VirtualView as Window;
Assert.NotNull(window);

var appWindowPlatform = handler.PlatformView.GetAppWindow();
Assert.NotNull(appWindowPlatform?.Presenter);
var presenter = Assert.IsType<OverlappedPresenter>(appWindowPlatform.Presenter);

// Capture the frame values at destroy time so we can verify them after cleanup
window.Destroying += (s, e) =>
{
destroyingY = window.Y;
destroyingHeight = window.Height;
};

// Maximize the window and wait until the frame update reflects the maximized bounds
presenter.Maximize();
await AssertEventually(() => window.Y == 0);
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
await AssertEventually(() => window.Height > 0);
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Regression Prevention and Test Coverage - This does not prove the Destroying-time regression is fixed. window.Height > 0 can be true before maximize completes, and the post-cleanup assertions only require Height > 0 and finite Y, so the reported broken values (Y shifted by 8, Height too large by 8) would still pass. Capture/compute the expected maximized work-area (or DWM extended frame) bounds before cleanup and assert Destroying Y and Height match those values within tolerance.

});

// The window is destroyed during CreateHandlerAndAddToWindow cleanup.
// Verify that the Y and Height values at Destroying time were non-negative / non-zero.
Assert.False(double.IsNaN(destroyingHeight), "Window.Destroying event was not raised");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] Regression PreventionWindowsYAndHeightCorrectWhenClosingMaximizedWindow checks destroyingHeight for NaN to prove Destroying fired. Add the same sentinel check for destroyingY so a missing or malformed captured Y value reports the lifecycle problem directly instead of falling through to the tolerance assertion.

Assert.True(destroyingHeight > 0, $"Height should be > 0 when closing a maximized window, but was {destroyingHeight}");
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
Assert.True(destroyingY >= 0, $"Y should be >= 0 when closing a maximized window, but was {destroyingY}");
}

[Fact]
public async Task ToggleFullscreenTitleBarWorks()
{
Expand Down
22 changes: 21 additions & 1 deletion src/Core/src/Handlers/Window/WindowHandler.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,32 @@ void OnWindowChanged(AppWindow sender, AppWindowChangedEventArgs args)

UpdateVirtualViewFrame(sender);
}

void UpdateVirtualViewFrame(AppWindow appWindow)
{
var hwnd = PlatformView.GetWindowHandle();
if (hwnd == IntPtr.Zero)
{
return;
}
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated

var bounds = hwnd.GetExtendedFrameBounds();
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated

var size = appWindow.Size;
var pos = appWindow.Position;

if (appWindow.Presenter is OverlappedPresenter presenter &&
presenter.IsMaximizable &&
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
presenter.State == OverlappedPresenterState.Maximized &&
bounds.Width > 0 && bounds.Height > 0)
{
// If the window is maximized, we need to use the bounds of the window
// instead of the size and position of the app window.
// Only apply when GetExtendedFrameBounds returned a valid (non-empty) rect;
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
// if DWM failed (returned EmptyRect), keep the AppWindow values as fallback.
size = new SizeInt32((int)bounds.Width, (int)bounds.Height);
pos = new PointInt32((int)bounds.X, (int)bounds.Y);
}

var density = PlatformView.GetDisplayDensity();

VirtualView.FrameChanged(new Rect(
Expand Down
38 changes: 38 additions & 0 deletions src/Core/src/Platform/Windows/WindowExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
Expand All @@ -14,6 +15,43 @@ namespace Microsoft.Maui.Platform
{
public static partial class WindowExtensions
{
const int DWMWA_EXTENDED_FRAME_BOUNDS = 9;

[DllImport("dwmapi.dll", PreserveSig = true)]
static extern int DwmGetWindowAttribute(
IntPtr hwnd,
int dwAttribute,
out PlatformMethods.RECT pvAttribute,
int cbAttribute);
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated

static readonly Rect EmptyRect = new(0, 0, 0, 0);

internal static Rect GetExtendedFrameBounds(this IntPtr hwnd)
{
Comment thread
Dhivya-SF4094 marked this conversation as resolved.
Outdated
if (hwnd == IntPtr.Zero)
{
return EmptyRect;
}

try
{
PlatformMethods.RECT rect = default;
int result = DwmGetWindowAttribute(
hwnd,
DWMWA_EXTENDED_FRAME_BOUNDS,
out rect,
Marshal.SizeOf<PlatformMethods.RECT>());

return result == 0
? new Rect(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top)
: EmptyRect;
}
catch
{
return EmptyRect;
}
}

internal static Rect[]? GetDefaultTitleBarDragRectangles(this UI.Xaml.Window platformWindow, IWindow window)
{
if (window?.Handler?.MauiContext is IMauiContext mauiContext)
Expand Down
Loading