27 changed files with 493 additions and 510 deletions
@ -0,0 +1,99 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
|
||||
|
namespace Avalonia.Controls.ApplicationLifetimes |
||||
|
{ |
||||
|
public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime |
||||
|
{ |
||||
|
private readonly Application _app; |
||||
|
private int _exitCode; |
||||
|
private CancellationTokenSource _cts; |
||||
|
private bool _isShuttingDown; |
||||
|
|
||||
|
public ClassicDesktopStyleApplicationLifetime(Application app) |
||||
|
{ |
||||
|
_app = app; |
||||
|
app.Windows.OnWindowClosed += HandleWindowClosed; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; |
||||
|
/// <inheritdoc/>
|
||||
|
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ShutdownMode ShutdownMode { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Window MainWindow { get; set; } |
||||
|
|
||||
|
private void HandleWindowClosed(Window window) |
||||
|
{ |
||||
|
if (window == null) |
||||
|
return; |
||||
|
|
||||
|
if (_isShuttingDown) |
||||
|
return; |
||||
|
|
||||
|
if (ShutdownMode == ShutdownMode.OnLastWindowClose && _app.Windows.Count == 0) |
||||
|
Shutdown(); |
||||
|
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) |
||||
|
Shutdown(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public void Shutdown(int exitCode = 0) |
||||
|
{ |
||||
|
if (_isShuttingDown) |
||||
|
throw new InvalidOperationException("Application is already shutting down."); |
||||
|
|
||||
|
_exitCode = exitCode; |
||||
|
_isShuttingDown = true; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
_app.Windows.CloseAll(); |
||||
|
var e = new ControlledApplicationLifetimeExitEventArgs(exitCode); |
||||
|
Exit?.Invoke(this, e); |
||||
|
_exitCode = e.ApplicationExitCode; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
_cts?.Cancel(); |
||||
|
_cts = null; |
||||
|
_isShuttingDown = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public int Start(string[] args) |
||||
|
{ |
||||
|
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); |
||||
|
_cts = new CancellationTokenSource(); |
||||
|
MainWindow?.Show(); |
||||
|
_app.Run(_cts.Token); |
||||
|
Environment.ExitCode = _exitCode; |
||||
|
return _exitCode; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
public static class ClassicDesktopStyleApplicationLifetimeExtensions |
||||
|
{ |
||||
|
public static int StartWithClassicDesktopLifetime<T>( |
||||
|
this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) |
||||
|
where T : AppBuilderBase<T>, new() |
||||
|
{ |
||||
|
var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; |
||||
|
builder.Instance.ApplicationLifetime = lifetime; |
||||
|
builder.SetupWithoutStarting(); |
||||
|
return lifetime.Start(args); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Avalonia.Controls.ApplicationLifetimes |
||||
|
{ |
||||
|
public interface IApplicationLifetime |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls.ApplicationLifetimes |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Controls application lifetime in classic desktop style
|
||||
|
/// </summary>
|
||||
|
public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly.
|
||||
|
/// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called.
|
||||
|
/// The default is OnLastWindowClose
|
||||
|
/// </summary>
|
||||
|
/// <value>
|
||||
|
/// The shutdown mode.
|
||||
|
/// </value>
|
||||
|
ShutdownMode ShutdownMode { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the main window of the application.
|
||||
|
/// </summary>
|
||||
|
/// <value>
|
||||
|
/// The main window.
|
||||
|
/// </value>
|
||||
|
Window MainWindow { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,22 +1,19 @@ |
|||||
using System; |
using System; |
||||
|
|
||||
namespace Avalonia.Controls |
namespace Avalonia.Controls.ApplicationLifetimes |
||||
{ |
{ |
||||
/// <summary>
|
public interface IControlledApplicationLifetime : IApplicationLifetime |
||||
/// Sends events about the application lifecycle.
|
|
||||
/// </summary>
|
|
||||
public interface IApplicationLifecycle |
|
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Sent when the application is starting up.
|
/// Sent when the application is starting up.
|
||||
/// </summary>
|
/// </summary>
|
||||
event EventHandler<StartupEventArgs> Startup; |
event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Sent when the application is exiting.
|
/// Sent when the application is exiting.
|
||||
/// </summary>
|
/// </summary>
|
||||
event EventHandler<ExitEventArgs> Exit; |
event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Shuts down the application and sets the exit code that is returned to the operating system when the application exits.
|
/// Shuts down the application and sets the exit code that is returned to the operating system when the application exits.
|
||||
/// </summary>
|
/// </summary>
|
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Avalonia.Controls.ApplicationLifetimes |
||||
|
{ |
||||
|
public interface ISingleViewLifetime : IApplicationLifetime |
||||
|
{ |
||||
|
Control MainView { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Avalonia.Controls.ApplicationLifetimes |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Startup"/> event.
|
||||
|
/// </summary>
|
||||
|
public class ControlledApplicationLifetimeStartupEventArgs : EventArgs |
||||
|
{ |
||||
|
public ControlledApplicationLifetimeStartupEventArgs(IEnumerable<string> args) |
||||
|
{ |
||||
|
Args = args?.ToArray() ?? Array.Empty<string>(); |
||||
|
} |
||||
|
|
||||
|
public string[] Args { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
public static class DesktopApplicationExtensions |
||||
|
{ |
||||
|
[Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] |
||||
|
public static void Run(this Application app) => throw new NotSupportedException(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// On desktop-style platforms runs the application's main loop until closable is closed
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details
|
||||
|
/// </remarks>
|
||||
|
public static void Run(this Application app, ICloseable closable) |
||||
|
{ |
||||
|
var cts = new CancellationTokenSource(); |
||||
|
closable.Closed += (s, e) => cts.Cancel(); |
||||
|
|
||||
|
app.Run(cts.Token); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// On desktop-style platforms runs the application's main loop until main window is closed
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details
|
||||
|
/// </remarks>
|
||||
|
public static void Run(this Application app, Window mainWindow) |
||||
|
{ |
||||
|
if (mainWindow == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(mainWindow)); |
||||
|
} |
||||
|
var cts = new CancellationTokenSource(); |
||||
|
mainWindow.Closed += (_, __) => cts.Cancel(); |
||||
|
if (!mainWindow.IsVisible) |
||||
|
{ |
||||
|
mainWindow.Show(); |
||||
|
} |
||||
|
app.Run(cts.Token); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// On desktop-style platforms runs the application's main loop with custom CancellationToken
|
||||
|
/// without setting a lifetime.
|
||||
|
/// </summary>
|
||||
|
/// <param name="token">The token to track.</param>
|
||||
|
public static void Run(this Application app, CancellationToken token) |
||||
|
{ |
||||
|
Dispatcher.UIThread.MainLoop(token); |
||||
|
} |
||||
|
|
||||
|
public static void RunWithMainWindow<TWindow>(this Application app) |
||||
|
where TWindow : Avalonia.Controls.Window, new() |
||||
|
{ |
||||
|
var window = new TWindow(); |
||||
|
window.Show(); |
||||
|
app.Run(window); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,36 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
|
|
||||
namespace Avalonia.Controls |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Contains the arguments for the <see cref="IApplicationLifecycle.Startup"/> event.
|
|
||||
/// </summary>
|
|
||||
public class StartupEventArgs : EventArgs |
|
||||
{ |
|
||||
private string[] _args; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the command line arguments that were passed to the application.
|
|
||||
/// </summary>
|
|
||||
public IReadOnlyList<string> Args => _args ?? (_args = GetArgs()); |
|
||||
|
|
||||
private static string[] GetArgs() |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var args = Environment.GetCommandLineArgs(); |
|
||||
|
|
||||
return args.Length > 1 ? args.Skip(1).ToArray() : new string[0]; |
|
||||
} |
|
||||
catch (NotSupportedException) |
|
||||
{ |
|
||||
return new string[0]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,139 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
using Avalonia.Threading; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
|
||||
|
public class DesktopStyleApplicationLifetimeTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Set_ExitCode_After_Shutdown() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) |
||||
|
{ |
||||
|
var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); |
||||
|
Dispatcher.UIThread.InvokeAsync(() => lifetime.Shutdown(1337)); |
||||
|
lifetime.Shutdown(1337); |
||||
|
|
||||
|
var exitCode = lifetime.Start(Array.Empty<string>()); |
||||
|
|
||||
|
Assert.Equal(1337, exitCode); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var windows = new List<Window> { new Window(), new Window(), new Window(), new Window() }; |
||||
|
|
||||
|
foreach (var window in windows) |
||||
|
{ |
||||
|
window.Show(); |
||||
|
} |
||||
|
new ClassicDesktopStyleApplicationLifetime(Application.Current).Shutdown(); |
||||
|
|
||||
|
Assert.Empty(Application.Current.Windows); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Only_Exit_On_Explicit_Exit() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); |
||||
|
lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown; |
||||
|
|
||||
|
var hasExit = false; |
||||
|
|
||||
|
lifetime.Exit += (s, e) => hasExit = true; |
||||
|
|
||||
|
var windowA = new Window(); |
||||
|
|
||||
|
windowA.Show(); |
||||
|
|
||||
|
var windowB = new Window(); |
||||
|
|
||||
|
windowB.Show(); |
||||
|
|
||||
|
windowA.Close(); |
||||
|
|
||||
|
Assert.False(hasExit); |
||||
|
|
||||
|
windowB.Close(); |
||||
|
|
||||
|
Assert.False(hasExit); |
||||
|
|
||||
|
lifetime.Shutdown(); |
||||
|
|
||||
|
Assert.True(hasExit); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Exit_After_MainWindow_Closed() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); |
||||
|
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose; |
||||
|
|
||||
|
var hasExit = false; |
||||
|
|
||||
|
lifetime.Exit += (s, e) => hasExit = true; |
||||
|
|
||||
|
var mainWindow = new Window(); |
||||
|
|
||||
|
mainWindow.Show(); |
||||
|
|
||||
|
lifetime.MainWindow = mainWindow; |
||||
|
|
||||
|
var window = new Window(); |
||||
|
|
||||
|
window.Show(); |
||||
|
|
||||
|
mainWindow.Close(); |
||||
|
|
||||
|
Assert.True(hasExit); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Exit_After_Last_Window_Closed() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current); |
||||
|
lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose; |
||||
|
|
||||
|
var hasExit = false; |
||||
|
|
||||
|
lifetime.Exit += (s, e) => hasExit = true; |
||||
|
|
||||
|
var windowA = new Window(); |
||||
|
|
||||
|
windowA.Show(); |
||||
|
|
||||
|
var windowB = new Window(); |
||||
|
|
||||
|
windowB.Show(); |
||||
|
|
||||
|
windowA.Close(); |
||||
|
|
||||
|
Assert.False(hasExit); |
||||
|
|
||||
|
windowB.Close(); |
||||
|
|
||||
|
Assert.True(hasExit); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue