Browse Source

Refactored lifetime control into separate lifetime classes

pull/2676/head
Nikita Tsukanov 7 years ago
parent
commit
b33601ee9b
  1. 22
      samples/ControlCatalog.NetCore/Program.cs
  2. 11
      samples/ControlCatalog/App.xaml.cs
  3. 15
      src/Avalonia.Controls/AppBuilderBase.cs
  4. 200
      src/Avalonia.Controls/Application.cs
  5. 99
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  6. 11
      src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs
  7. 7
      src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs
  8. 28
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  9. 13
      src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs
  10. 7
      src/Avalonia.Controls/ApplicationLifetimes/ISingleViewLifetime.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. 19
      src/Avalonia.Controls/Window.cs
  16. 33
      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. 139
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
  24. 19
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  25. 6
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  26. 2
      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 ISingleViewLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();
base.OnFrameworkInitializationCompleted();
}
}
}

15
src/Avalonia.Controls/AppBuilderBase.cs

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

200
src/Avalonia.Controls/Application.cs

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

99
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

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

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

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

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

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/ISingleViewLifetime.cs

@ -0,0 +1,7 @@
namespace Avalonia.Controls.ApplicationLifetimes
{
public interface ISingleViewLifetime : 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>

19
src/Avalonia.Controls/Window.cs

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

33
src/Avalonia.Controls/WindowCollection.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
@ -12,6 +13,7 @@ namespace Avalonia
{
private readonly Application _application;
private readonly List<Window> _windows = new List<Window>();
public event Action<Window> OnWindowClosed;
public WindowCollection(Application application)
{
@ -92,7 +94,7 @@ namespace Avalonia
/// <summary>
/// Closes all windows and removes them from the underlying collection.
/// </summary>
internal void Clear()
public void CloseAll()
{
while (_windows.Count > 0)
{
@ -102,33 +104,8 @@ namespace Avalonia
private void OnRemoveWindow(Window window)
{
if (window == null)
{
return;
}
if (_application.IsShuttingDown)
{
return;
}
switch (_application.ShutdownMode)
{
case ShutdownMode.OnLastWindowClose:
if (Count == 0)
{
_application.Shutdown();
}
break;
case ShutdownMode.OnMainWindowClose:
if (window == _application.MainWindow)
{
_application.Shutdown();
}
break;
}
if (window != null)
OnWindowClosed?.Invoke(window);
}
}
}

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, ISingleViewLifetime
{
public event EventHandler Closed;
private readonly LinuxFramebuffer _fb;
private TopLevel _topLevel;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public CancellationToken Token => _cts.Token;
public TokenClosable(CancellationToken token)
public LinuxFramebufferLifetime(LinuxFramebuffer fb)
{
_fb = fb;
}
public Control MainView
{
token.Register(() => Dispatcher.UIThread.Post(() => Closed?.Invoke(this, new EventArgs())));
get => (Control)_topLevel?.Content;
set
{
if (_topLevel == null)
{
var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb));
tl.Prepare();
_topLevel = tl;
}
_topLevel.Content = value;
}
}
public int ExitCode { get; private set; }
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
public void Start(string[] args)
{
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
}
public void Shutdown(int exitCode)
{
ExitCode = exitCode;
var e = new ControlledApplicationLifetimeExitEventArgs(exitCode);
Exit?.Invoke(this, e);
ExitCode = e.ApplicationExitCode;
_cts.Cancel();
}
}
}
public static void InitializeWithLinuxFramebuffer<T>(this T builder, Action<TopLevel> setup,
CancellationToken stop = default(CancellationToken), string fbdev = null)
public static class LinuxFramebufferPlatformExtensions
{
public static int StartLinuxFramebuffer<T>(this T builder, string[] args, string fbdev = null)
where T : AppBuilderBase<T>, new()
{
setup(LinuxFramebufferPlatform.Initialize(builder, fbdev));
builder.BeforeStartCallback(builder);
builder.Instance.Run(new TokenClosable(stop));
var lifetime = LinuxFramebufferPlatform.Initialize(builder, fbdev);
builder.Instance.ApplicationLifetime = lifetime;
builder.SetupWithoutStarting();
lifetime.Start(args);
builder.Instance.Run(lifetime.Token);
return lifetime.ExitCode;
}
}

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

139
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

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

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

2
tests/Avalonia.Controls.UnitTests/WindowTests.cs

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

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