Browse Source

Merge pull request #2676 from AvaloniaUI/app-lifetime

Refactored lifetime control into separate lifetime classes
gtk-solution
Nikita Tsukanov 7 years ago
committed by GitHub
parent
commit
8349b4dd95
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      samples/ControlCatalog.NetCore/Program.cs
  2. 11
      samples/ControlCatalog/App.xaml.cs
  3. 51
      src/Avalonia.Controls/AppBuilderBase.cs
  4. 216
      src/Avalonia.Controls/Application.cs
  5. 133
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  6. 11
      src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs
  7. 7
      src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs
  8. 31
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  9. 13
      src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs
  10. 7
      src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs
  11. 22
      src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs
  12. 67
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  13. 36
      src/Avalonia.Controls/StartupEventArgs.cs
  14. 20
      src/Avalonia.Controls/TopLevel.cs
  15. 60
      src/Avalonia.Controls/Window.cs
  16. 134
      src/Avalonia.Controls/WindowCollection.cs
  17. 12
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  18. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  19. 74
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  20. 12
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  21. 120
      tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
  22. 4
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  23. 213
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  24. 19
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  25. 6
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  26. 76
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  27. 3
      tests/Avalonia.UnitTests/UnitTestApplication.cs

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

51
src/Avalonia.Controls/AppBuilderBase.cs

@ -57,10 +57,6 @@ namespace Avalonia.Controls
/// </summary>
public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
/// <summary>
/// Gets or sets a method to call before Start is called on the <see cref="Application"/>.
/// </summary>
public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
protected AppBuilderBase(IRuntimePlatform platform, Action<TAppBuilder> platformServices)
{
@ -95,17 +91,6 @@ namespace Avalonia.Controls
protected TAppBuilder Self => (TAppBuilder)this;
/// <summary>
/// Registers a callback to call before Start is called on the <see cref="Application"/>.
/// </summary>
/// <param name="callback">The callback.</param>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public TAppBuilder BeforeStarting(Action<TAppBuilder> callback)
{
BeforeStartCallback = (Action<TAppBuilder>)Delegate.Combine(BeforeStartCallback, callback);
return Self;
}
public TAppBuilder AfterSetup(Action<TAppBuilder> callback)
{
AfterSetupCallback = (Action<TAppBuilder>)Delegate.Combine(AfterSetupCallback, callback);
@ -117,48 +102,28 @@ 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()
{
Setup();
BeforeStartCallback(Self);
var window = new TMainWindow();
if (dataContextProvider != null)
window.DataContext = dataContextProvider();
Instance.Run(window);
}
/// <summary>
/// Starts the application with the provided instance of <typeparamref name="TMainWindow"/>.
/// </summary>
/// <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>
public void Start<TMainWindow>(TMainWindow mainWindow, Func<object> 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());
/// <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 +267,7 @@ namespace Avalonia.Controls
Instance.RegisterServices();
Instance.Initialize();
AfterSetupCallback(Self);
Instance.OnFrameworkInitializationCompleted();
}
}
}

216
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,22 +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.
/// </summary>
public Application()
{
Windows = new WindowCollection(this);
}
/// <inheritdoc/>
public event EventHandler<StartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<ExitEventArgs> Exit;
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
@ -166,198 +151,21 @@ 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>
/// <value>
/// 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="ISingleViewApplicationLifetime"/>
/// - <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 +191,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 +201,11 @@ namespace Avalonia
.GetService<IRenderLoop>()?.Add(clock);
}
public virtual void OnFrameworkInitializationCompleted()
{
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);

133
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<Window> _windows = new HashSet<Window>();
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;
}
/// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
/// <inheritdoc/>
public ShutdownMode ShutdownMode { get; set; }
/// <inheritdoc/>
public Window MainWindow { get; set; }
public IReadOnlyList<Window> 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<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);
}
}
}

11
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
src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs

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

31
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
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; }
IReadOnlyList<Window> Windows { get; }
}
}

13
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
src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs

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

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

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

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>

60
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<bool> CanResizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
/// <summary>
/// Routed event that can be used for global tracking of window destruction
/// </summary>
public static readonly RoutedEvent WindowClosedEvent =
RoutedEvent.Register<Window, RoutedEventArgs>("WindowClosed", RoutingStrategies.Direct);
/// <summary>
/// Routed event that can be used for global tracking of opening windows
/// </summary>
public static readonly RoutedEvent WindowOpenedEvent =
RoutedEvent.Register<Window, RoutedEventArgs>("WindowOpened", RoutingStrategies.Direct);
private readonly NameScope _nameScope = new NameScope();
private object _dialogResult;
private readonly Size _maxPlatformClientSize;
@ -249,26 +264,6 @@ namespace Avalonia.Controls
/// </summary>
public event EventHandler<CancelEventArgs> 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);
}
/// <summary>
/// Closes the window.
/// </summary>
@ -277,12 +272,6 @@ namespace Avalonia.Controls
Close(false);
}
protected override void HandleApplicationExiting()
{
base.HandleApplicationExiting();
Close(true);
}
/// <summary>
/// Closes a dialog window with the specified result.
/// </summary>
@ -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<TWindow>(this Application app) where TWindow : Avalonia.Controls.Window, new()
{
var window = new TWindow();
window.Show();
app.Run(window);
}
}
}

134
src/Avalonia.Controls/WindowCollection.cs

@ -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<Window>
{
private readonly Application _application;
private readonly List<Window> _windows = new List<Window>();
public WindowCollection(Application application)
{
_application = application;
}
/// <inheritdoc />
/// <summary>
/// Gets the number of elements in the collection.
/// </summary>
public int Count => _windows.Count;
/// <inheritdoc />
/// <summary>
/// Gets the <see cref="T:Avalonia.Controls.Window" /> at the specified index.
/// </summary>
/// <value>
/// The <see cref="T:Avalonia.Controls.Window" />.
/// </value>
/// <param name="index">The index.</param>
/// <returns></returns>
public Window this[int index] => _windows[index];
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// An enumerator that can be used to iterate through the collection.
/// </returns>
public IEnumerator<Window> GetEnumerator()
{
return _windows.GetEnumerator();
}
/// <inheritdoc />
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Adds the specified window.
/// </summary>
/// <param name="window">The window.</param>
internal void Add(Window window)
{
if (window == null)
{
return;
}
_windows.Add(window);
}
/// <summary>
/// Removes the specified window.
/// </summary>
/// <param name="window">The window.</param>
internal void Remove(Window window)
{
if (window == null)
{
return;
}
_windows.Remove(window);
OnRemoveWindow(window);
}
/// <summary>
/// Closes all windows and removes them from the underlying collection.
/// </summary>
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;
}
}
}
}

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

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

74
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, 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<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;
}
}

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

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

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

213
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<string>());
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<Window> { 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<IWindowImpl>();
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);
}
}
}
}

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

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

76
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<IWindowImpl>();
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<IRenderRoot>()) == 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();
}
}
}

3
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)

Loading…
Cancel
Save