Browse Source

Fix desktop lifetime non-mainwindow cancellation (#17059)

* Only check MainWindow visiblity in DoShutdown cancellation, when ShutdownMode == ShutdownMode.OnMainWindowClose

* Raise WindowClosedEvent event AFTER IsVisible/_shown properties were updated

* Add OnMainWindowClose cancellation tests

* Assert that Closing event was actually raised.

* Re-do fix by forcing window closing

* Forced .Shutdown() should also raise Window.Closed events, and not ignore them
pull/17259/head
Max Katz 2 years ago
committed by GitHub
parent
commit
7f2cd0723a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  2. 12
      src/Avalonia.Controls/Window.cs
  3. 93
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  4. 2
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

6
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia.Collections;
@ -184,11 +183,12 @@ namespace Avalonia.Controls.ApplicationLifetimes
// 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))
{
if (w.Owner is null)
{
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic);
var ignoreCancel = force || (ShutdownMode == ShutdownMode.OnMainWindowClose && w != MainWindow);
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic, ignoreCancel);
}
}

12
src/Avalonia.Controls/Window.cs

@ -461,7 +461,7 @@ namespace Avalonia.Controls
/// </summary>
public void Close()
{
CloseCore(WindowCloseReason.WindowClosing, true);
CloseCore(WindowCloseReason.WindowClosing, true, false);
}
/// <summary>
@ -477,10 +477,10 @@ namespace Avalonia.Controls
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;
@ -493,7 +493,7 @@ namespace Avalonia.Controls
}
finally
{
if (close)
if (close || ignoreCancel)
{
CloseInternal();
}
@ -1113,10 +1113,12 @@ namespace Avalonia.Controls
private protected sealed override void HandleClosed()
{
RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
_shown = false;
base.HandleClosed();
RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
Owner = null;
}

93
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@ -126,6 +126,86 @@ namespace Avalonia.Controls.UnitTests
}
}
[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()
{
@ -401,6 +481,8 @@ namespace Avalonia.Controls.UnitTests
lifetime.SetupCore(Array.Empty<string>());
var hasExit = false;
var closingRaised = 0;
var closedRaised = 0;
lifetime.Exit += (_, _) => hasExit = true;
@ -411,18 +493,21 @@ namespace Avalonia.Controls.UnitTests
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);
}
}

2
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -112,6 +112,8 @@ namespace Avalonia.Controls.UnitTests
var window = new Window();
window.Show();
Assert.True(window.IsVisible);
windowImpl.Object.Closed();
Assert.False(window.IsVisible);

Loading…
Cancel
Save