diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index a26b7d7405..419064b051 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -148,13 +148,20 @@ namespace Avalonia.Controls public delegate void AppMainDelegate(Application app, string[] args); + public void Start() + { + Setup(); + BeforeStartCallback(Self); + Instance.Run(); + } + public void Start(AppMainDelegate main, string[] args) { Setup(); BeforeStartCallback(Self); main(Instance, args); } - + /// /// Sets up the platform-specific services for the application, but does not run it. /// @@ -220,13 +227,13 @@ namespace Avalonia.Controls /// /// Sets the shutdown mode of the application. /// - /// The shutdown mode. + /// The shutdown mode. /// - public TAppBuilder SetExitMode(ExitMode exitMode) + public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode) { - Instance.ExitMode = exitMode; + Instance.ShutdownMode = shutdownMode; return Self; - } + } protected virtual bool CheckSetup => true; diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 1d4e4cbeaa..bbea3693cc 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -43,8 +43,8 @@ namespace Avalonia private readonly Styler _styler = new Styler(); private Styles _styles; private IResourceDictionary _resources; - private CancellationTokenSource _mainLoopCancellationTokenSource; + private int _exitCode; /// /// Initializes a new instance of the class. @@ -52,10 +52,14 @@ namespace Avalonia public Application() { Windows = new WindowCollection(this); - - OnExit += OnExiting; } + /// + public event EventHandler Startup; + + /// + public event EventHandler Exit; + /// public event EventHandler ResourcesChanged; @@ -164,14 +168,14 @@ namespace Avalonia IResourceNode IResourceNode.ResourceParent => null; /// - /// Gets or sets the . This property indicates whether the application exits explicitly or implicitly. - /// If is set to OnExplicitExit the application is only closes if Exit is called. + /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. + /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. /// The default is OnLastWindowClose /// /// /// The shutdown mode. /// - public ExitMode ExitMode { get; set; } + public ShutdownMode ShutdownMode { get; set; } /// /// Gets or sets the main window of the application. @@ -190,108 +194,171 @@ namespace Avalonia public WindowCollection Windows { get; } /// - /// Gets or sets a value indicating whether this instance is existing. + /// Gets or sets a value indicating whether this instance is shutting down. /// /// - /// true if this instance is existing; otherwise, false. + /// true if this instance is shutting down; otherwise, false. /// - internal bool IsExiting { get; set; } + internal bool IsShuttingDown { get; private set; } /// /// Initializes the application by loading XAML etc. /// - public virtual void Initialize() + public virtual void Initialize() { } + + /// + /// Runs the application's main loop. + /// + /// + /// This will return when the condition is met + /// or was called. + /// + /// The application's exit code that is returned to the operating system on termination. + public int Run() { + return Run(new CancellationTokenSource()); } /// - /// Runs the application's main loop until the is closed. + /// Runs the application's main loop. /// - /// The closable to track - public void Run(ICloseable closable) + /// + /// This will return when the condition is met + /// or was called. + /// This also returns when is closed. + /// + /// The closable to track. + /// The application's exit code that is returned to the operating system on termination. + public int Run(ICloseable closable) { - if (_mainLoopCancellationTokenSource != null) + closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); + + return Run(new CancellationTokenSource()); + } + + /// + /// Runs the application's main loop. + /// + /// + /// This will return when the condition is met + /// or was called. + /// + /// The window that is used as + /// when the isn't already set. + /// The application's exit code that is returned to the operating system on termination. + public int Run(Window mainWindow) + { + if (mainWindow == null) { - throw new Exception("Run should only called once"); + throw new ArgumentNullException(nameof(mainWindow)); } - closable.Closed += (s, e) => Exit(); - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); + if (MainWindow == null) + { + Dispatcher.UIThread.Post(() => + { + if (!mainWindow.IsVisible) + { + mainWindow.Show(); + } - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); + MainWindow = mainWindow; + }); + } - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } + return Run(new CancellationTokenSource()); } - /// - /// Runs the application's main loop until some condition occurs that is specified by ExitMode. + /// Runs the application's main loop. /// - /// The main window - public void Run(Window mainWindow) + /// + /// This will return when the condition is met + /// or was called. + /// This also returns when the is canceled. + /// + /// The application's exit code that is returned to the operating system on termination. + /// The token to track. + public int Run(CancellationToken token) { - if (_mainLoopCancellationTokenSource != null) + return Run(CancellationTokenSource.CreateLinkedTokenSource(token)); + } + + private int Run(CancellationTokenSource tokenSource) + { + if (IsShuttingDown) { - throw new Exception("Run should only called once"); + throw new InvalidOperationException("Application is shutting down."); } - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - - if (MainWindow == null) + if (_mainLoopCancellationTokenSource != null) { - if (mainWindow == null) - { - throw new ArgumentNullException(nameof(mainWindow)); - } + throw new InvalidOperationException("Application is already running."); + } - if (!mainWindow.IsVisible) - { - mainWindow.Show(); - } + _mainLoopCancellationTokenSource = tokenSource; - MainWindow = mainWindow; - } + Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) + if (!IsShuttingDown) { - OnExit?.Invoke(this, EventArgs.Empty); + Shutdown(_exitCode); } + + return _exitCode; } /// - /// Runs the application's main loop until the is canceled. + /// Raises the event. /// - /// The token to track - public void Run(CancellationToken token) + /// A that contains the event data. + protected virtual void OnStartup(StartupEventArgs e) { - Dispatcher.UIThread.MainLoop(token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } + Startup?.Invoke(this, e); } /// - /// Exits the application + /// Raises the event. /// - public void Exit() + /// A that contains the event data. + protected virtual void OnExit(ExitEventArgs e) { - IsExiting = true; + Exit?.Invoke(this, e); + } + + /// + public void Shutdown(int exitCode = 0) + { + if (IsShuttingDown) + { + throw new InvalidOperationException("Application is already shutting down."); + } + + _exitCode = exitCode; + + IsShuttingDown = true; Windows.Clear(); - OnExit?.Invoke(this, EventArgs.Empty); + try + { + var e = new ExitEventArgs { ApplicationExitCode = _exitCode }; + + OnExit(e); + + _exitCode = e.ApplicationExitCode; + } + finally + { + _mainLoopCancellationTokenSource?.Cancel(); + + _mainLoopCancellationTokenSource = null; + + IsShuttingDown = false; - _mainLoopCancellationTokenSource?.Cancel(); + Environment.ExitCode = _exitCode; + } } /// @@ -302,20 +369,6 @@ namespace Avalonia Styles.TryGetResource(key, out value); } - /// - /// Sent when the application is exiting. - /// - public event EventHandler OnExit; - - /// - /// Called when the application is exiting. - /// - /// - /// - protected virtual void OnExiting(object sender, EventArgs e) - { - } - /// /// Register's the services needed by Avalonia. /// diff --git a/src/Avalonia.Controls/ExitEventArgs.cs b/src/Avalonia.Controls/ExitEventArgs.cs new file mode 100644 index 0000000000..c99f7fe047 --- /dev/null +++ b/src/Avalonia.Controls/ExitEventArgs.cs @@ -0,0 +1,18 @@ +// 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; + +namespace Avalonia.Controls +{ + /// + /// Contains the arguments for the event. + /// + public class ExitEventArgs : EventArgs + { + /// + /// Gets or sets the exit code that an application returns to the operating system when the application exits. + /// + public int ApplicationExitCode { get; set; } + } +} diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index 51f554c078..a3c6599b20 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -7,14 +7,20 @@ namespace Avalonia.Controls /// public interface IApplicationLifecycle { + /// + /// Sent when the application is starting up. + /// + event EventHandler Startup; + /// /// Sent when the application is exiting. /// - event EventHandler OnExit; + event EventHandler Exit; /// - /// Exits the application. + /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. /// - void Exit(); + /// An integer exit code for an application. The default exit code is 0. + void Shutdown(int exitCode = 0); } } diff --git a/src/Avalonia.Controls/ExitMode.cs b/src/Avalonia.Controls/ShutdownMode.cs similarity index 54% rename from src/Avalonia.Controls/ExitMode.cs rename to src/Avalonia.Controls/ShutdownMode.cs index b73fe4a963..46e27ff4e1 100644 --- a/src/Avalonia.Controls/ExitMode.cs +++ b/src/Avalonia.Controls/ShutdownMode.cs @@ -1,26 +1,26 @@ // 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. -namespace Avalonia +namespace Avalonia.Controls { /// - /// Enum for ExitMode + /// Describes the possible values for . /// - public enum ExitMode + public enum ShutdownMode { /// - /// Indicates an implicit call to Application.Exit when the last window closes. + /// Indicates an implicit call to Application.Shutdown when the last window closes. /// OnLastWindowClose, /// - /// Indicates an implicit call to Application.Exit when the main window closes. + /// Indicates an implicit call to Application.Shutdown when the main window closes. /// OnMainWindowClose, /// - /// Indicates that the application only exits on an explicit call to Application.Exit. + /// Indicates that the application only exits on an explicit call to Application.Shutdown. /// - OnExplicitExit + OnExplicitShutdown } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs new file mode 100644 index 0000000000..0e12f5c01a --- /dev/null +++ b/src/Avalonia.Controls/StartupEventArgs.cs @@ -0,0 +1,36 @@ +// 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 +{ + /// + /// Contains the arguments for the event. + /// + public class StartupEventArgs : EventArgs + { + private string[] _args; + + /// + /// Gets the command line arguments that were passed to the application. + /// + public IReadOnlyList 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]; + } + } + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index b08d55ff18..21bd0e4e57 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -127,7 +127,7 @@ namespace Avalonia.Controls if (_applicationLifecycle != null) { - _applicationLifecycle.OnExit += OnApplicationExiting; + _applicationLifecycle.Exit += OnApplicationExiting; } if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources) @@ -281,7 +281,7 @@ namespace Avalonia.Controls Closed?.Invoke(this, EventArgs.Empty); Renderer?.Dispose(); Renderer = null; - _applicationLifecycle.OnExit -= OnApplicationExiting; + _applicationLifecycle.Exit -= OnApplicationExiting; } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index e40e114769..01c9a3a110 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -250,7 +250,7 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler Closing; + public event EventHandler Closing; private static void AddWindow(Window window) { @@ -440,7 +440,7 @@ namespace Avalonia.Controls /// public Task ShowDialog(IWindowImpl owner) { - if(owner == null) + if (owner == null) throw new ArgumentNullException(nameof(owner)); if (IsVisible) diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs index df79c3e3c8..328bb9f147 100644 --- a/src/Avalonia.Controls/WindowCollection.cs +++ b/src/Avalonia.Controls/WindowCollection.cs @@ -107,24 +107,24 @@ namespace Avalonia return; } - if (_application.IsExiting) + if (_application.IsShuttingDown) { return; } - switch (_application.ExitMode) + switch (_application.ShutdownMode) { - case ExitMode.OnLastWindowClose: + case ShutdownMode.OnLastWindowClose: if (Count == 0) { - _application.Exit(); + _application.Shutdown(); } break; - case ExitMode.OnMainWindowClose: + case ShutdownMode.OnMainWindowClose: if (window == _application.MainWindow) { - _application.Exit(); + _application.Shutdown(); } break; diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index df14c808db..b7d752dfe3 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -15,7 +16,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnMainWindowClose; + Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; + + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; var mainWindow = new Window(); @@ -29,7 +34,7 @@ namespace Avalonia.Controls.UnitTests mainWindow.Close(); - Assert.True(Application.Current.IsExiting); + Assert.True(hasExit); } } @@ -38,7 +43,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnLastWindowClose; + Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose; + + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; var windowA = new Window(); @@ -50,11 +59,11 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(hasExit); windowB.Close(); - Assert.True(Application.Current.IsExiting); + Assert.True(hasExit); } } @@ -63,7 +72,11 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnExplicitExit; + Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; + + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; var windowA = new Window(); @@ -75,15 +88,15 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(hasExit); windowB.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(hasExit); - Application.Current.Exit(); + Application.Current.Shutdown(); - Assert.True(Application.Current.IsExiting); + Assert.True(hasExit); } } @@ -99,7 +112,7 @@ namespace Avalonia.Controls.UnitTests window.Show(); } - Application.Current.Exit(); + Application.Current.Shutdown(); Assert.Empty(Application.Current.Windows); } @@ -129,5 +142,18 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } + + [Fact] + public void Should_Set_ExitCode_After_Shutdown() + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + Application.Current.Shutdown(1337); + + var exitCode = Application.Current.Run(); + + Assert.Equal(1337, exitCode); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 014bf458ea..aa99d31cff 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -215,7 +215,7 @@ namespace Avalonia.Controls.UnitTests var impl = new Mock(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); - UnitTestApplication.Current.Exit(); + UnitTestApplication.Current.Shutdown(); Assert.True(target.IsClosed); } }