diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 65f1f0fff2..79780dbd0b 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -46,6 +46,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public event EventHandler Startup; + + /// + public event EventHandler ShutdownRequested; + /// public event EventHandler Exit; @@ -115,7 +119,7 @@ namespace Avalonia.Controls.ApplicationLifetimes var lifetimeEvents = AvaloniaLocator.Current.GetService(); if (lifetimeEvents != null) - lifetimeEvents.ShutdownRequested += ShutdownRequested; + lifetimeEvents.ShutdownRequested += OnShutdownRequested; _cts = new CancellationTokenSource(); MainWindow?.Show(); @@ -130,8 +134,10 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private void ShutdownRequested(object sender, CancelEventArgs e) + private void OnShutdownRequested(object sender, CancelEventArgs e) { + ShutdownRequested?.Invoke(this, e); + if (e.Cancel) return; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 212f0b8617..ecf8a0358f 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; namespace Avalonia.Controls.ApplicationLifetimes { @@ -34,5 +35,16 @@ namespace Avalonia.Controls.ApplicationLifetimes Window MainWindow { get; set; } IReadOnlyList Windows { get; } + + /// + /// Raised by the platform when a shutdown is requested. + /// + /// + /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event + /// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application + /// will try to close each non-owned open window, invoking the event on each and allowing + /// each window to cancel the shutdown. + /// + event EventHandler ShutdownRequested; } } diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 84f02aeda5..38713834c3 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; using Avalonia.Threading; @@ -209,6 +210,33 @@ namespace Avalonia.Controls.UnitTests Assert.Empty(lifetime.Windows); } } + + [Fact] + public void Should_Allow_Canceling_Shutdown_Via_ShutdownRequested_Event() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) + { + var lifetimeEvents = new Mock(); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(lifetimeEvents.Object); + lifetime.Start(Array.Empty()); + + var window = new Window(); + var raised = 0; + + window.Show(); + + lifetime.ShutdownRequested += (s, e) => + { + e.Cancel = true; + ++raised; + }; + + lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs()); + + Assert.Equal(1, raised); + Assert.Equal(new[] { window }, lifetime.Windows); + } + } } - }