From 5557829950b81a892818c76b765f19caf919322d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 10 Apr 2019 18:57:35 +0200 Subject: [PATCH 01/12] Initial --- samples/ControlCatalog.Desktop/Program.cs | 8 +- samples/ControlCatalog.NetCore/Program.cs | 2 +- samples/ControlCatalog/App.xaml.cs | 9 ++ src/Avalonia.Controls/AppBuilderBase.cs | 17 ++- src/Avalonia.Controls/Application.cs | 112 +++++++++--------- .../IApplicationLifecycle.cs | 9 +- .../{ExitMode.cs => ShutdownMode.cs} | 8 +- src/Avalonia.Controls/TopLevel.cs | 4 +- src/Avalonia.Controls/Window.cs | 18 ++- src/Avalonia.Controls/WindowCollection.cs | 12 +- .../ApplicationTests.cs | 35 ++++-- .../TopLevelTests.cs | 2 +- 12 files changed, 144 insertions(+), 92 deletions(-) rename src/Avalonia.Controls/{ExitMode.cs => ShutdownMode.cs} (88%) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 329b2ab5a3..b809cc8c94 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -4,7 +4,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Platform; -using Serilog; namespace ControlCatalog { @@ -15,7 +14,12 @@ namespace ControlCatalog { // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + private static void AppMain(Application app, string[] args) + { + app.Run(); } /// diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 57c8b700df..ee4c3fe350 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -36,7 +36,7 @@ namespace ControlCatalog.NetCore static void AppMain(Application app, string[] args) { - app.Run(new MainWindow()); + app.Run(); } /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index d862749132..6fbcecfd6e 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -9,5 +9,14 @@ namespace ControlCatalog { AvaloniaXamlLoader.Load(this); } + + protected override void OnStartup() + { + base.OnStartup(); + + var mainWindow = new MainWindow(); + + mainWindow.Show(); + } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 1f6870d60d..2dfc04132a 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -147,13 +147,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. /// @@ -219,13 +226,13 @@ namespace Avalonia.Controls /// /// Sets the shutdown mode of the application. /// - /// The shutdown mode. + /// The shutdown mode. /// - public TAppBuilder SetExitMode(ExitMode exitMode) + public TAppBuilder SetExitMode(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..383b994e70 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -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,12 +194,12 @@ 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; set; } /// /// Initializes the application by loading XAML etc. @@ -204,6 +208,35 @@ namespace Avalonia { } + public void Run() + { + if (_mainLoopCancellationTokenSource != null) + { + throw new Exception("Run should only called once"); + } + + _mainLoopCancellationTokenSource = new CancellationTokenSource(); + + Dispatcher.UIThread.Post(OnStartup, DispatcherPriority.Send); + + Run(_mainLoopCancellationTokenSource.Token); + } + + /// + /// Runs the application's main loop until the is canceled. + /// + /// The token to track + public void Run(CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + + // Make sure we call OnExit in case an error happened and OnExit() wasn't called explicitly + if (!IsShuttingDown) + { + OnExit(); + } + } + /// /// Runs the application's main loop until the is closed. /// @@ -215,17 +248,11 @@ namespace Avalonia throw new Exception("Run should only called once"); } - closable.Closed += (s, e) => Exit(); + closable.Closed += (s, e) => OnExit(); _mainLoopCancellationTokenSource = new CancellationTokenSource(); - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } + Run(_mainLoopCancellationTokenSource.Token); } /// @@ -252,46 +279,31 @@ namespace Avalonia { mainWindow.Show(); } - - MainWindow = mainWindow; - } - - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); } + + Run(_mainLoopCancellationTokenSource.Token); } - /// - /// Runs the application's main loop until the is canceled. - /// - /// The token to track - public void Run(CancellationToken token) + protected virtual void OnStartup() { - Dispatcher.UIThread.MainLoop(token); + Startup?.Invoke(this, EventArgs.Empty); + } - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } + protected virtual void OnExit() + { + Exit?.Invoke(this, EventArgs.Empty); } - /// - /// Exits the application - /// - public void Exit() + /// + public void Shutdown() { - IsExiting = true; + IsShuttingDown = true; Windows.Clear(); - OnExit?.Invoke(this, EventArgs.Empty); - _mainLoopCancellationTokenSource?.Cancel(); + + OnExit(); } /// @@ -302,19 +314,7 @@ 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/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index 51f554c078..c088ceb0eb 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -7,14 +7,19 @@ 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. /// - void Exit(); + void Shutdown(); } } diff --git a/src/Avalonia.Controls/ExitMode.cs b/src/Avalonia.Controls/ShutdownMode.cs similarity index 88% rename from src/Avalonia.Controls/ExitMode.cs rename to src/Avalonia.Controls/ShutdownMode.cs index b73fe4a963..8c52d4b70e 100644 --- a/src/Avalonia.Controls/ExitMode.cs +++ b/src/Avalonia.Controls/ShutdownMode.cs @@ -6,7 +6,7 @@ namespace Avalonia /// /// Enum for ExitMode /// - public enum ExitMode + public enum ShutdownMode { /// /// Indicates an implicit call to Application.Exit when the last window closes. @@ -19,8 +19,8 @@ namespace Avalonia 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/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 32c40847c5..d627785129 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -124,7 +124,7 @@ namespace Avalonia.Controls if (_applicationLifecycle != null) { - _applicationLifecycle.OnExit += OnApplicationExiting; + _applicationLifecycle.Exit += OnApplicationExiting; } if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources) @@ -278,7 +278,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 f5af6774b5..648256e667 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -49,6 +49,8 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope { + private static bool s_hasAddedFirstWindow; + /// /// Defines the property. /// @@ -250,7 +252,7 @@ namespace Avalonia.Controls /// /// Fired before a window is closed. /// - public event EventHandler Closing; + public event EventHandler Closing; private static void AddWindow(Window window) { @@ -260,6 +262,18 @@ namespace Avalonia.Controls } Application.Current.Windows.Add(window); + + if (s_hasAddedFirstWindow) + { + return; + } + + s_hasAddedFirstWindow = true; + + if (Application.Current.MainWindow == null) + { + Application.Current.MainWindow = window; + } } private static void RemoveWindow(Window window) @@ -428,7 +442,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..7cedd9b69a 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnMainWindowClose; + Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; var mainWindow = new Window(); @@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests mainWindow.Close(); - Assert.True(Application.Current.IsExiting); + Assert.True(Application.Current.IsShuttingDown); } } @@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnLastWindowClose; + Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose; var windowA = new Window(); @@ -50,11 +50,11 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(Application.Current.IsShuttingDown); windowB.Close(); - Assert.True(Application.Current.IsExiting); + Assert.True(Application.Current.IsShuttingDown); } } @@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Application.Current.ExitMode = ExitMode.OnExplicitExit; + Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; var windowA = new Window(); @@ -75,15 +75,15 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(Application.Current.IsShuttingDown); windowB.Close(); - Assert.False(Application.Current.IsExiting); + Assert.False(Application.Current.IsShuttingDown); - Application.Current.Exit(); + Application.Current.Shutdown(); - Assert.True(Application.Current.IsExiting); + Assert.True(Application.Current.IsShuttingDown); } } @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests window.Show(); } - Application.Current.Exit(); + Application.Current.Shutdown(); Assert.Empty(Application.Current.Windows); } @@ -129,5 +129,18 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } + + [Fact] + public void Should_Have_MainWindow_After_First_Window_Shown() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var mainWindow = new Window(); + + mainWindow.Show(); + + Assert.Equal(mainWindow, Application.Current.MainWindow); + } + } } } 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); } } From ff0228e85eb1334a506a1b0d5f4b0b2120111cbc Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 10 Apr 2019 19:10:45 +0200 Subject: [PATCH 02/12] Rename AppBuilderBase.SetExitMode --- src/Avalonia.Controls/AppBuilderBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 2dfc04132a..cda49a12b3 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -228,7 +228,7 @@ namespace Avalonia.Controls /// /// The shutdown mode. /// - public TAppBuilder SetExitMode(ShutdownMode shutdownMode) + public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode) { Instance.ShutdownMode = shutdownMode; return Self; From 1bb3156950a3caa3af8395a8ab5a36cba8e7bd47 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 11 Apr 2019 17:11:09 +0200 Subject: [PATCH 03/12] Introduce StartupEventArgs and ExitEventArgs --- samples/BindingDemo/App.xaml.cs | 3 +- samples/ControlCatalog.NetCore/Program.cs | 13 ++---- samples/ControlCatalog/App.xaml.cs | 10 ++--- samples/PlatformSanityChecks/App.xaml.cs | 3 +- samples/Previewer/App.xaml.cs | 5 ++- samples/RenderDemo/App.xaml.cs | 3 +- samples/VirtualizationDemo/App.xaml.cs | 3 +- .../interop/Direct3DInteropSample/App.paml.cs | 3 +- src/Avalonia.Controls/AppBuilderBase.cs | 1 - src/Avalonia.Controls/Application.cs | 43 +++++++++---------- .../IApplicationLifecycle.cs | 12 ++++-- .../Controls/ExitEventArgs.cs | 12 ++++++ .../Controls/StartupEventArgs.cs | 30 +++++++++++++ .../App.xaml.cs | 8 +--- 14 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 src/Avalonia.Styling/Controls/ExitEventArgs.cs create mode 100644 src/Avalonia.Styling/Controls/StartupEventArgs.cs diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 01c52a2a49..d95241372f 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -3,13 +3,12 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; -using Serilog; namespace BindingDemo { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index ee4c3fe350..42be488569 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -3,13 +3,11 @@ using System.Diagnostics; using System.Linq; using System.Threading; using Avalonia; -using Avalonia.Skia; namespace ControlCatalog.NetCore { static class Program - { - + { static void Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); @@ -28,15 +26,10 @@ namespace ControlCatalog.NetCore AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(AppMain, args); - } - - static void AppMain(Application app, string[] args) - { - app.Run(); + BuildAvaloniaApp().Start(); } /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 6fbcecfd6e..1f466d9f91 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -1,18 +1,16 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace ControlCatalog { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { - AvaloniaXamlLoader.Load(this); - } + base.OnStartup(e); - protected override void OnStartup() - { - base.OnStartup(); + AvaloniaXamlLoader.Load(this); var mainWindow = new MainWindow(); diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs index 508fc1e34b..b8f8350156 100644 --- a/samples/PlatformSanityChecks/App.xaml.cs +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -1,11 +1,12 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace PlatformSanityChecks { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index fffa987a27..0fc42583a5 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -1,14 +1,15 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Previewer { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } } -} \ No newline at end of file +} diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index 0f627961e6..4ac844983b 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; +using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; @@ -9,7 +10,7 @@ namespace RenderDemo { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index b220807443..787bbf8d11 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -2,13 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace VirtualizationDemo { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index 1b6d5fd39c..b08c02509c 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -1,11 +1,12 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Direct3DInteropSample { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index cda49a12b3..568b04c78c 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -292,7 +292,6 @@ namespace Avalonia.Controls WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); Instance.RegisterServices(); - Instance.Initialize(); AfterSetupCallback(Self); } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 383b994e70..2893505655 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -55,10 +55,10 @@ namespace Avalonia } /// - public event EventHandler Startup; + public event EventHandler Startup; /// - public event EventHandler Exit; + public event EventHandler Exit; /// public event EventHandler ResourcesChanged; @@ -199,14 +199,7 @@ namespace Avalonia /// /// true if this instance is shutting down; otherwise, false. /// - internal bool IsShuttingDown { get; set; } - - /// - /// Initializes the application by loading XAML etc. - /// - public virtual void Initialize() - { - } + internal bool IsShuttingDown { get; private set; } public void Run() { @@ -217,7 +210,7 @@ namespace Avalonia _mainLoopCancellationTokenSource = new CancellationTokenSource(); - Dispatcher.UIThread.Post(OnStartup, DispatcherPriority.Send); + Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); Run(_mainLoopCancellationTokenSource.Token); } @@ -233,7 +226,7 @@ namespace Avalonia // Make sure we call OnExit in case an error happened and OnExit() wasn't called explicitly if (!IsShuttingDown) { - OnExit(); + OnExit(new ExitEventArgs()); } } @@ -248,10 +241,10 @@ namespace Avalonia throw new Exception("Run should only called once"); } - closable.Closed += (s, e) => OnExit(); - _mainLoopCancellationTokenSource = new CancellationTokenSource(); + closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); + Run(_mainLoopCancellationTokenSource.Token); } @@ -284,26 +277,34 @@ namespace Avalonia Run(_mainLoopCancellationTokenSource.Token); } - protected virtual void OnStartup() + protected virtual void OnStartup(StartupEventArgs e) { - Startup?.Invoke(this, EventArgs.Empty); + Startup?.Invoke(this, e); } - protected virtual void OnExit() + protected virtual void OnExit(ExitEventArgs e) { - Exit?.Invoke(this, EventArgs.Empty); + Exit?.Invoke(this, e); + + Environment.ExitCode = e.ApplicationExitCode; } /// public void Shutdown() + { + Shutdown(0); + } + + /// + public void Shutdown(int exitCode) { IsShuttingDown = true; Windows.Clear(); - _mainLoopCancellationTokenSource?.Cancel(); + OnExit(new ExitEventArgs { ApplicationExitCode = exitCode }); - OnExit(); + _mainLoopCancellationTokenSource?.Cancel(); } /// @@ -314,8 +315,6 @@ namespace Avalonia Styles.TryGetResource(key, out value); } - - /// /// Register's the services needed by Avalonia. /// diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/IApplicationLifecycle.cs index c088ceb0eb..958c448d3e 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -10,16 +10,22 @@ namespace Avalonia.Controls /// /// Sent when the application is starting up. /// - event EventHandler Startup; + event EventHandler Startup; /// /// Sent when the application is exiting. /// - event EventHandler Exit; + event EventHandler Exit; /// - /// Exits the application. + /// Shuts down an application that returns the specified exit code to the operating system. /// void Shutdown(); + + /// + /// Shuts down an application that returns the specified exit code to the operating system. + /// + /// An integer exit code for an application. The default exit code is 0. + void Shutdown(int exitCode); } } diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Styling/Controls/ExitEventArgs.cs new file mode 100644 index 0000000000..1a4ee15f17 --- /dev/null +++ b/src/Avalonia.Styling/Controls/ExitEventArgs.cs @@ -0,0 +1,12 @@ +// 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 +{ + public class ExitEventArgs : EventArgs + { + public int ApplicationExitCode { get; set; } + } +} diff --git a/src/Avalonia.Styling/Controls/StartupEventArgs.cs b/src/Avalonia.Styling/Controls/StartupEventArgs.cs new file mode 100644 index 0000000000..8b66ce0dc2 --- /dev/null +++ b/src/Avalonia.Styling/Controls/StartupEventArgs.cs @@ -0,0 +1,30 @@ +// 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 +{ + public class StartupEventArgs : EventArgs + { + private string[] _args; + + 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/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index 653a51232b..e2fc6ab7a2 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp { public class App : Application { - public override void Initialize() + protected override void OnStartup(StartupEventArgs e) { AvaloniaXamlLoader.Load(this); } From 7e042158977c4111a67e901b21bbfe289897033e Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 12 Apr 2019 11:41:59 +0200 Subject: [PATCH 04/12] No longer set first window opened as MainWindow --- samples/ControlCatalog/App.xaml.cs | 2 ++ src/Avalonia.Controls/Application.cs | 2 ++ src/Avalonia.Controls/Window.cs | 14 -------------- .../ApplicationTests.cs | 13 ------------- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 1f466d9f91..100cfa634e 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -15,6 +15,8 @@ namespace ControlCatalog var mainWindow = new MainWindow(); mainWindow.Show(); + + MainWindow = mainWindow; } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 2893505655..b750e570b3 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -272,6 +272,8 @@ namespace Avalonia { mainWindow.Show(); } + + MainWindow = mainWindow; } Run(_mainLoopCancellationTokenSource.Token); diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 648256e667..411685800e 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -49,8 +49,6 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope { - private static bool s_hasAddedFirstWindow; - /// /// Defines the property. /// @@ -262,18 +260,6 @@ namespace Avalonia.Controls } Application.Current.Windows.Add(window); - - if (s_hasAddedFirstWindow) - { - return; - } - - s_hasAddedFirstWindow = true; - - if (Application.Current.MainWindow == null) - { - Application.Current.MainWindow = window; - } } private static void RemoveWindow(Window window) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index 7cedd9b69a..f362e691dc 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -129,18 +129,5 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } - - [Fact] - public void Should_Have_MainWindow_After_First_Window_Shown() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var mainWindow = new Window(); - - mainWindow.Show(); - - Assert.Equal(mainWindow, Application.Current.MainWindow); - } - } } } From 05978b0ba620649e547f4f778ed145d407abfc94 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 12 Apr 2019 11:50:53 +0200 Subject: [PATCH 05/12] Make sure Shutdown is only called once --- src/Avalonia.Controls/Application.cs | 33 +++++++++---------- .../Controls/ExitEventArgs.cs | 6 +++- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index b750e570b3..99fc2e7367 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -215,21 +215,6 @@ namespace Avalonia Run(_mainLoopCancellationTokenSource.Token); } - /// - /// Runs the application's main loop until the is canceled. - /// - /// The token to track - public void Run(CancellationToken token) - { - Dispatcher.UIThread.MainLoop(token); - - // Make sure we call OnExit in case an error happened and OnExit() wasn't called explicitly - if (!IsShuttingDown) - { - OnExit(new ExitEventArgs()); - } - } - /// /// Runs the application's main loop until the is closed. /// @@ -279,6 +264,17 @@ namespace Avalonia Run(_mainLoopCancellationTokenSource.Token); } + /// + /// Runs the application's main loop until the is canceled. + /// + /// The token to track + public void Run(CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + + Shutdown(); + } + protected virtual void OnStartup(StartupEventArgs e) { Startup?.Invoke(this, e); @@ -287,8 +283,6 @@ namespace Avalonia protected virtual void OnExit(ExitEventArgs e) { Exit?.Invoke(this, e); - - Environment.ExitCode = e.ApplicationExitCode; } /// @@ -300,6 +294,11 @@ namespace Avalonia /// public void Shutdown(int exitCode) { + if (IsShuttingDown) + { + return; + } + IsShuttingDown = true; Windows.Clear(); diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Styling/Controls/ExitEventArgs.cs index 1a4ee15f17..2e6409e296 100644 --- a/src/Avalonia.Styling/Controls/ExitEventArgs.cs +++ b/src/Avalonia.Styling/Controls/ExitEventArgs.cs @@ -7,6 +7,10 @@ namespace Avalonia.Controls { public class ExitEventArgs : EventArgs { - public int ApplicationExitCode { get; set; } + public int ApplicationExitCode + { + get => Environment.ExitCode; + set => Environment.ExitCode = value; + } } } From 21eda0db12c13b27a0671ec2df8b04c9011c461d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 20 Apr 2019 18:36:51 +0200 Subject: [PATCH 06/12] Revert removal of Application.Initialize --- samples/BindingDemo/App.xaml.cs | 2 +- samples/ControlCatalog.NetCore/Program.cs | 2 +- samples/ControlCatalog/App.xaml.cs | 11 +- samples/PlatformSanityChecks/App.xaml.cs | 2 +- samples/Previewer/App.xaml.cs | 2 +- samples/RenderDemo/App.xaml.cs | 2 +- samples/VirtualizationDemo/App.xaml.cs | 2 +- .../interop/Direct3DInteropSample/App.paml.cs | 2 +- src/Avalonia.Controls/AppBuilderBase.cs | 1 + src/Avalonia.Controls/Application.cs | 104 +++++++++++------- .../Controls/ExitEventArgs.cs | 6 +- .../ApplicationTests.cs | 25 +++++ .../App.xaml.cs | 2 +- 13 files changed, 102 insertions(+), 61 deletions(-) diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 88333857b8..f2f44cd502 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -10,7 +10,7 @@ namespace BindingDemo { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 24f88ecba0..6a8175129f 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -31,7 +31,7 @@ namespace ControlCatalog.NetCore ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(); } /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 100cfa634e..d862749132 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -1,22 +1,13 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace ControlCatalog { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { - base.OnStartup(e); - AvaloniaXamlLoader.Load(this); - - var mainWindow = new MainWindow(); - - mainWindow.Show(); - - MainWindow = mainWindow; } } } diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs index b8f8350156..0f9c004630 100644 --- a/samples/PlatformSanityChecks/App.xaml.cs +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -6,7 +6,7 @@ namespace PlatformSanityChecks { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index 0fc42583a5..6d7d051218 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -6,7 +6,7 @@ namespace Previewer { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index b758f887df..ce75a335d5 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -11,7 +11,7 @@ namespace RenderDemo { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index 787bbf8d11..637c8726f6 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -9,7 +9,7 @@ namespace VirtualizationDemo { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index b08c02509c..5269854138 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -6,7 +6,7 @@ namespace Direct3DInteropSample { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 697c838b6a..419064b051 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -311,6 +311,7 @@ namespace Avalonia.Controls WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); Instance.RegisterServices(); + Instance.Initialize(); AfterSetupCallback(Self); } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 99fc2e7367..e0f7b84e98 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. @@ -201,53 +201,40 @@ namespace Avalonia /// internal bool IsShuttingDown { get; private set; } - public void Run() - { - if (_mainLoopCancellationTokenSource != null) - { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - - Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); + /// + /// Initializes the application by loading XAML etc. + /// + public virtual void Initialize() { } - Run(_mainLoopCancellationTokenSource.Token); + public int Run() + { + return Run(new CancellationTokenSource()); } /// /// Runs the application's main loop until the is closed. /// /// The closable to track - public void Run(ICloseable closable) + public int Run(ICloseable closable) { - if (_mainLoopCancellationTokenSource != null) - { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); - Run(_mainLoopCancellationTokenSource.Token); + return Run(new CancellationTokenSource()); } /// /// Runs the application's main loop until some condition occurs that is specified by ExitMode. /// /// The main window - public void Run(Window mainWindow) + public int Run(Window mainWindow) { - if (_mainLoopCancellationTokenSource != null) + Dispatcher.UIThread.Post(() => { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); + if (MainWindow != null) + { + return; + } - if (MainWindow == null) - { if (mainWindow == null) { throw new ArgumentNullException(nameof(mainWindow)); @@ -259,20 +246,44 @@ namespace Avalonia } MainWindow = mainWindow; - } + }); - Run(_mainLoopCancellationTokenSource.Token); + return Run(new CancellationTokenSource()); } /// /// Runs the application's main loop until the is canceled. /// /// The token to track - public void Run(CancellationToken token) + public int Run(CancellationToken token) { - Dispatcher.UIThread.MainLoop(token); + return Run(CancellationTokenSource.CreateLinkedTokenSource(token)); + } - Shutdown(); + private int Run(CancellationTokenSource tokenSource) + { + if (IsShuttingDown) + { + throw new InvalidOperationException("Application is shutting down."); + } + + if (_mainLoopCancellationTokenSource != null) + { + throw new InvalidOperationException("Application is already running."); + } + + _mainLoopCancellationTokenSource = tokenSource; + + Dispatcher.UIThread.Post(() => OnStartup(new StartupEventArgs()), DispatcherPriority.Send); + + Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); + + if (!IsShuttingDown) + { + Shutdown(_exitCode); + } + + return _exitCode; } protected virtual void OnStartup(StartupEventArgs e) @@ -296,16 +307,33 @@ namespace Avalonia { if (IsShuttingDown) { - return; + throw new InvalidOperationException("Application is already shutting down."); } - IsShuttingDown = true; + _exitCode = exitCode; + + IsShuttingDown = true; Windows.Clear(); - OnExit(new ExitEventArgs { ApplicationExitCode = exitCode }); + try + { + var e = new ExitEventArgs { ApplicationExitCode = _exitCode }; + + OnExit(e); - _mainLoopCancellationTokenSource?.Cancel(); + _exitCode = e.ApplicationExitCode; + + Environment.ExitCode = _exitCode; + } + finally + { + _mainLoopCancellationTokenSource?.Cancel(); + + _mainLoopCancellationTokenSource = null; + + IsShuttingDown = false; + } } /// diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Styling/Controls/ExitEventArgs.cs index 2e6409e296..1a4ee15f17 100644 --- a/src/Avalonia.Styling/Controls/ExitEventArgs.cs +++ b/src/Avalonia.Styling/Controls/ExitEventArgs.cs @@ -7,10 +7,6 @@ namespace Avalonia.Controls { public class ExitEventArgs : EventArgs { - public int ApplicationExitCode - { - get => Environment.ExitCode; - set => Environment.ExitCode = value; - } + public int ApplicationExitCode { get; set; } } } diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index f362e691dc..f9b1ea251b 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; @@ -129,5 +130,29 @@ namespace Avalonia.Controls.UnitTests Assert.True(raised); } } + + [Fact] + public void Throws_InvalidOperationException_On_Run_When_Application_Is_Already_Running() + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + Application.Current.Run(); + + Assert.Throws(() => { Application.Current.Run(); }); + } + } + + [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.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index e2fc6ab7a2..caa3c1baa8 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -5,7 +5,7 @@ namespace Avalonia.DesignerSupport.TestApp { public class App : Application { - protected override void OnStartup(StartupEventArgs e) + public override void Initialize() { AvaloniaXamlLoader.Load(this); } From c73363f76b659b52cbd96bdccb82d228f22c94fd Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 20 Apr 2019 18:44:15 +0200 Subject: [PATCH 07/12] Remove extra usings --- samples/ControlCatalog.Desktop/Program.cs | 7 +------ samples/ControlCatalog.NetCore/Program.cs | 12 +++++++++--- samples/Previewer/App.xaml.cs | 1 - samples/RenderDemo/App.xaml.cs | 1 - samples/VirtualizationDemo/App.xaml.cs | 1 - samples/interop/Direct3DInteropSample/App.paml.cs | 1 - tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs | 5 ++--- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 2bfd223bb6..b7aa34f5ba 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -16,12 +16,7 @@ namespace ControlCatalog { // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. - BuildAvaloniaApp().Start(AppMain, args); - } - - private static void AppMain(Application app, string[] args) - { - app.Run(); + BuildAvaloniaApp().Start(); } /// diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 6a8175129f..c8f3fb9921 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -9,7 +9,8 @@ using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore { static class Program - { + { + static void Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); @@ -28,10 +29,15 @@ namespace ControlCatalog.NetCore AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => { tl.Content = new MainView(); - ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); }); else - BuildAvaloniaApp().Start(); + BuildAvaloniaApp().Start(AppMain, args); + } + + static void AppMain(Application app, string[] args) + { + app.Run(new MainWindow()); } /// diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index 6d7d051218..4c18874b04 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Previewer diff --git a/samples/RenderDemo/App.xaml.cs b/samples/RenderDemo/App.xaml.cs index ce75a335d5..d95018520a 100644 --- a/samples/RenderDemo/App.xaml.cs +++ b/samples/RenderDemo/App.xaml.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; -using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs index 637c8726f6..b220807443 100644 --- a/samples/VirtualizationDemo/App.xaml.cs +++ b/samples/VirtualizationDemo/App.xaml.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace VirtualizationDemo diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs index 5269854138..1b6d5fd39c 100644 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ b/samples/interop/Direct3DInteropSample/App.paml.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Direct3DInteropSample diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index caa3c1baa8..5ee3487b6b 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,11 +1,10 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp { public class App : Application { - public override void Initialize() + public override void Initialize() { AvaloniaXamlLoader.Load(this); } From a5313ab3fb08b25050960a7d2f5c077a630e38b0 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 20 Apr 2019 18:50:35 +0200 Subject: [PATCH 08/12] Revert changes --- samples/PlatformSanityChecks/App.xaml.cs | 1 - samples/Previewer/App.xaml.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs index 0f9c004630..508fc1e34b 100644 --- a/samples/PlatformSanityChecks/App.xaml.cs +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace PlatformSanityChecks diff --git a/samples/Previewer/App.xaml.cs b/samples/Previewer/App.xaml.cs index 4c18874b04..fffa987a27 100644 --- a/samples/Previewer/App.xaml.cs +++ b/samples/Previewer/App.xaml.cs @@ -11,4 +11,4 @@ namespace Previewer } } -} +} \ No newline at end of file From a14435f59f49c6df75a8b9f1b11dd57e8dae27e7 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 27 Apr 2019 19:54:05 +0200 Subject: [PATCH 09/12] Fix unit tests --- src/Avalonia.Controls/Application.cs | 30 +++++++++--------- .../ApplicationTests.cs | 31 ++++++++++++++----- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index e0f7b84e98..6f5cc3d4d6 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -228,25 +228,23 @@ namespace Avalonia /// The main window public int Run(Window mainWindow) { - Dispatcher.UIThread.Post(() => + if (mainWindow == null) { - if (MainWindow != null) - { - return; - } - - if (mainWindow == null) - { - throw new ArgumentNullException(nameof(mainWindow)); - } + throw new ArgumentNullException(nameof(mainWindow)); + } - if (!mainWindow.IsVisible) + if (MainWindow == null) + { + Dispatcher.UIThread.Post(() => { - mainWindow.Show(); - } - - MainWindow = mainWindow; - }); + if (!mainWindow.IsVisible) + { + mainWindow.Show(); + } + + MainWindow = mainWindow; + }); + } return Run(new CancellationTokenSource()); } diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index f9b1ea251b..b3ce1a3866 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -18,6 +18,10 @@ namespace Avalonia.Controls.UnitTests { Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose; + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; + var mainWindow = new Window(); mainWindow.Show(); @@ -30,7 +34,7 @@ namespace Avalonia.Controls.UnitTests mainWindow.Close(); - Assert.True(Application.Current.IsShuttingDown); + Assert.True(hasExit); } } @@ -41,6 +45,10 @@ namespace Avalonia.Controls.UnitTests { Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose; + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; + var windowA = new Window(); windowA.Show(); @@ -51,11 +59,11 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsShuttingDown); + Assert.False(hasExit); windowB.Close(); - Assert.True(Application.Current.IsShuttingDown); + Assert.True(hasExit); } } @@ -66,6 +74,10 @@ namespace Avalonia.Controls.UnitTests { Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; + var hasExit = false; + + Application.Current.Exit += (s, e) => hasExit = true; + var windowA = new Window(); windowA.Show(); @@ -76,15 +88,15 @@ namespace Avalonia.Controls.UnitTests windowA.Close(); - Assert.False(Application.Current.IsShuttingDown); + Assert.False(hasExit); windowB.Close(); - Assert.False(Application.Current.IsShuttingDown); + Assert.False(hasExit); Application.Current.Shutdown(); - Assert.True(Application.Current.IsShuttingDown); + Assert.True(hasExit); } } @@ -136,9 +148,12 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) { - Application.Current.Run(); + Application.Current.Startup += (s, e) => + { + Assert.Throws(() => { Application.Current.Run(); }); + }; - Assert.Throws(() => { Application.Current.Run(); }); + Application.Current.Run(); } } From fa04a50f85675fc214b3913a1de8141f5c962431 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 29 Apr 2019 15:26:07 +0200 Subject: [PATCH 10/12] Remove non working test --- .../ApplicationTests.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index b3ce1a3866..b7d752dfe3 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs @@ -143,20 +143,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Throws_InvalidOperationException_On_Run_When_Application_Is_Already_Running() - { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) - { - Application.Current.Startup += (s, e) => - { - Assert.Throws(() => { Application.Current.Run(); }); - }; - - Application.Current.Run(); - } - } - [Fact] public void Should_Set_ExitCode_After_Shutdown() { From d25fba75e5a6702a04f6f66c243bbd8570b1215d Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sat, 11 May 2019 16:38:31 +0200 Subject: [PATCH 11/12] Add xml comments --- src/Avalonia.Controls/Application.cs | 47 +++++++++++++------ .../ExitEventArgs.cs | 6 +++ .../IApplicationLifecycle.cs | 9 +--- .../StartupEventArgs.cs | 6 +++ 4 files changed, 47 insertions(+), 21 deletions(-) rename src/{Avalonia.Styling/Controls => Avalonia.Controls}/ExitEventArgs.cs (52%) rename src/{Avalonia.Styling/Controls => Avalonia.Controls}/StartupEventArgs.cs (76%) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6f5cc3d4d6..62f49f0f88 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -206,15 +206,25 @@ namespace Avalonia /// 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 passed on the operating system. public int Run() { return Run(new CancellationTokenSource()); } /// - /// Runs the application's main loop until the is closed. + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. + /// This also returns when is closed. /// - /// The closable to track + /// The closable to track. + /// The application's exit code that is passed on the operating system. public int Run(ICloseable closable) { closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); @@ -223,9 +233,13 @@ namespace Avalonia } /// - /// Runs the application's main loop until some condition occurs that is specified by ExitMode. + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. /// - /// The main window + /// The window that is used as + /// when the isn't already set. + /// The application's exit code that is passed on the operating system. public int Run(Window mainWindow) { if (mainWindow == null) @@ -248,11 +262,14 @@ namespace Avalonia return Run(new CancellationTokenSource()); } - /// - /// Runs the application's main loop until the is canceled. + /// Runs the application's main loop. + /// This will return when the condition is met + /// or was called. + /// This also returns when the is canceled. /// - /// The token to track + /// The application's exit code that is passed on the operating system. + /// The token to track. public int Run(CancellationToken token) { return Run(CancellationTokenSource.CreateLinkedTokenSource(token)); @@ -284,24 +301,26 @@ namespace Avalonia return _exitCode; } + /// + /// Raises the event. + /// + /// A that contains the event data. protected virtual void OnStartup(StartupEventArgs e) { Startup?.Invoke(this, e); } + /// + /// Raises the event. + /// + /// A that contains the event data. protected virtual void OnExit(ExitEventArgs e) { Exit?.Invoke(this, e); } /// - public void Shutdown() - { - Shutdown(0); - } - - /// - public void Shutdown(int exitCode) + public void Shutdown(int exitCode = 0) { if (IsShuttingDown) { diff --git a/src/Avalonia.Styling/Controls/ExitEventArgs.cs b/src/Avalonia.Controls/ExitEventArgs.cs similarity index 52% rename from src/Avalonia.Styling/Controls/ExitEventArgs.cs rename to src/Avalonia.Controls/ExitEventArgs.cs index 1a4ee15f17..c99f7fe047 100644 --- a/src/Avalonia.Styling/Controls/ExitEventArgs.cs +++ b/src/Avalonia.Controls/ExitEventArgs.cs @@ -5,8 +5,14 @@ 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 958c448d3e..a3c6599b20 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/IApplicationLifecycle.cs @@ -18,14 +18,9 @@ namespace Avalonia.Controls event EventHandler Exit; /// - /// Shuts down an application that returns the specified exit code to the operating system. - /// - void Shutdown(); - - /// - /// Shuts down an application that returns the specified exit code to the operating system. + /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. /// /// An integer exit code for an application. The default exit code is 0. - void Shutdown(int exitCode); + void Shutdown(int exitCode = 0); } } diff --git a/src/Avalonia.Styling/Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs similarity index 76% rename from src/Avalonia.Styling/Controls/StartupEventArgs.cs rename to src/Avalonia.Controls/StartupEventArgs.cs index 8b66ce0dc2..17dcbb2aa9 100644 --- a/src/Avalonia.Styling/Controls/StartupEventArgs.cs +++ b/src/Avalonia.Controls/StartupEventArgs.cs @@ -7,10 +7,16 @@ using System.Linq; namespace Avalonia.Controls { + /// + /// Contains the arguments for the event. + /// public class StartupEventArgs : EventArgs { private string[] _args; + /// + /// Gets command line arguments that were passed to the application. + /// public IReadOnlyList Args => _args ?? (_args = GetArgs()); private static string[] GetArgs() From 19593825d5f4a472a8f726a9b17fb90542d3804a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sun, 12 May 2019 11:24:09 +0200 Subject: [PATCH 12/12] Fix comments --- src/Avalonia.Controls/Application.cs | 42 +++++++++++-------- src/Avalonia.Controls/ShutdownMode.cs | 8 ++-- src/Avalonia.Controls/StartupEventArgs.cs | 2 +- .../App.xaml.cs | 7 +++- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 62f49f0f88..bbea3693cc 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -208,10 +208,12 @@ namespace Avalonia /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. /// - /// The application's exit code that is passed on the operating system. + /// + /// 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()); @@ -219,12 +221,14 @@ namespace Avalonia /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. - /// This also returns when is closed. /// + /// + /// 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 passed on the operating system. + /// The application's exit code that is returned to the operating system on termination. public int Run(ICloseable closable) { closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); @@ -234,12 +238,14 @@ namespace Avalonia /// /// Runs the application's main loop. - /// This will return when the condition is met - /// or was called. /// + /// + /// 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 passed on the operating system. + /// The application's exit code that is returned to the operating system on termination. public int Run(Window mainWindow) { if (mainWindow == null) @@ -264,11 +270,13 @@ namespace Avalonia } /// /// Runs the application's main loop. - /// 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 passed on the operating system. + /// + /// 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) { @@ -339,9 +347,7 @@ namespace Avalonia OnExit(e); - _exitCode = e.ApplicationExitCode; - - Environment.ExitCode = _exitCode; + _exitCode = e.ApplicationExitCode; } finally { @@ -350,6 +356,8 @@ namespace Avalonia _mainLoopCancellationTokenSource = null; IsShuttingDown = false; + + Environment.ExitCode = _exitCode; } } diff --git a/src/Avalonia.Controls/ShutdownMode.cs b/src/Avalonia.Controls/ShutdownMode.cs index 8c52d4b70e..46e27ff4e1 100644 --- a/src/Avalonia.Controls/ShutdownMode.cs +++ b/src/Avalonia.Controls/ShutdownMode.cs @@ -1,20 +1,20 @@ // 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 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, diff --git a/src/Avalonia.Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs index 17dcbb2aa9..0e12f5c01a 100644 --- a/src/Avalonia.Controls/StartupEventArgs.cs +++ b/src/Avalonia.Controls/StartupEventArgs.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls private string[] _args; /// - /// Gets command line arguments that were passed to the application. + /// Gets the command line arguments that were passed to the application. /// public IReadOnlyList Args => _args ?? (_args = GetArgs()); diff --git a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs index 5ee3487b6b..653a51232b 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs +++ b/tests/Avalonia.DesignerSupport.TestApp/App.xaml.cs @@ -1,4 +1,9 @@ -using Avalonia.Markup.Xaml; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Markup.Xaml; namespace Avalonia.DesignerSupport.TestApp {