csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
10 KiB
284 lines
10 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using Avalonia.Collections;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.Platform;
|
|
using Avalonia.Reactive;
|
|
using Avalonia.Threading;
|
|
|
|
namespace Avalonia.Controls.ApplicationLifetimes
|
|
{
|
|
public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
|
|
{
|
|
private int _exitCode;
|
|
private CancellationTokenSource? _cts;
|
|
private bool _isShuttingDown;
|
|
private readonly AvaloniaList<Window> _windows = new();
|
|
private CompositeDisposable? _compositeDisposable;
|
|
|
|
/// <inheritdoc/>
|
|
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs>? Startup;
|
|
|
|
/// <inheritdoc/>
|
|
public event EventHandler<ShutdownRequestedEventArgs>? ShutdownRequested;
|
|
|
|
/// <inheritdoc/>
|
|
public event EventHandler<ControlledApplicationLifetimeExitEventArgs>? Exit;
|
|
|
|
/// <summary>
|
|
/// Gets the arguments passed to the AppBuilder Start method.
|
|
/// </summary>
|
|
public string[]? Args { get; set; }
|
|
|
|
/// <inheritdoc/>
|
|
public ShutdownMode ShutdownMode { get; set; }
|
|
|
|
/// <inheritdoc/>
|
|
public Window? MainWindow { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyList<Window> Windows => _windows;
|
|
|
|
private void HandleWindowClosed(Window? window)
|
|
{
|
|
if (window == null)
|
|
return;
|
|
|
|
if (_isShuttingDown)
|
|
return;
|
|
|
|
if (ShutdownMode == ShutdownMode.OnLastWindowClose && _windows.Count == 0)
|
|
TryShutdown();
|
|
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && ReferenceEquals(window, MainWindow))
|
|
TryShutdown();
|
|
}
|
|
|
|
public void Shutdown(int exitCode = 0)
|
|
{
|
|
DoShutdown(new ShutdownRequestedEventArgs(), true, true, exitCode);
|
|
}
|
|
|
|
public bool TryShutdown(int exitCode = 0)
|
|
{
|
|
return DoShutdown(new ShutdownRequestedEventArgs(), true, false, exitCode);
|
|
}
|
|
|
|
internal void SubscribeGlobalEvents()
|
|
{
|
|
if (_compositeDisposable is not null)
|
|
{
|
|
// There could be a case, when lifetime was setup without starting.
|
|
// Until developer started it manually later. To avoid API breaking changes, it will execute Setup method twice.
|
|
return;
|
|
}
|
|
|
|
_compositeDisposable = new CompositeDisposable(
|
|
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (sender, _) =>
|
|
{
|
|
var window = (Window)sender!;
|
|
if (!_windows.Contains(window))
|
|
{
|
|
_windows.Add(window);
|
|
}
|
|
}),
|
|
Window.WindowClosedEvent.AddClassHandler(typeof(Window), (sender, _) =>
|
|
{
|
|
var window = (Window)sender!;
|
|
_windows.Remove(window);
|
|
HandleWindowClosed(window);
|
|
}));
|
|
}
|
|
|
|
internal void SetupCore(string[] args)
|
|
{
|
|
SubscribeGlobalEvents();
|
|
|
|
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
|
|
|
|
var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
|
|
|
|
if (lifetimeEvents != null)
|
|
lifetimeEvents.ShutdownRequested += OnShutdownRequested;
|
|
}
|
|
|
|
public int Start(string[] args)
|
|
{
|
|
return StartCore(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Since the lifetime must be set up/prepared with 'args' before executing Start(), an overload with no parameters seems more suitable for integrating with some lifetime manager providers, such as MS HostApplicationBuilder.
|
|
/// </summary>
|
|
/// <returns>exit code</returns>
|
|
public int Start()
|
|
{
|
|
return StartCore(Args ?? Array.Empty<string>());
|
|
}
|
|
|
|
internal int StartCore(string[] args)
|
|
{
|
|
SetupCore(args);
|
|
|
|
_cts = new CancellationTokenSource();
|
|
|
|
// Note due to a bug in the JIT we wrap this in a method, otherwise MainWindow
|
|
// gets stuffed into a local var and can not be GCed until after the program stops.
|
|
// this method never exits until program end.
|
|
ShowMainWindow();
|
|
|
|
Dispatcher.UIThread.MainLoop(_cts.Token);
|
|
Environment.ExitCode = _exitCode;
|
|
return _exitCode;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
private void ShowMainWindow()
|
|
{
|
|
MainWindow?.Show();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_compositeDisposable?.Dispose();
|
|
_compositeDisposable = null;
|
|
}
|
|
|
|
private bool DoShutdown(
|
|
ShutdownRequestedEventArgs e,
|
|
bool isProgrammatic,
|
|
bool force = false,
|
|
int exitCode = 0)
|
|
{
|
|
if (!force)
|
|
{
|
|
ShutdownRequested?.Invoke(this, e);
|
|
|
|
if (e.Cancel)
|
|
return false;
|
|
|
|
if (_isShuttingDown)
|
|
throw new InvalidOperationException("Application is already shutting down.");
|
|
}
|
|
|
|
_exitCode = exitCode;
|
|
_isShuttingDown = true;
|
|
var shutdownCancelled = false;
|
|
|
|
try
|
|
{
|
|
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
|
|
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
|
|
// owners.
|
|
foreach (var w in new List<Window>(_windows))
|
|
{
|
|
if (w.Owner is null)
|
|
{
|
|
var ignoreCancel = force || (ShutdownMode == ShutdownMode.OnMainWindowClose && w != MainWindow);
|
|
var reason = e.IsOSShutdown ?
|
|
WindowCloseReason.OSShutdown :
|
|
WindowCloseReason.ApplicationShutdown;
|
|
w.CloseCore(reason, isProgrammatic, ignoreCancel);
|
|
}
|
|
}
|
|
|
|
if (!force && Windows.Count > 0)
|
|
{
|
|
e.Cancel = true;
|
|
shutdownCancelled = true;
|
|
return false;
|
|
}
|
|
|
|
var args = new ControlledApplicationLifetimeExitEventArgs(exitCode);
|
|
Exit?.Invoke(this, args);
|
|
_exitCode = args.ApplicationExitCode;
|
|
}
|
|
finally
|
|
{
|
|
_isShuttingDown = false;
|
|
|
|
if (!shutdownCancelled)
|
|
{
|
|
_cts?.Cancel();
|
|
_cts = null;
|
|
Dispatcher.UIThread.InvokeShutdown();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e) => DoShutdown(e, false);
|
|
}
|
|
}
|
|
|
|
namespace Avalonia
|
|
{
|
|
/// <summary>
|
|
/// IClassicDesktopStyleApplicationLifetime related AppBuilder extensions.
|
|
/// </summary>
|
|
public static class ClassicDesktopStyleApplicationLifetimeExtensions
|
|
{
|
|
private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder builder, string[] args,
|
|
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder)
|
|
{
|
|
var lifetime = new ClassicDesktopStyleApplicationLifetime();
|
|
lifetime.SubscribeGlobalEvents();
|
|
|
|
lifetime.Args = args;
|
|
lifetimeBuilder?.Invoke(lifetime);
|
|
|
|
return lifetime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setups the Application with a IClassicDesktopStyleApplicationLifetime, but doesn't show the main window and doesn't run application main loop.
|
|
/// </summary>
|
|
/// <param name="builder">Application builder.</param>
|
|
/// <param name="args">Startup arguments.</param>
|
|
/// <param name="lifetimeBuilder">Lifetime builder to modify the lifetime before application started.</param>
|
|
/// <returns>Exit code.</returns>
|
|
public static AppBuilder SetupWithClassicDesktopLifetime(this AppBuilder builder, string[] args,
|
|
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder = null)
|
|
{
|
|
var lifetime = PrepareLifetime(builder, args, lifetimeBuilder);
|
|
var result = builder.SetupWithLifetime(lifetime);
|
|
lifetime.SetupCore(args);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the Application with a IClassicDesktopStyleApplicationLifetime, shows main window and runs application main loop.
|
|
/// </summary>
|
|
/// <param name="builder">Application builder.</param>
|
|
/// <param name="args">Startup arguments.</param>
|
|
/// <param name="lifetimeBuilder">Lifetime builder to modify the lifetime before application started.</param>
|
|
/// <returns>Exit code.</returns>
|
|
public static int StartWithClassicDesktopLifetime(
|
|
this AppBuilder builder, string[] args,
|
|
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder = null)
|
|
{
|
|
var lifetime = PrepareLifetime(builder, args, lifetimeBuilder);
|
|
builder.SetupWithLifetime(lifetime);
|
|
return lifetime.Start(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the Application with a IClassicDesktopStyleApplicationLifetime, shows main window and runs application main loop.
|
|
/// </summary>
|
|
/// <param name="builder">Application builder.</param>
|
|
/// <param name="args">Startup arguments.</param>
|
|
/// <param name="shutdownMode">Lifetime shutdown mode.</param>
|
|
/// <returns>Exit code.</returns>
|
|
public static int StartWithClassicDesktopLifetime(
|
|
this AppBuilder builder, string[] args, ShutdownMode shutdownMode)
|
|
{
|
|
var lifetime = PrepareLifetime(builder, args, l => l.ShutdownMode = shutdownMode);
|
|
builder.SetupWithLifetime(lifetime);
|
|
return lifetime.Start(args);
|
|
}
|
|
}
|
|
}
|
|
|