Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix desktop lifetime non-mainwindow cancellation #17059

Merged
merged 8 commits into from
Oct 11, 2024
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia.Collections;
Expand Down Expand Up @@ -184,11 +183,12 @@ private bool DoShutdown(
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in Windows.ToArray())
foreach (var w in new List<Window>(_windows))
MrJul marked this conversation as resolved.
Show resolved Hide resolved
{
if (w.Owner is null)
{
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic);
var ignoreCancel = force || (ShutdownMode == ShutdownMode.OnMainWindowClose && w != MainWindow);
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic, ignoreCancel);
}
}

Expand Down
12 changes: 7 additions & 5 deletions src/Avalonia.Controls/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ public PixelPoint Position
/// </summary>
public void Close()
{
CloseCore(WindowCloseReason.WindowClosing, true);
CloseCore(WindowCloseReason.WindowClosing, true, false);
}

/// <summary>
Expand All @@ -477,10 +477,10 @@ public void Close()
public void Close(object? dialogResult)
{
_dialogResult = dialogResult;
CloseCore(WindowCloseReason.WindowClosing, true);
CloseCore(WindowCloseReason.WindowClosing, true, false);
}

internal void CloseCore(WindowCloseReason reason, bool isProgrammatic)
internal void CloseCore(WindowCloseReason reason, bool isProgrammatic, bool ignoreCancel)
{
bool close = true;

Expand All @@ -493,7 +493,7 @@ internal void CloseCore(WindowCloseReason reason, bool isProgrammatic)
}
finally
{
if (close)
if (close || ignoreCancel)
{
CloseInternal();
}
Expand Down Expand Up @@ -1113,10 +1113,12 @@ protected sealed override Size ArrangeSetBounds(Size size)

private protected sealed override void HandleClosed()
{
RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
_shown = false;

base.HandleClosed();

RaiseEvent(new RoutedEventArgs(WindowClosedEvent));

Owner = null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,86 @@ public void Should_Exit_After_MainWindow_Closed()
}
}

[Fact]
public void OnMainWindowClose_Overrides_Secondary_Window_Cancellation()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
lifetime.SetupCore(Array.Empty<string>());

var hasExit = false;
var secondaryWindowClosingExecuted = false;
var secondaryWindowClosedExecuted = false;

lifetime.Exit += (_, _) => hasExit = true;

var mainWindow = new Window();
mainWindow.Show();

lifetime.MainWindow = mainWindow;

var window = new Window();
window.Closing += (_, args) =>
{
secondaryWindowClosingExecuted = true;
args.Cancel = true;
};
window.Closed += (_, _) =>
{
secondaryWindowClosedExecuted = true;
};
window.Show();

mainWindow.Close();

Assert.True(secondaryWindowClosingExecuted);
Assert.True(secondaryWindowClosedExecuted);
Assert.True(hasExit);
}
}

[Fact]
public void OnMainWindowClose_Overrides_Secondary_Window_Cancellation_From_TryShutdown()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
lifetime.SetupCore(Array.Empty<string>());

var hasExit = false;
var secondaryWindowClosingExecuted = false;
var secondaryWindowClosedExecuted = false;

lifetime.Exit += (_, _) => hasExit = true;

var mainWindow = new Window();
mainWindow.Show();

lifetime.MainWindow = mainWindow;

var window = new Window();
window.Closing += (_, args) =>
{
secondaryWindowClosingExecuted = true;
args.Cancel = true;
};
window.Closed += (_, _) =>
{
secondaryWindowClosedExecuted = true;
};
window.Show();

lifetime.TryShutdown();

Assert.True(secondaryWindowClosingExecuted);
Assert.True(secondaryWindowClosedExecuted);
Assert.True(hasExit);
}
}

[Fact]
public void Should_Exit_After_Last_Window_Closed()
{
Expand Down Expand Up @@ -401,6 +481,8 @@ public void Shutdown_NotCancellable_By_Preventing_Window_Close()
lifetime.SetupCore(Array.Empty<string>());

var hasExit = false;
var closingRaised = 0;
var closedRaised = 0;

lifetime.Exit += (_, _) => hasExit = true;

Expand All @@ -411,18 +493,21 @@ public void Shutdown_NotCancellable_By_Preventing_Window_Close()
var windowB = new Window();

windowB.Show();

var raised = 0;

windowA.Closing += (_, e) =>
{
e.Cancel = true;
++raised;
++closingRaised;
};
windowA.Closed += (_, e) =>
{
++closedRaised;
};

lifetime.Shutdown();

Assert.Equal(1, raised);
Assert.Equal(1, closingRaised);
Assert.Equal(1, closedRaised);
Assert.True(hasExit);
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/Avalonia.Controls.UnitTests/WindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public void IsVisible_Should_Be_False_After_Impl_Signals_Close()
var window = new Window();

window.Show();
Assert.True(window.IsVisible);

windowImpl.Object.Closed();

Assert.False(window.IsVisible);
Expand Down
Loading