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);
}
}