Просмотр исходного кода

Added WindowClosingEventArgs.

Fixes #9524.
Steven Kirk 2 лет назад
Родитель
Сommit
fdc4a4c497

+ 9 - 5
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

+ 1 - 1
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.
         /// </summary>
-        Func<bool> Closing { get; set; }
+        Func<WindowCloseReason, bool> Closing { get; set; }
 
         /// <summary>
         /// Gets a value to indicate if the platform was able to extend client area to non-client area.

+ 20 - 17
src/Avalonia.Controls/Window.cs

@@ -430,14 +430,14 @@ namespace Avalonia.Controls
         /// <summary>
         /// Fired before a window is closed.
         /// </summary>
-        public event EventHandler<CancelEventArgs>? Closing;
+        public event EventHandler<WindowClosingEventArgs>? Closing;
 
         /// <summary>
         /// Closes the window.
         /// </summary>
         public void Close()
         {
-            Close(false);
+            CloseCore(WindowCloseReason.WindowClosing, true);
         }
 
         /// <summary>
@@ -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 <see cref="IWindowImpl.Closing"/>.
         /// <returns>true if closing is cancelled. Otherwise false.</returns>
         /// </summary>
-        protected virtual bool HandleClosing()
+        /// <param name="reason">The reason the window is closing.</param>
+        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 <see cref="OnClosing"/> on the base class if the
         /// <see cref="Closing"/> event needs to be raised.
         /// </remarks>
-        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)
         {

+ 57 - 0
src/Avalonia.Controls/WindowClosingEventArgs.cs

@@ -0,0 +1,57 @@
+using System.ComponentModel;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Specifies the reason that a window was closed.
+    /// </summary>
+    public enum WindowCloseReason
+    {
+        /// <summary>
+        /// The cause of the closure was not provided by the underlying platform.
+        /// </summary>
+        Undefined,
+
+        /// <summary>
+        /// The window itself was requested to close.
+        /// </summary>
+        WindowClosing,
+
+        /// <summary>
+        /// The window is closing due to a parent/owner window closing.
+        /// </summary>
+        OwnerWindowClosing,
+
+        /// <summary>
+        /// The window is closing due to the application shutting down.
+        /// </summary>
+        ApplicationShutdown,
+
+        /// <summary>
+        /// The window is closing due to the operating system shutting down.
+        /// </summary>
+        OSShutdown,
+    }
+
+    /// <summary>
+    /// Provides data for the <see cref="Window.Closing"/> event.
+    /// </summary>
+    public class WindowClosingEventArgs : CancelEventArgs
+    {
+        internal WindowClosingEventArgs(WindowCloseReason reason, bool isProgrammatic)
+        {
+            CloseReason = reason;
+            IsProgrammatic = isProgrammatic;
+        }
+
+        /// <summary>
+        /// Gets a value that indicates why the window is being closed.
+        /// </summary>
+        public WindowCloseReason CloseReason { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether the window is being closed programmatically.
+        /// </summary>
+        public bool IsProgrammatic { get; }
+    }
+}

+ 1 - 1
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@@ -42,7 +42,7 @@ namespace Avalonia.DesignerSupport.Remote
         public Action<PixelPoint> PositionChanged { get; set; }
         public Action Deactivated { get; set; }
         public Action Activated { get; set; }
-        public Func<bool> Closing { get; set; }
+        public Func<WindowCloseReason, bool> Closing { get; set; }
         public IPlatformHandle Handle { get; }
         public WindowState WindowState { get; set; }
         public Action<WindowState> WindowStateChanged { get; set; }

+ 1 - 1
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -32,7 +32,7 @@ namespace Avalonia.DesignerSupport.Remote
         public Action<Rect> Paint { get; set; }
         public Action<Size, PlatformResizeReason> Resized { get; set; }
         public Action<double> ScalingChanged { get; set; }
-        public Func<bool> Closing { get; set; }
+        public Func<WindowCloseReason, bool> Closing { get; set; }
         public Action Closed { get; set; }
         public Action LostFocus { get; set; }
         public IMouseDevice MouseDevice { get; } = new MouseDevice();

+ 1 - 1
src/Avalonia.Headless/HeadlessWindowImpl.cs

@@ -175,7 +175,7 @@ namespace Avalonia.Headless
 
         }
 
-        public Func<bool> Closing { get; set; }
+        public Func<WindowCloseReason, bool> Closing { get; set; }
 
         class FramebufferProxy : ILockedFramebuffer
         {

+ 2 - 2
src/Avalonia.X11/X11Window.cs

@@ -354,7 +354,7 @@ namespace Avalonia.X11
         public Action<double> ScalingChanged { get; set; }
         public Action Deactivated { get; set; }
         public Action Activated { get; set; }
-        public Func<bool> Closing { get; set; }
+        public Func<WindowCloseReason, bool> Closing { get; set; }
         public Action<WindowState> WindowStateChanged { get; set; }
 
         public Action<WindowTransparencyLevel> 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)

+ 1 - 1
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;

+ 1 - 1
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -191,7 +191,7 @@ namespace Avalonia.Win32
 
         public Action Activated { get; set; }
 
-        public Func<bool> Closing { get; set; }
+        public Func<WindowCloseReason, bool> Closing { get; set; }
 
         public Action Closed { get; set; }
 

+ 6 - 2
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);
                 }