diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 37637b1624..1f84574318 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -44,5 +44,11 @@ namespace Avalonia.Platform /// Enables or disables the taskbar icon /// void ShowTaskbarIcon(bool value); + + /// + /// 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; } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 9db7db365d..c66209d3c6 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -13,6 +13,7 @@ using Avalonia.Styling; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using System.ComponentModel; namespace Avalonia.Controls { @@ -129,6 +130,7 @@ namespace Avalonia.Controls public Window(IWindowImpl impl) : base(impl) { + impl.Closing = HandleClosing; _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); Screens = new Screens(PlatformImpl?.Screen); } @@ -230,20 +232,23 @@ namespace Avalonia.Controls /// Type IStyleable.StyleKey => typeof(Window); + /// + /// Fired before a window is closed. + /// + public event EventHandler Closing; + /// /// Closes the window. /// public void Close() { - s_windows.Remove(this); - PlatformImpl?.Dispose(); - IsVisible = false; + Close(false); } protected override void HandleApplicationExiting() { base.HandleApplicationExiting(); - Close(); + Close(true); } /// @@ -258,7 +263,35 @@ namespace Avalonia.Controls public void Close(object dialogResult) { _dialogResult = dialogResult; - Close(); + Close(false); + } + + internal void Close(bool ignoreCancel) + { + var cancelClosing = false; + try + { + cancelClosing = HandleClosing(); + } + finally + { + if (ignoreCancel || !cancelClosing) + { + s_windows.Remove(this); + PlatformImpl?.Dispose(); + IsVisible = false; + } + } + } + + /// + /// Handles a closing notification from . + /// + protected virtual bool HandleClosing() + { + var args = new CancelEventArgs(); + Closing?.Invoke(this, args); + return args.Cancel; } /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 3c7ef86d5d..fc9541abb7 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -39,6 +39,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 IPlatformHandle Handle { get; } public WindowState WindowState { get; set; } public Size MaxClientSize { get; } = new Size(4096, 4096); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 2ed434a2dc..560425286e 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -26,6 +26,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 Action Closed { get; set; } public IMouseDevice MouseDevice { get; } = new MouseDevice(); public Point Position { get; set; } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 5aac4466b2..a42c8a19b9 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -54,6 +54,7 @@ namespace Avalonia.Gtk3 ConnectEvent("key-press-event", OnKeyEvent); ConnectEvent("key-release-event", OnKeyEvent); ConnectEvent("leave-notify-event", OnLeaveNotifyEvent); + ConnectEvent("delete-event", OnClosingEvent); Connect("destroy", OnDestroy); Native.GtkWidgetRealize(gtkWidget); GdkWindowHandle = this.Handle.Handle; @@ -125,6 +126,12 @@ namespace Avalonia.Gtk3 return rv; } + private unsafe bool OnClosingEvent(IntPtr w, IntPtr ev, IntPtr userdata) + { + bool? preventClosing = Closing?.Invoke(); + return preventClosing ?? false; + } + private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata) { var evnt = (GdkEventButton*)ev; @@ -343,6 +350,7 @@ namespace Avalonia.Gtk3 string IPlatformHandle.HandleDescriptor => "HWND"; public Action Activated { get; set; } + public Func Closing { get; set; } public Action Closed { get; set; } public Action Deactivated { get; set; } public Action Input { get; set; } diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index 9ce1756aae..e5ba285f4f 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -4,6 +4,7 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using MonoMac.AppKit; using MonoMac.CoreGraphics; +using MonoMac.Foundation; using MonoMac.ObjCRuntime; namespace Avalonia.MonoMac @@ -69,6 +70,12 @@ namespace Avalonia.MonoMac _impl.PositionChanged?.Invoke(_impl.Position); } + public override bool WindowShouldClose(NSObject sender) + { + bool? preventClose = _impl.Closing?.Invoke(); + return preventClose != true; + } + public override void WillClose(global::MonoMac.Foundation.NSNotification notification) { _impl.Window.Dispose(); @@ -107,6 +114,7 @@ namespace Avalonia.MonoMac public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } + public Func Closing { get; set; } public override Size ClientSize => Window.ContentRectFor(Window.Frame).Size.ToAvaloniaSize(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3ba926b42a..a67362d59f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -56,6 +56,8 @@ namespace Avalonia.Win32 public Action Activated { get; set; } + public Func Closing { get; set; } + public Action Closed { get; set; } public Action Deactivated { get; set; } @@ -431,6 +433,14 @@ namespace Avalonia.Win32 return IntPtr.Zero; + case UnmanagedMethods.WindowsMessage.WM_CLOSE: + bool? preventClosing = Closing?.Invoke(); + if (preventClosing == true) + { + return IntPtr.Zero; + } + break; + case UnmanagedMethods.WindowsMessage.WM_DESTROY: //Window doesn't exist anymore _hwnd = IntPtr.Zero;