Browse Source

Added WindowClosingEventArgs.

Fixes #9524.
pull/9715/head
Steven Kirk 3 years ago
parent
commit
fdc4a4c497
  1. 14
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  2. 2
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  3. 37
      src/Avalonia.Controls/Window.cs
  4. 57
      src/Avalonia.Controls/WindowClosingEventArgs.cs
  5. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  6. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  7. 2
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  8. 4
      src/Avalonia.X11/X11Window.cs
  9. 2
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  10. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  11. 8
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

14
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

2
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.

37
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
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; }
}
}

2
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; }

2
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();

2
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
{

4
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)

2
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;

2
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; }

8
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);
}

Loading…
Cancel
Save