Skip to content

OverlappedPresenter Maximize Crashes with "Layout Cycle Detected" #10439

@jpageacsys

Description

@jpageacsys

Describe the bug

This was an issue a while ago but after the last update it started happening again.
I have a WinUI App. After the window launches, I save the position and presenter state to the app settings. Then the next time the window opens before I attach the changed listener, I restore the position, size, and state from the settings.

This is the code in the constructor for the MainWindow
RestoreWindowPositionAndSize(); this.AppWindow.Changed += AppWindow_Changed;

This is the functions:

private void RestoreWindowPositionAndSize()
{
    try
    {
        var localSettings = ApplicationData.Current.LocalSettings.Values;

        object xSetting = localSettings.TryGetValue(X_POSITION, out object? xValue) ? xValue : 0;
        int x = int.TryParse(xSetting?.ToString(), out int xPosition) ? xPosition : 0;
        object ySetting = localSettings.TryGetValue(Y_POSITION, out object? yValue) ? yValue : 0;
        int y = int.TryParse(ySetting?.ToString(), out int yPosition) ? yPosition : 0;

        object widthSetting = localSettings.TryGetValue(WIDTH, out object? widthValue) ? widthValue : 800;
        int width = int.TryParse(widthSetting?.ToString(), out int widthValueInt) ? widthValueInt : 800;
        object heightSetting = localSettings.TryGetValue(HEIGHT, out object? heightValue) ? heightValue : 600;
        int height = int.TryParse(heightSetting?.ToString(), out int heightValueInt) ? heightValueInt : 600;

        Debug.WriteLine($"Restoring window position and size: {x}, {y}, {width}, {height}");
        AppWindow?.MoveAndResize(new Windows.Graphics.RectInt32(x, y, width, height), DisplayArea.Primary);

        if (IsWindowOffScreen(this))
        {
            AppWindow?.MoveAndResize(new Windows.Graphics.RectInt32(0, 0, 800, 600), DisplayArea.Primary);
        }
        Debug.WriteLine("Restored window position");

        if (localSettings.TryGetValue(PRESENTER_STATE, out var stateValue) && AppWindow?.Presenter is OverlappedPresenter presenter)
        {
            var state = (OverlappedPresenterState)Enum.ToObject(typeof(OverlappedPresenterState), stateValue);
            if (state == OverlappedPresenterState.Maximized)
                presenter.Maximize();
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Failed to restore window position and size: {ex.Message}");
    }
}
private enum MonitorFlag : uint
{
    /// <summary>Returns NULL.</summary>
    MONITOR_DEFAULTTONULL = 0,
    /// <summary>Returns a handle to the primary display monitor.</summary>
    MONITOR_DEFAULTTOPRIMARY = 1,
    /// <summary>Returns a handle to the display monitor that is nearest to the window.</summary>
    MONITOR_DEFAULTTONEAREST = 2
}

[LibraryImport("user32.dll")]
private static partial IntPtr MonitorFromWindow(IntPtr hwnd, MonitorFlag flag);

public static bool IsWindowOffScreen(Window window)
{
    try
    {
        // get pointer from window
        var hWnd = WindowNative.GetWindowHandle(window);
        // get monitor from pointer and default to null if none is found
        var monitor = MonitorFromWindow(hWnd, MonitorFlag.MONITOR_DEFAULTTONULL);
        // will return true if no monitor is found for the window
        return monitor == IntPtr.Zero;
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Failed to determine if window is off screen: {ex.Message}");
        return true;
    }
}

private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
    // save position, size, and presenter so it can be restored on next start
    if (args.DidSizeChange)
    {
        ApplicationData.Current.LocalSettings.Values[WIDTH] = sender.Size.Width;
        ApplicationData.Current.LocalSettings.Values[HEIGHT] = sender.Size.Height;
    }
    if (args.DidPositionChange)
    {
        ApplicationData.Current.LocalSettings.Values[X_POSITION] = sender.Position.X;
        ApplicationData.Current.LocalSettings.Values[Y_POSITION] = sender.Position.Y;
    }
    if (args.DidPresenterChange)
    {
        if (sender.Presenter is OverlappedPresenter presenter)
        {
            ApplicationData.Current.LocalSettings.Values[PRESENTER_STATE] = (int)presenter.State;
        }
        Debug.WriteLine("Presenter changed to " + sender.Presenter.Kind);
        ApplicationData.Current.LocalSettings.Values[PRESENTER_KIND] = (int)sender.Presenter.Kind;
    }
}

This was working correctly and works correctly most of the time, but it has some trouble on 4K monitors with certain scaling.

The example I can reproduce on my computer is my monitor with a 3840 x 2160 resolution works if I have scaling set to 150% in windows. Anything less than this (125% or 100%) causes the following error with a crash.

Removing the move and resize doesn't fix the issue but commenting out presenter.Maximize() fixes the issue. Is there a workaround for this?

Microsoft.UI.Xaml.dll!00007FF814CBD5D8: 88000FA8 - AG_E_LAYOUT_CYCLE Layout Iteration Countdown: 7. Launching EffectiveViewport Pass. Layout Iteration Countdown: 6. Raising SizeChanged Events. Layout Iteration Countdown: 5. Launching Measure Pass. Layout Iteration Countdown: 4. Launching Arrange Pass. Layout Iteration Countdown: 3. Launching EffectiveViewport Pass. Layout Iteration Countdown: 2. Raising SizeChanged Events. Layout Iteration Countdown: 1. Launching Measure Pass. Layout Iteration Countdown: 0. Launching Arrange Pass. Exception thrown at 0x00007FF9354EAB6A (KernelBase.dll) in AcSYS Tools UWP.exe: WinRT originate error - 0x802B0014 : 'Layout cycle detected. Layout could not complete.'.

Steps to reproduce the bug

On a 4K monitor with no scaling, call AppWindow.Presenter.Maximize() in the constructor for the MainWindow.

Expected behavior

It should be able to go fullscreen from the code. Maximizing manually doesn't result in the error.

Screenshots

No response

NuGet package version

WinUI 3 - Windows App SDK 1.6.6: 1.6.250228001

Windows version

Windows 11 (24H2): Build 26100

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions