diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index c59458311c..757db96799 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -83,12 +83,12 @@ namespace Avalonia.Controls.ApplicationLifetimes public void Shutdown(int exitCode = 0) { - DoShutdown(new ShutdownRequestedEventArgs(), true, exitCode); + DoShutdown(new ShutdownRequestedEventArgs(), true, true, exitCode); } public bool TryShutdown(int exitCode = 0) { - return DoShutdown(new ShutdownRequestedEventArgs(), false, exitCode); + return DoShutdown(new ShutdownRequestedEventArgs(), true, false, exitCode); } public int Start(string[] args) @@ -134,7 +134,11 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private bool DoShutdown(ShutdownRequestedEventArgs e, bool force = false, int exitCode = 0) + private bool DoShutdown( + ShutdownRequestedEventArgs e, + bool isProgrammatic, + bool force = false, + int exitCode = 0) { if (!force) { @@ -159,7 +163,7 @@ namespace Avalonia.Controls.ApplicationLifetimes { if (w.Owner is null) { - w.Close(); + w.CloseCore(WindowCloseReason.ApplicationShutdown, isProgrammatic); } } @@ -183,7 +187,7 @@ namespace Avalonia.Controls.ApplicationLifetimes return true; } - private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e); + private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e, false); } public class ClassicDesktopStyleApplicationLifetimeOptions diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index af9392d440..8d9d8e0e7b 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -68,7 +68,7 @@ namespace Avalonia.Platform /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. /// - Func Closing { get; set; } + Func Closing { get; set; } /// /// Gets a value to indicate if the platform was able to extend client area to non-client area. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index b0911403cc..88f3b520ce 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -430,14 +430,14 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler? Closing; + public event EventHandler? Closing; /// /// Closes the window. /// public void Close() { - Close(false); + CloseCore(WindowCloseReason.WindowClosing, true); } /// @@ -453,16 +453,16 @@ namespace Avalonia.Controls public void Close(object dialogResult) { _dialogResult = dialogResult; - Close(false); + CloseCore(WindowCloseReason.WindowClosing, true); } - internal void Close(bool ignoreCancel) + internal void CloseCore(WindowCloseReason reason, bool isProgrammatic) { bool close = true; try { - if (!ignoreCancel && ShouldCancelClose()) + if (ShouldCancelClose(new WindowClosingEventArgs(reason, isProgrammatic))) { close = false; } @@ -480,9 +480,10 @@ namespace Avalonia.Controls /// Handles a closing notification from . /// true if closing is cancelled. Otherwise false. /// - protected virtual bool HandleClosing() + /// The reason the window is closing. + private protected virtual bool HandleClosing(WindowCloseReason reason) { - if (!ShouldCancelClose()) + if (!ShouldCancelClose(new WindowClosingEventArgs(reason, false))) { CloseInternal(); return false; @@ -510,20 +511,22 @@ namespace Avalonia.Controls _showingAsDialog = false; } - private bool ShouldCancelClose(CancelEventArgs? args = null) + private bool ShouldCancelClose(WindowClosingEventArgs args) { - if (args is null) - { - args = new CancelEventArgs(); - } - bool canClose = true; - foreach (var (child, _) in _children.ToArray()) + if (_children.Count > 0) { - if (child.ShouldCancelClose(args)) + var childArgs = args.CloseReason == WindowCloseReason.WindowClosing ? + new WindowClosingEventArgs(WindowCloseReason.OwnerWindowClosing, args.IsProgrammatic) : + args; + + foreach (var (child, _) in _children.ToArray()) { - canClose = false; + if (child.ShouldCancelClose(childArgs)) + { + canClose = false; + } } } @@ -1033,7 +1036,7 @@ namespace Avalonia.Controls /// overridden method must call on the base class if the /// event needs to be raised. /// - protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); + protected virtual void OnClosing(WindowClosingEventArgs e) => Closing?.Invoke(this, e); protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { diff --git a/src/Avalonia.Controls/WindowClosingEventArgs.cs b/src/Avalonia.Controls/WindowClosingEventArgs.cs new file mode 100644 index 0000000000..7cfae2d005 --- /dev/null +++ b/src/Avalonia.Controls/WindowClosingEventArgs.cs @@ -0,0 +1,57 @@ +using System.ComponentModel; + +namespace Avalonia.Controls +{ + /// + /// Specifies the reason that a window was closed. + /// + public enum WindowCloseReason + { + /// + /// The cause of the closure was not provided by the underlying platform. + /// + Undefined, + + /// + /// The window itself was requested to close. + /// + WindowClosing, + + /// + /// The window is closing due to a parent/owner window closing. + /// + OwnerWindowClosing, + + /// + /// The window is closing due to the application shutting down. + /// + ApplicationShutdown, + + /// + /// The window is closing due to the operating system shutting down. + /// + OSShutdown, + } + + /// + /// Provides data for the event. + /// + public class WindowClosingEventArgs : CancelEventArgs + { + internal WindowClosingEventArgs(WindowCloseReason reason, bool isProgrammatic) + { + CloseReason = reason; + IsProgrammatic = isProgrammatic; + } + + /// + /// Gets a value that indicates why the window is being closed. + /// + public WindowCloseReason CloseReason { get; } + + /// + /// Gets a value indicating whether the window is being closed programmatically. + /// + public bool IsProgrammatic { get; } + } +} diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 037c5e0c71..020e09526e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -42,7 +42,7 @@ namespace Avalonia.DesignerSupport.Remote public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public IPlatformHandle Handle { get; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 80a4c7d897..94679e8ade 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -32,7 +32,7 @@ namespace Avalonia.DesignerSupport.Remote public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public Action Closed { get; set; } public Action LostFocus { get; set; } public IMouseDevice MouseDevice { get; } = new MouseDevice(); diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 725fab1eaa..8eafce208b 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -175,7 +175,7 @@ namespace Avalonia.Headless } - public Func Closing { get; set; } + public Func Closing { get; set; } class FramebufferProxy : ILockedFramebuffer { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 160401c5fc..1775f86705 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -354,7 +354,7 @@ namespace Avalonia.X11 public Action ScalingChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public Action WindowStateChanged { get; set; } public Action TransparencyLevelChanged @@ -542,7 +542,7 @@ namespace Avalonia.X11 { if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW) { - if (Closing?.Invoke() != true) + if (Closing?.Invoke(WindowCloseReason.WindowClosing) != true) Dispose(); } else if (ev.ClientMessageEvent.ptr1 == _x11.Atoms._NET_WM_SYNC_REQUEST) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index fe7d881f11..03ffc24f83 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -69,7 +69,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_CLOSE: { - bool? preventClosing = Closing?.Invoke(); + bool? preventClosing = Closing?.Invoke(WindowCloseReason.WindowClosing); if (preventClosing == true) { return IntPtr.Zero; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9e11959101..7bb45d5363 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -191,7 +191,7 @@ namespace Avalonia.Win32 public Action Activated { get; set; } - public Func Closing { get; set; } + public Func Closing { get; set; } public Action Closed { get; set; } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 435d0d92ce..9dc0cb95bd 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -155,12 +155,16 @@ namespace Avalonia.Controls.UnitTests window.Closing += (sender, e) => { + Assert.Equal(WindowCloseReason.WindowClosing, e.CloseReason); + Assert.Equal(programaticClose, e.IsProgrammatic); count++; windowClosing = count; }; child.Closing += (sender, e) => { + Assert.Equal(WindowCloseReason.OwnerWindowClosing, e.CloseReason); + Assert.Equal(programaticClose, e.IsProgrammatic); count++; childClosing = count; }; @@ -186,7 +190,7 @@ namespace Avalonia.Controls.UnitTests } else { - var cancel = window.PlatformImpl.Closing(); + var cancel = window.PlatformImpl.Closing(WindowCloseReason.WindowClosing); Assert.Equal(false, cancel); } @@ -248,7 +252,7 @@ namespace Avalonia.Controls.UnitTests } else { - var cancel = window.PlatformImpl.Closing(); + var cancel = window.PlatformImpl.Closing(WindowCloseReason.WindowClosing); Assert.Equal(true, cancel); }