diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 4027c5cd63..40321496c0 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/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().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()); - } - + /// /// This method is needed for IDE previewer infrastructure /// diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index d862749132..07c42c60c4 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/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 ISingleViewApplicationLifetime singleViewLifetime) + singleViewLifetime.MainView = new MainView(); + + base.OnFrameworkInitializationCompleted(); + } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 419064b051..0f0b55e9e8 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -57,10 +57,6 @@ namespace Avalonia.Controls /// public Action AfterSetupCallback { get; private set; } = builder => { }; - /// - /// Gets or sets a method to call before Start is called on the . - /// - public Action BeforeStartCallback { get; private set; } = builder => { }; protected AppBuilderBase(IRuntimePlatform platform, Action platformServices) { @@ -95,17 +91,6 @@ namespace Avalonia.Controls protected TAppBuilder Self => (TAppBuilder)this; - /// - /// Registers a callback to call before Start is called on the . - /// - /// The callback. - /// An instance. - public TAppBuilder BeforeStarting(Action callback) - { - BeforeStartCallback = (Action)Delegate.Combine(BeforeStartCallback, callback); - return Self; - } - public TAppBuilder AfterSetup(Action callback) { AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); @@ -117,48 +102,28 @@ namespace Avalonia.Controls /// /// The window type. /// A delegate that will be called to create a data context for the window (optional). + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] public void Start(Func dataContextProvider = null) where TMainWindow : Window, new() { - Setup(); - BeforeStartCallback(Self); - var window = new TMainWindow(); if (dataContextProvider != null) window.DataContext = dataContextProvider(); Instance.Run(window); } - /// - /// Starts the application with the provided instance of . - /// - /// The window type. - /// Instance of type TMainWindow to use when starting the app - /// A delegate that will be called to create a data context for the window (optional). - public void Start(TMainWindow mainWindow, Func dataContextProvider = null) - where TMainWindow : Window - { - Setup(); - BeforeStartCallback(Self); - - if (dataContextProvider != null) - mainWindow.DataContext = dataContextProvider(); - Instance.Run(mainWindow); - } - 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(); - BeforeStartCallback(Self); Instance.Run(); } public void Start(AppMainDelegate main, string[] args) { Setup(); - BeforeStartCallback(Self); main(Instance, args); } @@ -224,17 +189,6 @@ namespace Avalonia.Controls public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); - /// - /// Sets the shutdown mode of the application. - /// - /// The shutdown mode. - /// - public TAppBuilder SetShutdownMode(ShutdownMode shutdownMode) - { - Instance.ShutdownMode = shutdownMode; - return Self; - } - protected virtual bool CheckSetup => true; private void SetupAvaloniaModules() @@ -313,6 +267,7 @@ namespace Avalonia.Controls Instance.RegisterServices(); Instance.Initialize(); AfterSetupCallback(Self); + Instance.OnFrameworkInitializationCompleted(); } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 9f9fe5eae1..382106de65 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/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. /// - public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode + public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. @@ -43,22 +44,6 @@ 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. - /// - public Application() - { - Windows = new WindowCollection(this); - } - - /// - public event EventHandler Startup; - - /// - public event EventHandler Exit; /// public event EventHandler ResourcesChanged; @@ -166,198 +151,21 @@ namespace Avalonia /// IResourceNode IResourceNode.ResourceParent => null; - - /// - /// 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 ShutdownMode ShutdownMode { get; set; } - - /// - /// Gets or sets the main window of the application. - /// - /// - /// The main window. - /// - public Window MainWindow { get; set; } - - /// - /// Gets the open windows of the application. - /// - /// - /// The windows. - /// - public WindowCollection Windows { get; } - + /// - /// 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: + /// - + /// - + /// - /// - /// - /// true if this instance is shutting down; otherwise, false. - /// - internal bool IsShuttingDown { get; private set; } + public IApplicationLifetime ApplicationLifetime { get; set; } /// /// Initializes the application by loading XAML etc. /// public virtual void Initialize() { } - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// - /// The application's exit code that is returned to the operating system on termination. - public int Run() - { - return Run(new CancellationTokenSource()); - } - - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// This also returns when is closed. - /// - /// The closable to track. - /// The application's exit code that is returned to the operating system on termination. - public int Run(ICloseable closable) - { - closable.Closed += (s, e) => _mainLoopCancellationTokenSource?.Cancel(); - - return Run(new CancellationTokenSource()); - } - - /// - /// Runs the application's main loop. - /// - /// - /// This will return when the condition is met - /// or was called. - /// - /// The window that is used as - /// when the isn't already set. - /// The application's exit code that is returned to the operating system on termination. - public int Run(Window mainWindow) - { - if (mainWindow == null) - { - throw new ArgumentNullException(nameof(mainWindow)); - } - - if (MainWindow == null) - { - if (!mainWindow.IsVisible) - { - mainWindow.Show(); - } - - MainWindow = mainWindow; - } - - return Run(new CancellationTokenSource()); - } - /// - /// 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 returned to the operating system on termination. - /// The token to track. - 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; - } - - /// - /// 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(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; - } - } - /// bool IResourceProvider.TryGetResource(object key, out object value) { @@ -383,7 +191,6 @@ namespace Avalonia .Bind().ToConstant(InputManager) .Bind().ToTransient() .Bind().ToConstant(_styler) - .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance) .Bind().ToTransient(); @@ -394,6 +201,11 @@ namespace Avalonia .GetService()?.Add(clock); } + public virtual void OnFrameworkInitializationCompleted() + { + + } + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { ResourcesChanged?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs new file mode 100644 index 0000000000..abca7a64ee --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Interactivity; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable + { + private readonly Application _app; + private int _exitCode; + private CancellationTokenSource _cts; + private bool _isShuttingDown; + private HashSet _windows = new HashSet(); + + private static ClassicDesktopStyleApplicationLifetime _activeLifetime; + static ClassicDesktopStyleApplicationLifetime() + { + Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); + Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); + } + + private static void WindowClosedEvent(object sender, RoutedEventArgs e) + { + _activeLifetime?._windows.Remove((Window)sender); + _activeLifetime?.HandleWindowClosed((Window)sender); + } + + private static void OnWindowOpened(object sender, RoutedEventArgs e) + { + _activeLifetime?._windows.Add((Window)sender); + } + + public ClassicDesktopStyleApplicationLifetime(Application app) + { + if (_activeLifetime != null) + throw new InvalidOperationException( + "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); + _app = app; + _activeLifetime = this; + } + + /// + public event EventHandler Startup; + /// + public event EventHandler Exit; + + /// + public ShutdownMode ShutdownMode { get; set; } + + /// + public Window MainWindow { get; set; } + + public IReadOnlyList Windows => _windows.ToList(); + + private void HandleWindowClosed(Window window) + { + if (window == null) + return; + + if (_isShuttingDown) + return; + + if (ShutdownMode == ShutdownMode.OnLastWindowClose && _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 + { + foreach (var w in Windows) + w.Close(); + 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; + } + + public void Dispose() + { + if (_activeLifetime == this) + _activeLifetime = null; + } + } +} + +namespace Avalonia +{ + public static class ClassicDesktopStyleApplicationLifetimeExtensions + { + public static int StartWithClassicDesktopLifetime( + this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) + where T : AppBuilderBase, new() + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; + builder.Instance.ApplicationLifetime = lifetime; + builder.SetupWithoutStarting(); + return lifetime.Start(args); + } + } +} diff --git a/src/Avalonia.Controls/ExitEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs similarity index 54% rename from src/Avalonia.Controls/ExitEventArgs.cs rename to src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs index c99f7fe047..d4c3b27f7a 100644 --- a/src/Avalonia.Controls/ExitEventArgs.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs @@ -3,13 +3,18 @@ using System; -namespace Avalonia.Controls +namespace Avalonia.Controls.ApplicationLifetimes { /// - /// Contains the arguments for the event. + /// Contains the arguments for the event. /// - public class ExitEventArgs : EventArgs + public class ControlledApplicationLifetimeExitEventArgs : EventArgs { + public ControlledApplicationLifetimeExitEventArgs(int applicationExitCode) + { + ApplicationExitCode = applicationExitCode; + } + /// /// Gets or sets the exit code that an application returns to the operating system when the application exits. /// diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs new file mode 100644 index 0000000000..9860d0cb38 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface IApplicationLifetime + { + + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs new file mode 100644 index 0000000000..a1006d907b --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + /// + /// Controls application lifetime in classic desktop style + /// + public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime + { + /// + /// 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. + /// + ShutdownMode ShutdownMode { get; set; } + + /// + /// Gets or sets the main window of the application. + /// + /// + /// The main window. + /// + Window MainWindow { get; set; } + + IReadOnlyList Windows { get; } + } +} diff --git a/src/Avalonia.Controls/IApplicationLifecycle.cs b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs similarity index 65% rename from src/Avalonia.Controls/IApplicationLifecycle.cs rename to src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs index a3c6599b20..3f61aeb536 100644 --- a/src/Avalonia.Controls/IApplicationLifecycle.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs @@ -1,22 +1,19 @@ using System; -namespace Avalonia.Controls +namespace Avalonia.Controls.ApplicationLifetimes { - /// - /// Sends events about the application lifecycle. - /// - public interface IApplicationLifecycle + public interface IControlledApplicationLifetime : IApplicationLifetime { /// /// 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; + /// /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. /// diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs new file mode 100644 index 0000000000..eb451f51af --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface ISingleViewApplicationLifetime : IApplicationLifetime + { + Control MainView { get; set; } + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs new file mode 100644 index 0000000000..4c08712707 --- /dev/null +++ b/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 +{ + /// + /// Contains the arguments for the event. + /// + public class ControlledApplicationLifetimeStartupEventArgs : EventArgs + { + public ControlledApplicationLifetimeStartupEventArgs(IEnumerable args) + { + Args = args?.ToArray() ?? Array.Empty(); + } + + public string[] Args { get; } + } +} diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs new file mode 100644 index 0000000000..9b81bc94d1 --- /dev/null +++ b/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(); + + /// + /// On desktop-style platforms runs the application's main loop until closable is closed + /// + /// + /// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details + /// + public static void Run(this Application app, ICloseable closable) + { + var cts = new CancellationTokenSource(); + closable.Closed += (s, e) => cts.Cancel(); + + app.Run(cts.Token); + } + + /// + /// On desktop-style platforms runs the application's main loop until main window is closed + /// + /// + /// Consider using StartWithDesktopStyleLifetime instead, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details + /// + 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); + } + + /// + /// On desktop-style platforms runs the application's main loop with custom CancellationToken + /// without setting a lifetime. + /// + /// The token to track. + public static void Run(this Application app, CancellationToken token) + { + Dispatcher.UIThread.MainLoop(token); + } + + public static void RunWithMainWindow(this Application app) + where TWindow : Avalonia.Controls.Window, new() + { + var window = new TWindow(); + window.Show(); + app.Run(window); + } + } +} diff --git a/src/Avalonia.Controls/StartupEventArgs.cs b/src/Avalonia.Controls/StartupEventArgs.cs deleted file mode 100644 index 0e12f5c01a..0000000000 --- a/src/Avalonia.Controls/StartupEventArgs.cs +++ /dev/null @@ -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 -{ - /// - /// Contains the arguments for the event. - /// - public class StartupEventArgs : EventArgs - { - private string[] _args; - - /// - /// Gets the command line arguments that were passed to the application. - /// - public IReadOnlyList Args => _args ?? (_args = GetArgs()); - - private static string[] GetArgs() - { - try - { - var args = Environment.GetCommandLineArgs(); - - return args.Length > 1 ? args.Skip(1).ToArray() : new string[0]; - } - catch (NotSupportedException) - { - return new string[0]; - } - } - } -} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 21bd0e4e57..87100ceeb0 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/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(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); - _applicationLifecycle = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); @@ -125,11 +123,6 @@ namespace Avalonia.Controls x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) .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; } /// @@ -348,18 +340,6 @@ namespace Avalonia.Controls return result; } - private void OnApplicationExiting(object sender, EventArgs args) - { - HandleApplicationExiting(); - } - - /// - /// Handles the application exiting, either from the last window closing, or a call to . - /// - protected virtual void HandleApplicationExiting() - { - } - /// /// Handles input from . /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 2265e89af3..5c117f508b 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using System.ComponentModel; +using Avalonia.Interactivity; namespace Avalonia.Controls { @@ -97,6 +98,20 @@ namespace Avalonia.Controls public static readonly StyledProperty CanResizeProperty = AvaloniaProperty.Register(nameof(CanResize), true); + /// + /// Routed event that can be used for global tracking of window destruction + /// + public static readonly RoutedEvent WindowClosedEvent = + RoutedEvent.Register("WindowClosed", RoutingStrategies.Direct); + + /// + /// Routed event that can be used for global tracking of opening windows + /// + public static readonly RoutedEvent WindowOpenedEvent = + RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct); + + + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -249,26 +264,6 @@ namespace Avalonia.Controls /// public event EventHandler Closing; - private static void AddWindow(Window window) - { - if (Application.Current == null) - { - return; - } - - Application.Current.Windows.Add(window); - } - - private static void RemoveWindow(Window window) - { - if (Application.Current == null) - { - return; - } - - Application.Current.Windows.Remove(window); - } - /// /// Closes the window. /// @@ -277,12 +272,6 @@ namespace Avalonia.Controls Close(false); } - protected override void HandleApplicationExiting() - { - base.HandleApplicationExiting(); - Close(true); - } - /// /// Closes a dialog window with the specified result. /// @@ -382,7 +371,7 @@ namespace Avalonia.Controls return; } - AddWindow(this); + this.RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); IsVisible = true; @@ -444,7 +433,7 @@ namespace Avalonia.Controls throw new InvalidOperationException("The window is already being shown."); } - AddWindow(this); + RaiseEvent(new RoutedEventArgs(WindowOpenedEvent)); EnsureInitialized(); IsVisible = true; @@ -557,7 +546,7 @@ namespace Avalonia.Controls protected override void HandleClosed() { - RemoveWindow(this); + RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); base.HandleClosed(); } @@ -585,16 +574,3 @@ namespace Avalonia.Controls protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); } } - -namespace Avalonia -{ - public static class WindowApplicationExtensions - { - public static void RunWithMainWindow(this Application app) where TWindow : Avalonia.Controls.Window, new() - { - var window = new TWindow(); - window.Show(); - app.Run(window); - } - } -} diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs deleted file mode 100644 index 328bb9f147..0000000000 --- a/src/Avalonia.Controls/WindowCollection.cs +++ /dev/null @@ -1,134 +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.Collections; -using System.Collections.Generic; - -using Avalonia.Controls; - -namespace Avalonia -{ - public class WindowCollection : IReadOnlyList - { - private readonly Application _application; - private readonly List _windows = new List(); - - public WindowCollection(Application application) - { - _application = application; - } - - /// - /// - /// Gets the number of elements in the collection. - /// - public int Count => _windows.Count; - - /// - /// - /// Gets the at the specified index. - /// - /// - /// The . - /// - /// The index. - /// - public Window this[int index] => _windows[index]; - - /// - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// An enumerator that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - return _windows.GetEnumerator(); - } - - /// - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Adds the specified window. - /// - /// The window. - internal void Add(Window window) - { - if (window == null) - { - return; - } - - _windows.Add(window); - } - - /// - /// Removes the specified window. - /// - /// The window. - internal void Remove(Window window) - { - if (window == null) - { - return; - } - - _windows.Remove(window); - - OnRemoveWindow(window); - } - - /// - /// Closes all windows and removes them from the underlying collection. - /// - internal void Clear() - { - while (_windows.Count > 0) - { - _windows[0].Close(true); - } - } - - 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; - } - } - } -} diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index a0e86a53b0..617fbc8005 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/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); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 6a63014e1a..78369a3648 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/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); } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 9ed06c978d..396942c8dd 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/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().ToConstant(Threading); } - internal static TopLevel Initialize(T builder, string fbdev = null) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(T builder, string fbdev = null) where T : AppBuilderBase, 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, ISingleViewApplicationLifetime { - 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 Startup; + public event EventHandler 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(this T builder, Action setup, - CancellationToken stop = default(CancellationToken), string fbdev = null) +public static class LinuxFramebufferPlatformExtensions +{ + public static int StartLinuxFramebuffer(this T builder, string[] args, string fbdev = null) where T : AppBuilderBase, 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; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs index b982b98d38..2b82b4f4aa 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ b/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 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))); } } } diff --git a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs b/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs index b7d752dfe3..9e31cab421 100644 --- a/tests/Avalonia.Controls.UnitTests/ApplicationTests.cs +++ b/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 { 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); - } - } } } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 067c66969f..6482fcb4da 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/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); diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs new file mode 100644 index 0000000000..74523d4193 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + + public class DesktopStyleApplicationLifetimeTests + { + [Fact] + public void Should_Set_ExitCode_After_Shutdown() + { + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + lifetime.Shutdown(1337); + + var exitCode = lifetime.Start(Array.Empty()); + + Assert.Equal(1337, exitCode); + } + } + + + [Fact] + public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var windows = new List { new Window(), new Window(), new Window(), new Window() }; + + foreach (var window in windows) + { + window.Show(); + } + Assert.Equal(4, lifetime.Windows.Count); + lifetime.Shutdown(); + + Assert.Empty(lifetime.Windows); + } + } + + [Fact] + public void Should_Only_Exit_On_Explicit_Exit() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(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)) + using(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)) + using(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); + } + } + + [Fact] + public void Show_Should_Add_Window_To_OpenWindows() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + + Assert.Equal(new[] { window }, lifetime.Windows); + } + } + + [Fact] + public void Window_Should_Be_Added_To_OpenWindows_Only_Once() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + window.Show(); + window.IsVisible = true; + + Assert.Equal(new[] { window }, lifetime.Windows); + + window.Close(); + } + } + + [Fact] + public void Close_Should_Remove_Window_From_OpenWindows() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + Assert.Equal(1, lifetime.Windows.Count); + window.Close(); + + Assert.Empty(lifetime.Windows); + } + } + + [Fact] + public void Impl_Closing_Should_Remove_Window_From_OpenWindows() + { + var windowImpl = new Mock(); + windowImpl.SetupProperty(x => x.Closed); + windowImpl.Setup(x => x.Scaling).Returns(1); + + var services = TestServices.StyledWindow.With( + windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); + + using (UnitTestApplication.Start(services)) + using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current)) + { + var window = new Window(); + + window.Show(); + Assert.Equal(1, lifetime.Windows.Count); + windowImpl.Object.Closed(); + + Assert.Empty(lifetime.Windows); + } + } + } + +} diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index aa99d31cff..f112289460 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/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(); - 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; - } } } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index 6d00409ae0..12d29f2e5b 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -276,12 +276,6 @@ namespace Avalonia.Controls.UnitTests : base(impl) { } - - protected override void HandleApplicationExiting() - { - base.HandleApplicationExiting(); - IsClosed = true; - } } } } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index ee416c4cb0..f4d9a91d0c 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -121,75 +121,6 @@ namespace Avalonia.Controls.UnitTests } } - [Fact] - public void Show_Should_Add_Window_To_OpenWindows() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - - Assert.Equal(new[] { window }, Application.Current.Windows); - } - } - - [Fact] - public void Window_Should_Be_Added_To_OpenWindows_Only_Once() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - window.Show(); - window.IsVisible = true; - - Assert.Equal(new[] { window }, Application.Current.Windows); - - window.Close(); - } - } - - [Fact] - public void Close_Should_Remove_Window_From_OpenWindows() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - window.Close(); - - Assert.Empty(Application.Current.Windows); - } - } - - [Fact] - public void Impl_Closing_Should_Remove_Window_From_OpenWindows() - { - var windowImpl = new Mock(); - windowImpl.SetupProperty(x => x.Closed); - windowImpl.Setup(x => x.Scaling).Returns(1); - - var services = TestServices.StyledWindow.With( - windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object)); - - using (UnitTestApplication.Start(services)) - { - ClearOpenWindows(); - var window = new Window(); - - window.Show(); - windowImpl.Object.Closed(); - - Assert.Empty(Application.Current.Windows); - } - } - [Fact] public void Closing_Should_Only_Be_Invoked_Once() { @@ -420,12 +351,5 @@ namespace Avalonia.Controls.UnitTests x.Scaling == 1 && x.CreateRenderer(It.IsAny()) == renderer.Object); } - - private void ClearOpenWindows() - { - // HACK: We really need a decent way to have "statics" that can be scoped to - // AvaloniaLocator scopes. - Application.Current.Windows.Clear(); - } } } diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 3578471397..a516facb92 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -66,8 +66,7 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) - .Bind().ToSingleton() - .Bind().ToConstant(this); + .Bind().ToSingleton(); var styles = Services.Theme?.Invoke(); if (styles != null)