Skip to content

Commit 7f2cd07

Browse files
authored
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
1 parent be7bb76 commit 7f2cd07

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using System.Runtime.CompilerServices;
54
using System.Threading;
65
using Avalonia.Collections;
@@ -184,11 +183,12 @@ private bool DoShutdown(
184183
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
185184
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
186185
// owners.
187-
foreach (var w in Windows.ToArray())
186+
foreach (var w in new List<Window>(_windows))
188187
{
189188
if (w.Owner is null)
190189
{
191-
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic);
190+
var ignoreCancel = force || (ShutdownMode == ShutdownMode.OnMainWindowClose && w != MainWindow);
191+
w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic, ignoreCancel);
192192
}
193193
}
194194

src/Avalonia.Controls/Window.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ public PixelPoint Position
461461
/// </summary>
462462
public void Close()
463463
{
464-
CloseCore(WindowCloseReason.WindowClosing, true);
464+
CloseCore(WindowCloseReason.WindowClosing, true, false);
465465
}
466466

467467
/// <summary>
@@ -477,10 +477,10 @@ public void Close()
477477
public void Close(object? dialogResult)
478478
{
479479
_dialogResult = dialogResult;
480-
CloseCore(WindowCloseReason.WindowClosing, true);
480+
CloseCore(WindowCloseReason.WindowClosing, true, false);
481481
}
482482

483-
internal void CloseCore(WindowCloseReason reason, bool isProgrammatic)
483+
internal void CloseCore(WindowCloseReason reason, bool isProgrammatic, bool ignoreCancel)
484484
{
485485
bool close = true;
486486

@@ -493,7 +493,7 @@ internal void CloseCore(WindowCloseReason reason, bool isProgrammatic)
493493
}
494494
finally
495495
{
496-
if (close)
496+
if (close || ignoreCancel)
497497
{
498498
CloseInternal();
499499
}
@@ -1113,10 +1113,12 @@ protected sealed override Size ArrangeSetBounds(Size size)
11131113

11141114
private protected sealed override void HandleClosed()
11151115
{
1116-
RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
1116+
_shown = false;
11171117

11181118
base.HandleClosed();
11191119

1120+
RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
1121+
11201122
Owner = null;
11211123
}
11221124

tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,86 @@ public void Should_Exit_After_MainWindow_Closed()
126126
}
127127
}
128128

129+
[Fact]
130+
public void OnMainWindowClose_Overrides_Secondary_Window_Cancellation()
131+
{
132+
using (UnitTestApplication.Start(TestServices.StyledWindow))
133+
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
134+
{
135+
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
136+
lifetime.SetupCore(Array.Empty<string>());
137+
138+
var hasExit = false;
139+
var secondaryWindowClosingExecuted = false;
140+
var secondaryWindowClosedExecuted = false;
141+
142+
lifetime.Exit += (_, _) => hasExit = true;
143+
144+
var mainWindow = new Window();
145+
mainWindow.Show();
146+
147+
lifetime.MainWindow = mainWindow;
148+
149+
var window = new Window();
150+
window.Closing += (_, args) =>
151+
{
152+
secondaryWindowClosingExecuted = true;
153+
args.Cancel = true;
154+
};
155+
window.Closed += (_, _) =>
156+
{
157+
secondaryWindowClosedExecuted = true;
158+
};
159+
window.Show();
160+
161+
mainWindow.Close();
162+
163+
Assert.True(secondaryWindowClosingExecuted);
164+
Assert.True(secondaryWindowClosedExecuted);
165+
Assert.True(hasExit);
166+
}
167+
}
168+
169+
[Fact]
170+
public void OnMainWindowClose_Overrides_Secondary_Window_Cancellation_From_TryShutdown()
171+
{
172+
using (UnitTestApplication.Start(TestServices.StyledWindow))
173+
using(var lifetime = new ClassicDesktopStyleApplicationLifetime())
174+
{
175+
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
176+
lifetime.SetupCore(Array.Empty<string>());
177+
178+
var hasExit = false;
179+
var secondaryWindowClosingExecuted = false;
180+
var secondaryWindowClosedExecuted = false;
181+
182+
lifetime.Exit += (_, _) => hasExit = true;
183+
184+
var mainWindow = new Window();
185+
mainWindow.Show();
186+
187+
lifetime.MainWindow = mainWindow;
188+
189+
var window = new Window();
190+
window.Closing += (_, args) =>
191+
{
192+
secondaryWindowClosingExecuted = true;
193+
args.Cancel = true;
194+
};
195+
window.Closed += (_, _) =>
196+
{
197+
secondaryWindowClosedExecuted = true;
198+
};
199+
window.Show();
200+
201+
lifetime.TryShutdown();
202+
203+
Assert.True(secondaryWindowClosingExecuted);
204+
Assert.True(secondaryWindowClosedExecuted);
205+
Assert.True(hasExit);
206+
}
207+
}
208+
129209
[Fact]
130210
public void Should_Exit_After_Last_Window_Closed()
131211
{
@@ -401,6 +481,8 @@ public void Shutdown_NotCancellable_By_Preventing_Window_Close()
401481
lifetime.SetupCore(Array.Empty<string>());
402482

403483
var hasExit = false;
484+
var closingRaised = 0;
485+
var closedRaised = 0;
404486

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

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

413495
windowB.Show();
414-
415-
var raised = 0;
416496

417497
windowA.Closing += (_, e) =>
418498
{
419499
e.Cancel = true;
420-
++raised;
500+
++closingRaised;
501+
};
502+
windowA.Closed += (_, e) =>
503+
{
504+
++closedRaised;
421505
};
422506

423507
lifetime.Shutdown();
424508

425-
Assert.Equal(1, raised);
509+
Assert.Equal(1, closingRaised);
510+
Assert.Equal(1, closedRaised);
426511
Assert.True(hasExit);
427512
}
428513
}

tests/Avalonia.Controls.UnitTests/WindowTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ public void IsVisible_Should_Be_False_After_Impl_Signals_Close()
112112
var window = new Window();
113113

114114
window.Show();
115+
Assert.True(window.IsVisible);
116+
115117
windowImpl.Object.Closed();
116118

117119
Assert.False(window.IsVisible);

0 commit comments

Comments
 (0)