ソースを参照

Refactored lifetime control into separate lifetime classes

Nikita Tsukanov 6 年 前
コミット
b33601ee9b
27 ファイル変更493 行追加510 行削除
  1. 9 13
      samples/ControlCatalog.NetCore/Program.cs
  2. 11 0
      samples/ControlCatalog/App.xaml.cs
  3. 4 11
      src/Avalonia.Controls/AppBuilderBase.cs
  4. 14 186
      src/Avalonia.Controls/Application.cs
  5. 99 0
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  6. 8 3
      src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs
  7. 7 0
      src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs
  8. 28 0
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  9. 5 8
      src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs
  10. 7 0
      src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs
  11. 22 0
      src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs
  12. 67 0
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  13. 0 36
      src/Avalonia.Controls/StartupEventArgs.cs
  14. 0 20
      src/Avalonia.Controls/TopLevel.cs
  15. 0 19
      src/Avalonia.Controls/Window.cs
  16. 5 28
      src/Avalonia.Controls/WindowCollection.cs
  17. 2 10
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  18. 1 1
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  19. 55 19
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  20. 7 5
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  21. 0 120
      tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
  22. 1 3
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  23. 139 0
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  24. 0 19
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  25. 0 6
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  26. 1 1
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  27. 1 2
      tests/Avalonia.UnitTests/UnitTestApplication.cs

+ 9 - 13
samples/ControlCatalog.NetCore/Program.cs

@@ -3,6 +3,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using Avalonia;
+using Avalonia.Controls;
 using Avalonia.Skia;
 using Avalonia.ReactiveUI;
 
@@ -11,7 +12,7 @@ namespace ControlCatalog.NetCore
     static class Program
     {
 
-        static void Main(string[] args)
+        static int Main(string[] args)
         {
             Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
             if (args.Contains("--wait-for-attach"))
@@ -25,21 +26,16 @@ namespace ControlCatalog.NetCore
                 }
             }
 
+            var builder = BuildAvaloniaApp();
             if (args.Contains("--fbdev"))
-                AppBuilder.Configure<App>().InitializeWithLinuxFramebuffer(tl =>
-                {
-                    tl.Content = new MainView();
-                    System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
-                });
+            {
+                System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
+                return builder.StartLinuxFramebuffer(args);
+            }
             else
-                BuildAvaloniaApp().Start(AppMain, args);
+                return builder.StartWithClassicDesktopLifetime(args);
         }
-
-        static void AppMain(Application app, string[] args)
-        {
-            app.Run(new MainWindow());
-        }
-
+        
         /// <summary>
         /// This method is needed for IDE previewer infrastructure
         /// </summary>

+ 11 - 0
samples/ControlCatalog/App.xaml.cs

@@ -1,4 +1,5 @@
 using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Markup.Xaml;
 
 namespace ControlCatalog
@@ -9,5 +10,15 @@ namespace ControlCatalog
         {
             AvaloniaXamlLoader.Load(this);
         }
+
+        public override void OnFrameworkInitializationCompleted()
+        {
+            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+                desktopLifetime.MainWindow = new MainWindow();
+            else if (ApplicationLifetime is ISingleViewLifetime singleViewLifetime)
+                singleViewLifetime.MainView = new MainView();
+            
+            base.OnFrameworkInitializationCompleted();
+        }
     }
 }

+ 4 - 11
src/Avalonia.Controls/AppBuilderBase.cs

@@ -117,6 +117,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <typeparam name="TMainWindow">The window type.</typeparam>
         /// <param name="dataContextProvider">A delegate that will be called to create a data context for the window (optional).</param>
+        [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")]
         public void Start<TMainWindow>(Func<object> dataContextProvider = null)
             where TMainWindow : Window, new()
         {
@@ -135,6 +136,7 @@ namespace Avalonia.Controls
         /// <typeparam name="TMainWindow">The window type.</typeparam>
         /// <param name="mainWindow">Instance of type TMainWindow to use when starting the app</param>
         /// <param name="dataContextProvider">A delegate that will be called to create a data context for the window (optional).</param>
+        [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")]
         public void Start<TMainWindow>(TMainWindow mainWindow, Func<object> dataContextProvider = null)
             where TMainWindow : Window
         {
@@ -148,6 +150,7 @@ namespace Avalonia.Controls
 
         public delegate void AppMainDelegate(Application app, string[] args);
 
+        [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")]
         public void Start()
         {
             Setup();
@@ -224,17 +227,6 @@ namespace Avalonia.Controls
 
         public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
 
-        /// <summary>
-        /// Sets the shutdown mode of the application.
-        /// </summary>
-        /// <param name="shutdownMode">The shutdown mode.</param>
-        /// <returns></returns>
-        public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode)
-        {
-            Instance.ShutdownMode = shutdownMode;
-            return Self;
-        }
-
         protected virtual bool CheckSetup => true;
 
         private void SetupAvaloniaModules()
@@ -313,6 +305,7 @@ namespace Avalonia.Controls
             Instance.RegisterServices();
             Instance.Initialize();
             AfterSetupCallback(Self);
+            Instance.OnFrameworkInitializationCompleted();
         }
     }
 }

+ 14 - 186
src/Avalonia.Controls/Application.cs

@@ -6,6 +6,7 @@ using System.Reactive.Concurrency;
 using System.Threading;
 using Avalonia.Animation;
 using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
@@ -31,7 +32,7 @@ namespace Avalonia
     /// method.
     /// - Tracks the lifetime of the application.
     /// </remarks>
-    public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
+    public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
     {
         /// <summary>
         /// The application-global data templates.
@@ -43,8 +44,6 @@ namespace Avalonia
         private readonly Styler _styler = new Styler();
         private Styles _styles;
         private IResourceDictionary _resources;
-        private CancellationTokenSource _mainLoopCancellationTokenSource;
-        private int _exitCode;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Application"/> class.
@@ -54,12 +53,6 @@ namespace Avalonia
             Windows = new WindowCollection(this);
         }
 
-        /// <inheritdoc/>
-        public event EventHandler<StartupEventArgs> Startup;
-
-        /// <inheritdoc/>
-        public event EventHandler<ExitEventArgs> Exit;
-
         /// <inheritdoc/>
         public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
 
@@ -167,24 +160,6 @@ namespace Avalonia
         /// <inheritdoc/>
         IResourceNode IResourceNode.ResourceParent => null;
 
-        /// <summary>
-        /// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly. 
-        /// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called.
-        /// The default is OnLastWindowClose
-        /// </summary>
-        /// <value>
-        /// The shutdown mode.
-        /// </value>
-        public ShutdownMode ShutdownMode { get; set; }
-
-        /// <summary>
-        /// Gets or sets the main window of the application.
-        /// </summary>
-        /// <value>
-        /// The main window.
-        /// </value>
-        public Window MainWindow { get; set; }
-
         /// <summary>
         /// Gets the open windows of the application.
         /// </summary>
@@ -192,172 +167,21 @@ namespace Avalonia
         /// The windows.
         /// </value>
         public WindowCollection Windows { get; }
-
+        
         /// <summary>
-        /// Gets or sets a value indicating whether this instance is shutting down.
+        /// Application lifetime, use it for things like setting the main window and exiting the app from code
+        /// Currently supported lifetimes are:
+        /// - <see cref="IClassicDesktopStyleApplicationLifetime"/>
+        /// - <see cref="ISingleViewLifetime"/>
+        /// - <see cref="IControlledApplicationLifetime"/> 
         /// </summary>
-        /// <value>
-        ///   <c>true</c> if this instance is shutting down; otherwise, <c>false</c>.
-        /// </value>
-        internal bool IsShuttingDown { get; private set; }
+        public IApplicationLifetime ApplicationLifetime { get; set; }
 
         /// <summary>
         /// Initializes the application by loading XAML etc.
         /// </summary>
         public virtual void Initialize() { }
 
-        /// <summary>
-        /// Runs the application's main loop.
-        /// </summary>
-        /// <remarks>
-        /// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
-        /// or <see cref="Shutdown(int)"/> was called. 
-        /// </remarks>
-        /// <returns>The application's exit code that is returned to the operating system on termination.</returns>
-        public int Run()
-        {
-            return Run(new CancellationTokenSource());
-        }
-
-        /// <summary>
-        /// Runs the application's main loop.
-        /// </summary>
-        /// <remarks>
-        /// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
-        /// or <see cref="Shutdown(int)"/> was called.
-        /// This also returns when <see cref="ICloseable"/> is closed.
-        /// </remarks>
-        /// <param name="closable">The closable to track.</param>
-        /// <returns>The application's exit code that is returned to the operating system on termination.</returns>
-        public int Run(ICloseable closable)
-        {
-            closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel();
-
-            return Run(new CancellationTokenSource());
-        }
-
-        /// <summary>
-        /// Runs the application's main loop.
-        /// </summary>
-        /// <remarks>
-        /// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
-        /// or <see cref="Shutdown(int)"/> was called.
-        /// </remarks>
-        /// <param name="mainWindow">The window that is used as <see cref="MainWindow"/>
-        /// when the <see cref="MainWindow"/> isn't already set.</param>
-        /// <returns>The application's exit code that is returned to the operating system on termination.</returns>
-        public int Run(Window mainWindow)
-        {
-            if (mainWindow == null)
-            {
-                throw new ArgumentNullException(nameof(mainWindow));
-            }
-
-            if (MainWindow == null)
-            {
-                if (!mainWindow.IsVisible)
-                {
-                    mainWindow.Show();
-                }
-
-                MainWindow = mainWindow;
-            }
-
-            return Run(new CancellationTokenSource());
-        }
-        /// <summary>
-        /// Runs the application's main loop.
-        /// </summary>
-        /// <remarks>
-        /// This will return when the <see cref="Avalonia.Controls.ShutdownMode"/> condition is met
-        /// or <see cref="Shutdown(int)"/> was called.
-        /// This also returns when the <see cref="CancellationToken"/> is canceled.
-        /// </remarks>
-        /// <returns>The application's exit code that is returned to the operating system on termination.</returns>
-        /// <param name="token">The token to track.</param>
-        public int Run(CancellationToken token)
-        {
-            return Run(CancellationTokenSource.CreateLinkedTokenSource(token));
-        }
-
-        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;
-        }
-
-        /// <summary>
-        /// Raises the <see cref="Startup"/> event.
-        /// </summary>
-        /// <param name="e">A <see cref="StartupEventArgs"/> that contains the event data.</param>
-        protected virtual void OnStartup(StartupEventArgs e)
-        {
-            Startup?.Invoke(this, e);
-        }
-
-        /// <summary>
-        /// Raises the <see cref="Exit"/> event.
-        /// </summary>
-        /// <param name="e">A <see cref="ExitEventArgs"/> that contains the event data.</param>
-        protected virtual void OnExit(ExitEventArgs e)
-        {
-            Exit?.Invoke(this, e);
-        }
-
-        /// <inheritdoc/>
-        public void Shutdown(int exitCode = 0)
-        {
-            if (IsShuttingDown)
-            {
-                throw new InvalidOperationException("Application is already shutting down.");
-            }
-
-            _exitCode = exitCode;
-
-            IsShuttingDown = true;         
-
-            Windows.Clear();
-
-            try
-            {
-                var e = new ExitEventArgs { ApplicationExitCode = _exitCode };
-
-                OnExit(e);
-
-                _exitCode = e.ApplicationExitCode;                
-            }
-            finally
-            {
-                _mainLoopCancellationTokenSource?.Cancel();
-
-                _mainLoopCancellationTokenSource = null;
-
-                IsShuttingDown = false;
-
-                Environment.ExitCode = _exitCode;
-            }
-        }
-
         /// <inheritdoc/>
         bool IResourceProvider.TryGetResource(object key, out object value)
         {
@@ -383,7 +207,6 @@ namespace Avalonia
                 .Bind<IInputManager>().ToConstant(InputManager)
                 .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
                 .Bind<IStyler>().ToConstant(_styler)
-                .Bind<IApplicationLifecycle>().ToConstant(this)
                 .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
                 .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
                 .Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
@@ -394,6 +217,11 @@ namespace Avalonia
                 .GetService<IRenderLoop>()?.Add(clock);
         }
 
+        public virtual void OnFrameworkInitializationCompleted()
+        {
+            
+        }
+
         private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
         {
             ResourcesChanged?.Invoke(this, e);

+ 99 - 0
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@@ -0,0 +1,99 @@
+using System;
+using System.Threading;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Controls.ApplicationLifetimes
+{
+    public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime
+    {
+        private readonly Application _app;
+        private int _exitCode;
+        private CancellationTokenSource _cts;
+        private bool _isShuttingDown;
+
+        public ClassicDesktopStyleApplicationLifetime(Application app)
+        {
+            _app = app;
+            app.Windows.OnWindowClosed += HandleWindowClosed;
+        }
+        
+        /// <inheritdoc/>
+        public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
+        /// <inheritdoc/>
+        public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
+
+        /// <inheritdoc/>
+        public ShutdownMode ShutdownMode { get; set; }
+        
+        /// <inheritdoc/>
+        public Window MainWindow { get; set; }
+
+        private void HandleWindowClosed(Window window)
+        {
+            if (window == null)
+                return;
+            
+            if (_isShuttingDown)
+                return;
+
+            if (ShutdownMode == ShutdownMode.OnLastWindowClose && _app.Windows.Count == 0)
+                Shutdown();
+            else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow)
+                Shutdown();
+        }
+        
+        
+
+
+        public void Shutdown(int exitCode = 0)
+        {
+            if (_isShuttingDown)
+                throw new InvalidOperationException("Application is already shutting down.");
+            
+            _exitCode = exitCode;
+            _isShuttingDown = true;
+
+            try
+            {
+                _app.Windows.CloseAll();
+                var e = new ControlledApplicationLifetimeExitEventArgs(exitCode);
+                Exit?.Invoke(this, e);
+                _exitCode = e.ApplicationExitCode;                
+            }
+            finally
+            {
+                _cts?.Cancel();
+                _cts = null;
+                _isShuttingDown = false;
+            }
+        }
+        
+        
+        public int Start(string[] args)
+        {
+            Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
+            _cts = new CancellationTokenSource();
+            MainWindow?.Show();
+            _app.Run(_cts.Token);
+            Environment.ExitCode = _exitCode;
+            return _exitCode;
+        }
+    }
+}
+
+namespace Avalonia
+{
+    public static class ClassicDesktopStyleApplicationLifetimeExtensions
+    {
+        public static int StartWithClassicDesktopLifetime<T>(
+            this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
+            where T : AppBuilderBase<T>, new()
+        {
+            var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
+            builder.Instance.ApplicationLifetime = lifetime;
+            builder.SetupWithoutStarting();
+            return lifetime.Start(args);
+        }
+    }
+}

+ 8 - 3
src/Avalonia.Controls/ExitEventArgs.cs → src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs

@@ -3,13 +3,18 @@
 
 using System;
 
-namespace Avalonia.Controls
+namespace Avalonia.Controls.ApplicationLifetimes
 {
     /// <summary>
-    /// Contains the arguments for the <see cref="IApplicationLifecycle.Exit"/> event.
+    /// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Exit"/> event.
     /// </summary>
-    public class ExitEventArgs : EventArgs
+    public class ControlledApplicationLifetimeExitEventArgs : EventArgs
     {
+        public ControlledApplicationLifetimeExitEventArgs(int applicationExitCode)
+        {
+            ApplicationExitCode = applicationExitCode;
+        }
+
         /// <summary>
         /// Gets or sets the exit code that an application returns to the operating system when the application exits.
         /// </summary>

+ 7 - 0
src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs

@@ -0,0 +1,7 @@
+namespace Avalonia.Controls.ApplicationLifetimes
+{
+    public interface IApplicationLifetime
+    {
+        
+    }
+}

+ 28 - 0
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@@ -0,0 +1,28 @@
+using System;
+
+namespace Avalonia.Controls.ApplicationLifetimes
+{
+    /// <summary>
+    /// Controls application lifetime in classic desktop style
+    /// </summary>
+    public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime
+    {
+        /// <summary>
+        /// Gets or sets the <see cref="ShutdownMode"/>. This property indicates whether the application is shutdown explicitly or implicitly. 
+        /// If <see cref="ShutdownMode"/> is set to OnExplicitShutdown the application is only closes if Shutdown is called.
+        /// The default is OnLastWindowClose
+        /// </summary>
+        /// <value>
+        /// The shutdown mode.
+        /// </value>
+        ShutdownMode ShutdownMode { get; set; }
+
+        /// <summary>
+        /// Gets or sets the main window of the application.
+        /// </summary>
+        /// <value>
+        /// The main window.
+        /// </value>
+        Window MainWindow { get; set; }
+    }
+}

+ 5 - 8
src/Avalonia.Controls/IApplicationLifecycle.cs → src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs

@@ -1,22 +1,19 @@
 using System;
 
-namespace Avalonia.Controls
+namespace Avalonia.Controls.ApplicationLifetimes
 {
-    /// <summary>
-    /// Sends events about the application lifecycle.
-    /// </summary>
-    public interface IApplicationLifecycle
+    public interface IControlledApplicationLifetime : IApplicationLifetime
     {
         /// <summary>
         /// Sent when the application is starting up.
         /// </summary>
-        event EventHandler<StartupEventArgs> Startup;
+        event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
 
         /// <summary>
         /// Sent when the application is exiting.
         /// </summary>
-        event EventHandler<ExitEventArgs> Exit;
-
+        event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
+        
         /// <summary>
         /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits.
         /// </summary>

+ 7 - 0
src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.cs

@@ -0,0 +1,7 @@
+namespace Avalonia.Controls.ApplicationLifetimes
+{
+    public interface ISingleViewLifetime : IApplicationLifetime
+    {
+        Control MainView { get; set; }
+    }
+}

+ 22 - 0
src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs

@@ -0,0 +1,22 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Controls.ApplicationLifetimes
+{
+    /// <summary>
+    /// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Startup"/> event.
+    /// </summary>
+    public class ControlledApplicationLifetimeStartupEventArgs : EventArgs
+    {
+        public ControlledApplicationLifetimeStartupEventArgs(IEnumerable<string> args)
+        {
+            Args = args?.ToArray() ?? Array.Empty<string>();
+        }
+
+        public string[] Args { get;  }
+    }
+}

+ 67 - 0
src/Avalonia.Controls/DesktopApplicationExtensions.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Threading;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls
+{
+    public static class DesktopApplicationExtensions
+    {
+        [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")]
+        public static void Run(this Application app) => throw new NotSupportedException();
+
+        /// <summary>
+        /// On desktop-style platforms runs the application's main loop until closable is closed
+        /// </summary>
+        /// <remarks>
+        /// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details
+        /// </remarks>
+        public static void Run(this Application app, ICloseable closable)
+        {
+            var cts = new CancellationTokenSource();
+            closable.Closed += (s, e) => cts.Cancel();
+
+            app.Run(cts.Token);
+        }
+
+        /// <summary>
+        /// On desktop-style platforms runs the application's main loop until main window is closed
+        /// </summary>
+        /// <remarks>
+        /// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details
+        /// </remarks>
+        public static void Run(this Application app, Window mainWindow)
+        {
+            if (mainWindow == null)
+            {
+                throw new ArgumentNullException(nameof(mainWindow));
+            }
+            var cts = new CancellationTokenSource();
+            mainWindow.Closed += (_, __) => cts.Cancel();
+            if (!mainWindow.IsVisible)
+            {
+                mainWindow.Show();
+            }
+            app.Run(cts.Token);
+        }
+        
+        /// <summary>
+        /// On desktop-style platforms runs the application's main loop with custom CancellationToken
+        /// without setting a lifetime.
+        /// </summary>
+        /// <param name="token">The token to track.</param>
+        public static void Run(this Application app, CancellationToken token)
+        {
+            Dispatcher.UIThread.MainLoop(token);
+        }
+
+        public static void RunWithMainWindow<TWindow>(this Application app)
+            where TWindow : Avalonia.Controls.Window, new()
+        {
+            var window = new TWindow();
+            window.Show();
+            app.Run(window);
+        }
+    }
+}

+ 0 - 36
src/Avalonia.Controls/StartupEventArgs.cs

@@ -1,36 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Avalonia.Controls
-{
-    /// <summary>
-    /// Contains the arguments for the <see cref="IApplicationLifecycle.Startup"/> event.
-    /// </summary>
-    public class StartupEventArgs : EventArgs
-    {
-        private string[] _args;
-
-        /// <summary>
-        /// Gets the command line arguments that were passed to the application.
-        /// </summary>
-        public IReadOnlyList<string> Args => _args ?? (_args = GetArgs());
-
-        private static string[] GetArgs()
-        {
-            try
-            {
-                var args = Environment.GetCommandLineArgs();
-
-                return args.Length > 1 ? args.Skip(1).ToArray() : new string[0];
-            }
-            catch (NotSupportedException)
-            {
-                return new string[0];
-            }
-        }
-    }
-}

+ 0 - 20
src/Avalonia.Controls/TopLevel.cs

@@ -51,7 +51,6 @@ namespace Avalonia.Controls
         private readonly IInputManager _inputManager;
         private readonly IAccessKeyHandler _accessKeyHandler;
         private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
-        private readonly IApplicationLifecycle _applicationLifecycle;
         private readonly IPlatformRenderInterface _renderInterface;
         private Size _clientSize;
         private ILayoutManager _layoutManager;
@@ -96,7 +95,6 @@ namespace Avalonia.Controls
             _accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
             _inputManager = TryGetService<IInputManager>(dependencyResolver);
             _keyboardNavigationHandler = TryGetService<IKeyboardNavigationHandler>(dependencyResolver);
-            _applicationLifecycle = TryGetService<IApplicationLifecycle>(dependencyResolver);
             _renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
 
             Renderer = impl.CreateRenderer(this);
@@ -125,11 +123,6 @@ namespace Avalonia.Controls
                     x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
                 .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
 
-            if (_applicationLifecycle != null)
-            {
-                _applicationLifecycle.Exit += OnApplicationExiting;
-            }
-
             if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources)
             {
                 WeakSubscriptionManager.Subscribe(
@@ -281,7 +274,6 @@ namespace Avalonia.Controls
             Closed?.Invoke(this, EventArgs.Empty);
             Renderer?.Dispose();
             Renderer = null;
-            _applicationLifecycle.Exit -= OnApplicationExiting;
         }
 
         /// <summary>
@@ -348,18 +340,6 @@ namespace Avalonia.Controls
             return result;
         }
 
-        private void OnApplicationExiting(object sender, EventArgs args)
-        {
-            HandleApplicationExiting();
-        }
-
-        /// <summary>
-        /// Handles the application exiting, either from the last window closing, or a call to <see cref="IApplicationLifecycle.Exit"/>.
-        /// </summary>
-        protected virtual void HandleApplicationExiting()
-        {
-        }
-
         /// <summary>
         /// Handles input from <see cref="ITopLevelImpl.Input"/>.
         /// </summary>

+ 0 - 19
src/Avalonia.Controls/Window.cs

@@ -277,12 +277,6 @@ namespace Avalonia.Controls
             Close(false);
         }
 
-        protected override void HandleApplicationExiting()
-        {
-            base.HandleApplicationExiting();
-            Close(true);
-        }
-
         /// <summary>
         /// Closes a dialog window with the specified result.
         /// </summary>
@@ -585,16 +579,3 @@ namespace Avalonia.Controls
         protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
     }
 }
-
-namespace Avalonia
-{
-    public static class WindowApplicationExtensions
-    {
-        public static void RunWithMainWindow<TWindow>(this Application app) where TWindow : Avalonia.Controls.Window, new()
-        {
-            var window = new TWindow();
-            window.Show();
-            app.Run(window);
-        }
-    }
-}

+ 5 - 28
src/Avalonia.Controls/WindowCollection.cs

@@ -1,6 +1,7 @@
 // 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;
 using System.Collections.Generic;
 
@@ -12,6 +13,7 @@ namespace Avalonia
     {
         private readonly Application _application;
         private readonly List<Window> _windows = new List<Window>();
+        public event Action<Window> OnWindowClosed;
 
         public WindowCollection(Application application)
         {
@@ -92,7 +94,7 @@ namespace Avalonia
         /// <summary>
         /// Closes all windows and removes them from the underlying collection.
         /// </summary>
-        internal void Clear()
+        public void CloseAll()
         {
             while (_windows.Count > 0)
             {
@@ -102,33 +104,8 @@ namespace Avalonia
 
         private void OnRemoveWindow(Window window)
         {
-            if (window == null)
-            {
-                return;
-            }
-
-            if (_application.IsShuttingDown)
-            {
-                return;
-            }
-
-            switch (_application.ShutdownMode)
-            {
-                case ShutdownMode.OnLastWindowClose:
-                    if (Count == 0)
-                    {
-                        _application.Shutdown();
-                    }
-
-                    break;
-                case ShutdownMode.OnMainWindowClose:
-                    if (window == _application.MainWindow)
-                    {
-                        _application.Shutdown();
-                    }
-
-                    break;                   
-            }
+            if (window != null)
+                OnWindowClosed?.Invoke(window);
         }
     }
 }

+ 2 - 10
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Net;
 using System.Reflection;
+using System.Threading;
 using System.Xml;
 using Avalonia.Controls;
 using Avalonia.Input;
@@ -122,15 +123,6 @@ namespace Avalonia.DesignerSupport.Remote
         }
 
         private const string BuilderMethodName = "BuildAvaloniaApp";
-
-        class NeverClose : ICloseable
-        {
-            public event EventHandler Closed
-            {
-                add {}
-                remove {}
-            }
-        }
         
         public static void Main(string[] cmdline)
         {
@@ -155,7 +147,7 @@ namespace Avalonia.DesignerSupport.Remote
             transport.OnException += (t, e) => Die(e.ToString());
             Log("Sending StartDesignerSessionMessage");
             transport.Send(new StartDesignerSessionMessage {SessionId = args.SessionId});
-            app.Run(new NeverClose());
+            Dispatcher.UIThread.MainLoop(CancellationToken.None);
         }
 
 

+ 1 - 1
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -18,7 +18,7 @@ namespace Avalonia.LinuxFramebuffer
         {
             _fb = fb;
             Invalidate(default(Rect));
-            var mice = new Mice(ClientSize.Width, ClientSize.Height);
+            var mice = new Mice(this, ClientSize.Width, ClientSize.Height);
             mice.Start();
             mice.Event += e => Input?.Invoke(e);
         }

+ 55 - 19
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -2,6 +2,7 @@
 using System.Diagnostics;
 using System.Threading;
 using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Embedding;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
@@ -21,7 +22,6 @@ namespace Avalonia.LinuxFramebuffer
         private static readonly Stopwatch St = Stopwatch.StartNew();
         internal static uint Timestamp => (uint)St.ElapsedTicks;
         public static InternalPlatformThreadingInterface Threading;
-        public static FramebufferToplevelImpl TopLevel;
         LinuxFramebufferPlatform(string fbdev = null)
         {
             _fb = new LinuxFramebuffer(fbdev);
@@ -41,37 +41,73 @@ namespace Avalonia.LinuxFramebuffer
                 .Bind<IRenderTimer>().ToConstant(Threading);
         }
 
-        internal static TopLevel Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new()
+        internal static LinuxFramebufferLifetime Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new()
         {
             var platform = new LinuxFramebufferPlatform(fbdev);
-            builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev")
-                .SetupWithoutStarting();
-            var tl = new EmbeddableControlRoot(TopLevel = new FramebufferToplevelImpl(platform._fb));
-            tl.Prepare();
-            return tl;
+            builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev");
+            return new LinuxFramebufferLifetime(platform._fb);
         }
     }
-}
 
-public static class LinuxFramebufferPlatformExtensions
-{
-    class TokenClosable : ICloseable
+    class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewLifetime
     {
-        public event EventHandler Closed;
+        private readonly LinuxFramebuffer _fb;
+        private TopLevel _topLevel;
+        private readonly CancellationTokenSource _cts = new CancellationTokenSource();
+        public CancellationToken Token => _cts.Token;
 
-        public TokenClosable(CancellationToken token)
+        public LinuxFramebufferLifetime(LinuxFramebuffer fb)
+        {
+            _fb = fb;
+        }
+        
+        public Control MainView
         {
-            token.Register(() => Dispatcher.UIThread.Post(() => Closed?.Invoke(this, new EventArgs())));
+            get => (Control)_topLevel?.Content;
+            set
+            {
+                if (_topLevel == null)
+                {
+
+                    var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb));
+                    tl.Prepare();
+                    _topLevel = tl;
+                }
+                _topLevel.Content = value;
+            }
+        }
+
+        public int ExitCode { get; private set; }
+        public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
+        public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
+
+        public void Start(string[] args)
+        {
+            Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
+        }
+        
+        public void Shutdown(int exitCode)
+        {
+            ExitCode = exitCode;
+            var e = new ControlledApplicationLifetimeExitEventArgs(exitCode);
+            Exit?.Invoke(this, e);
+            ExitCode = e.ApplicationExitCode;
+            _cts.Cancel();
         }
     }
+}
 
-    public static void InitializeWithLinuxFramebuffer<T>(this T builder, Action<TopLevel> setup,
-        CancellationToken stop = default(CancellationToken), string fbdev = null)
+public static class LinuxFramebufferPlatformExtensions
+{
+    public static int StartLinuxFramebuffer<T>(this T builder, string[] args, string fbdev = null)
         where T : AppBuilderBase<T>, new()
     {
-        setup(LinuxFramebufferPlatform.Initialize(builder, fbdev));
-        builder.BeforeStartCallback(builder);
-        builder.Instance.Run(new TokenClosable(stop));
+        var lifetime = LinuxFramebufferPlatform.Initialize(builder, fbdev);
+        builder.Instance.ApplicationLifetime = lifetime;
+        builder.SetupWithoutStarting();
+        lifetime.Start(args);
+        builder.Instance.Run(lifetime.Token);
+        return lifetime.ExitCode;
     }
 }
 

+ 7 - 5
src/Linux/Avalonia.LinuxFramebuffer/Mice.cs

@@ -7,8 +7,9 @@ using Avalonia.Platform;
 
 namespace Avalonia.LinuxFramebuffer
 {
-    public unsafe class Mice
+    unsafe class Mice
     {
+        private readonly FramebufferToplevelImpl _topLevel;
         private readonly double _width;
         private readonly double _height;
         private double _x;
@@ -16,8 +17,9 @@ namespace Avalonia.LinuxFramebuffer
 
         public event Action<RawInputEventArgs> Event;
 
-        public Mice(double width, double height)
+        public Mice(FramebufferToplevelImpl topLevel, double width, double height)
         {
+            _topLevel = topLevel;
             _width = width;
             _height = height;
         }
@@ -78,7 +80,7 @@ namespace Avalonia.LinuxFramebuffer
                     return;
                 Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
                     LinuxFramebufferPlatform.Timestamp,
-                    LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
+                    _topLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
                     InputModifiers.None));
             }
             if (ev.type ==(int) EvType.EV_ABS)
@@ -91,7 +93,7 @@ namespace Avalonia.LinuxFramebuffer
                     return;
                 Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
                     LinuxFramebufferPlatform.Timestamp,
-                    LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
+                    _topLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
                     InputModifiers.None));
             }
             if (ev.type == (short) EvType.EV_KEY)
@@ -108,7 +110,7 @@ namespace Avalonia.LinuxFramebuffer
 
                 Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
                     LinuxFramebufferPlatform.Timestamp,
-                    LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
+                    _topLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
             }
         }
     }

+ 0 - 120
tests/Avalonia.Controls.UnitTests/ApplicationTests.cs

@@ -11,113 +11,6 @@ namespace Avalonia.Controls.UnitTests
 {
     public class ApplicationTests
     {
-        [Fact]
-        public void Should_Exit_After_MainWindow_Closed()
-        {
-            using (UnitTestApplication.Start(TestServices.StyledWindow))
-            {
-                Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
-
-                var hasExit = false;
-
-                Application.Current.Exit += (s, e) => hasExit = true;
-
-                var mainWindow = new Window();
-
-                mainWindow.Show();
-
-                Application.Current.MainWindow = mainWindow;
-
-                var window = new Window();
-
-                window.Show();
-
-                mainWindow.Close();
-
-                Assert.True(hasExit);
-            }
-        }
-
-        [Fact]
-        public void Should_Exit_After_Last_Window_Closed()
-        {
-            using (UnitTestApplication.Start(TestServices.StyledWindow))
-            {
-                Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
-
-                var hasExit = false;
-
-                Application.Current.Exit += (s, e) => hasExit = true;
-
-                var windowA = new Window();
-
-                windowA.Show();
-
-                var windowB = new Window();
-
-                windowB.Show();
-
-                windowA.Close();
-
-                Assert.False(hasExit);
-
-                windowB.Close();
-
-                Assert.True(hasExit);
-            }
-        }
-
-        [Fact]
-        public void Should_Only_Exit_On_Explicit_Exit()
-        {
-            using (UnitTestApplication.Start(TestServices.StyledWindow))
-            {
-                Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
-
-                var hasExit = false;
-
-                Application.Current.Exit += (s, e) => hasExit = true;
-
-                var windowA = new Window();
-
-                windowA.Show();
-
-                var windowB = new Window();
-
-                windowB.Show();
-
-                windowA.Close();
-
-                Assert.False(hasExit);
-
-                windowB.Close();
-
-                Assert.False(hasExit);
-
-                Application.Current.Shutdown();
-
-                Assert.True(hasExit);
-            }
-        }
-
-        [Fact]
-        public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call()
-        {
-            using (UnitTestApplication.Start(TestServices.StyledWindow))
-            {
-                var windows = new List<Window> { new Window(), new Window(), new Window(), new Window() };
-
-                foreach (var window in windows)
-                {
-                    window.Show();
-                }
-
-                Application.Current.Shutdown();
-
-                Assert.Empty(Application.Current.Windows);
-            }
-        }
-
         [Fact]
         public void Throws_ArgumentNullException_On_Run_If_MainWindow_Is_Null()
         {
@@ -142,18 +35,5 @@ 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);
-            }
-        }
     }
 }

+ 1 - 3
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@@ -59,9 +59,7 @@ namespace Avalonia.Controls.UnitTests
                 };
 
                 var window = new Window { Content = target };
-
-                Avalonia.Application.Current.MainWindow = window;
-
+                
                 _mouse.Click(target, MouseButton.Right);
 
                 Assert.True(sut.IsOpen);

+ 139 - 0
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+    
+    public class DesktopStyleApplicationLifetimeTests
+    {
+        [Fact]
+        public void Should_Set_ExitCode_After_Shutdown()
+        {
+            using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
+            {
+                var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
+                Dispatcher.UIThread.InvokeAsync(() => lifetime.Shutdown(1337));
+                lifetime.Shutdown(1337);
+
+                var exitCode = lifetime.Start(Array.Empty<string>());
+
+                Assert.Equal(1337, exitCode);
+            }
+        }
+        
+        
+        [Fact]
+        public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var windows = new List<Window> { new Window(), new Window(), new Window(), new Window() };
+
+                foreach (var window in windows)
+                {
+                    window.Show();
+                }
+                new ClassicDesktopStyleApplicationLifetime(Application.Current).Shutdown();
+
+                Assert.Empty(Application.Current.Windows);
+            }
+        }
+        
+        [Fact]
+        public void Should_Only_Exit_On_Explicit_Exit()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
+                lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown;
+
+                var hasExit = false;
+
+                lifetime.Exit += (s, e) => hasExit = true;
+
+                var windowA = new Window();
+
+                windowA.Show();
+
+                var windowB = new Window();
+
+                windowB.Show();
+
+                windowA.Close();
+
+                Assert.False(hasExit);
+
+                windowB.Close();
+
+                Assert.False(hasExit);
+
+                lifetime.Shutdown();
+
+                Assert.True(hasExit);
+            }
+        }
+        
+        [Fact]
+        public void Should_Exit_After_MainWindow_Closed()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var lifetime =  new ClassicDesktopStyleApplicationLifetime(Application.Current);
+                lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
+
+                var hasExit = false;
+
+                lifetime.Exit += (s, e) => hasExit = true;
+
+                var mainWindow = new Window();
+
+                mainWindow.Show();
+
+                lifetime.MainWindow = mainWindow;
+
+                var window = new Window();
+
+                window.Show();
+
+                mainWindow.Close();
+
+                Assert.True(hasExit);
+            }
+        }
+
+        [Fact]
+        public void Should_Exit_After_Last_Window_Closed()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
+                lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose;
+
+                var hasExit = false;
+
+                lifetime.Exit += (s, e) => hasExit = true;
+
+                var windowA = new Window();
+
+                windowA.Show();
+
+                var windowB = new Window();
+
+                windowB.Show();
+
+                windowA.Close();
+
+                Assert.False(hasExit);
+
+                windowB.Close();
+
+                Assert.True(hasExit);
+            }
+        }
+    }
+    
+}

+ 0 - 19
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@@ -207,19 +207,6 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
-        [Fact]
-        public void Exiting_Application_Notifies_Top_Level()
-        {
-            using (UnitTestApplication.Start(TestServices.StyledWindow))
-            {
-                var impl = new Mock<ITopLevelImpl>();
-                impl.SetupAllProperties();
-                var target = new TestTopLevel(impl.Object);
-                UnitTestApplication.Current.Shutdown();
-                Assert.True(target.IsClosed);
-            }
-        }
-
         [Fact]
         public void Adding_Resource_To_Application_Should_Raise_ResourcesChanged()
         {
@@ -259,12 +246,6 @@ namespace Avalonia.Controls.UnitTests
             }
 
             protected override ILayoutManager CreateLayoutManager() => _layoutManager;
-
-            protected override void HandleApplicationExiting()
-            {
-                base.HandleApplicationExiting();
-                IsClosed = true;
-            }
         }
     }
 }

+ 0 - 6
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@@ -276,12 +276,6 @@ namespace Avalonia.Controls.UnitTests
                 : base(impl)
             {
             }
-
-            protected override void HandleApplicationExiting()
-            {
-                base.HandleApplicationExiting();
-                IsClosed = true;
-            }
         }
     }
 }

+ 1 - 1
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -425,7 +425,7 @@ namespace Avalonia.Controls.UnitTests
         {
             // HACK: We really need a decent way to have "statics" that can be scoped to
             // AvaloniaLocator scopes.
-            Application.Current.Windows.Clear();
+            Application.Current.Windows.CloseAll();
         }
     }
 }

+ 1 - 2
tests/Avalonia.UnitTests/UnitTestApplication.cs

@@ -66,8 +66,7 @@ namespace Avalonia.UnitTests
                 .Bind<IStandardCursorFactory>().ToConstant(Services.StandardCursorFactory)
                 .Bind<IStyler>().ToConstant(Services.Styler)
                 .Bind<IWindowingPlatform>().ToConstant(Services.WindowingPlatform)
-                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
-                .Bind<IApplicationLifecycle>().ToConstant(this);
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
             var styles = Services.Theme?.Invoke();
 
             if (styles != null)