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; |
|||
|
|||
namespace Avalonia.Controls |
|||
namespace Avalonia.Controls.ApplicationLifetimes |
|||
{ |
|||
/// <summary>
|
|||
/// Sends events about the application lifecycle.
|
|||
/// </summary>
|
|||
public interface IApplicationLifecycle |
|||
public interface IControlledApplicationLifetime : IApplicationLifetime |
|||
{ |
|||
/// <summary>
|
|||
/// Sent when the application is starting up.
|
|||
/// </summary>
|
|||
event EventHandler<StartupEventArgs> Startup; |
|||
event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; |
|||
|
|||
/// <summary>
|
|||
/// Sent when the application is exiting.
|
|||
/// </summary>
|
|||
event EventHandler<ExitEventArgs> Exit; |
|||
|
|||
event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; |
|||
|
|||
/// <summary>
|
|||
/// Shuts down the application and sets the exit code that is returned to the operating system when the application exits.
|
|||
/// </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