Browse Source

Merge pull request #6174 from AvaloniaUI/feature/2736-applicationShouldTerminate

OSX: Handle applicationShouldTerminate
# Conflicts:
#	src/Avalonia.Controls/ApiCompatBaseline.txt
release/0.10.7
Dan Walmsley 5 years ago
parent
commit
7240127176
  1. 6
      native/Avalonia.Native/src/OSX/app.mm
  2. 13
      native/Avalonia.Native/src/OSX/window.mm
  3. 5
      src/Avalonia.Controls/ApiCompatBaseline.txt
  4. 29
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  5. 12
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  6. 16
      src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
  7. 13
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  8. 3
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  9. 1
      src/Avalonia.Native/avn.idl
  10. 30
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

6
native/Avalonia.Native/src/OSX/app.mm

@ -50,6 +50,12 @@ ComPtr<IAvnApplicationEvents> _events;
_events->FilesOpened(array);
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel;
}
@end
@interface AvnApplication : NSApplication

13
native/Avalonia.Native/src/OSX/window.mm

@ -1887,18 +1887,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
+(void)closeAll
{
NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]];
auto numWindows = [windows count];
for(int i = 0; i < numWindows; i++)
{
auto window = (AvnWindow*)[windows objectAtIndex:i];
if([window parentWindow] == nullptr) // Avalonia will handle the child windows.
{
[window performClose:nil];
}
}
[[NSApplication sharedApplication] terminate:self];
}
- (void)performClose:(id)sender

5
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -1,10 +1,13 @@
Compat issues with assembly Avalonia.Controls:
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<System.ComponentModel.CancelEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 7
Total Issues: 11

29
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using Avalonia.Controls;
@ -42,9 +43,13 @@ namespace Avalonia.Controls.ApplicationLifetimes
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
_activeLifetime = this;
}
/// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<CancelEventArgs> ShutdownRequested;
/// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
@ -111,6 +116,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args);
}
var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
if (lifetimeEvents != null)
lifetimeEvents.ShutdownRequested += OnShutdownRequested;
_cts = new CancellationTokenSource();
MainWindow?.Show();
Dispatcher.UIThread.MainLoop(_cts.Token);
@ -123,6 +133,23 @@ namespace Avalonia.Controls.ApplicationLifetimes
if (_activeLifetime == this)
_activeLifetime = null;
}
private void OnShutdownRequested(object sender, CancelEventArgs e)
{
ShutdownRequested?.Invoke(this, e);
if (e.Cancel)
return;
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in Windows)
if (w.Owner is null)
w.Close();
if (Windows.Count > 0)
e.Cancel = true;
}
}
public class ClassicDesktopStyleApplicationLifetimeOptions

12
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<Window> Windows { get; }
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// </summary>
/// <remarks>
/// 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 <see cref="Window.Closing"/> event on each and allowing
/// each window to cancel the shutdown.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
}
}

16
src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs

@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
namespace Avalonia.Platform
{
public interface IPlatformLifetimeEventsImpl
{
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// </summary>
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
}
}

13
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -1,14 +1,25 @@
using System;
using System.ComponentModel;
using Avalonia.Native.Interop;
using Avalonia.Platform;
namespace Avalonia.Native
{
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
{
public event EventHandler<CancelEventArgs> ShutdownRequested;
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
}
public int TryShutdown()
{
if (ShutdownRequested is null) return 1;
var e = new CancelEventArgs();
ShutdownRequested(this, e);
return (!e.Cancel).AsComBool();
}
}
}

3
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -111,7 +111,8 @@ namespace Avalonia.Native
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory));
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform);
if (_options.UseGpu)
{

1
src/Avalonia.Native/avn.idl

@ -732,4 +732,5 @@ interface IAvnNativeControlHostTopLevelAttachment : IUnknown
interface IAvnApplicationEvents : IUnknown
{
void FilesOpened (IAvnStringArray* urls);
bool TryShutdown();
}

30
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<IPlatformLifetimeEventsImpl>();
AvaloniaLocator.CurrentMutable.Bind<IPlatformLifetimeEventsImpl>().ToConstant(lifetimeEvents.Object);
lifetime.Start(Array.Empty<string>());
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);
}
}
}
}

Loading…
Cancel
Save