Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application ExitMode #1662

Merged
merged 12 commits into from
Jun 29, 2018
45 changes: 27 additions & 18 deletions src/Avalonia.Controls/AppBuilderBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Avalonia.Controls
public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
private static bool s_setupWasAlreadyCalled;

/// <summary>
/// Gets or sets the <see cref="IRuntimePlatform"/> instance.
/// </summary>
Expand Down Expand Up @@ -92,7 +92,7 @@ public static TAppBuilder Configure(Application app)
};
}

protected TAppBuilder Self => (TAppBuilder) this;
protected TAppBuilder Self => (TAppBuilder)this;

/// <summary>
/// Registers a callback to call before Start is called on the <see cref="Application"/>.
Expand Down Expand Up @@ -125,7 +125,6 @@ public void Start<TMainWindow>(Func<object> dataContextProvider = null)
var window = new TMainWindow();
if (dataContextProvider != null)
window.DataContext = dataContextProvider();
window.Show();
Instance.Run(window);
}

Expand All @@ -143,7 +142,6 @@ public void Start<TMainWindow>(TMainWindow mainWindow, Func<object> dataContextP

if (dataContextProvider != null)
mainWindow.DataContext = dataContextProvider();
mainWindow.Show();
Instance.Run(mainWindow);
}

Expand Down Expand Up @@ -209,25 +207,36 @@ static Action GetInitializer(string assemblyName) => () =>

public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());

/// <summary>
/// Sets the shutdown mode of the application.
/// </summary>
/// <param name="exitMode">The shutdown mode.</param>
/// <returns></returns>
public TAppBuilder SetExitMode(ExitMode exitMode)
{
Instance.ExitMode = exitMode;
return Self;
}

protected virtual bool CheckSetup => true;

private void SetupAvaloniaModules()
{
var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies()
from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>()
where attribute.ForWindowingSubsystem == ""
|| attribute.ForWindowingSubsystem == WindowingSubsystemName
where attribute.ForRenderingSubsystem == ""
|| attribute.ForRenderingSubsystem == RenderingSubsystemName
group attribute by attribute.Name into exports
select (from export in exports
orderby export.ForWindowingSubsystem.Length descending
orderby export.ForRenderingSubsystem.Length descending
select export).First().ModuleType into moduleType
select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors
where constructor.GetParameters().Length == 0 && !constructor.IsStatic
select constructor).Single() into constructor
select (Action)(() => constructor.Invoke(new object[0]));
from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>()
where attribute.ForWindowingSubsystem == ""
|| attribute.ForWindowingSubsystem == WindowingSubsystemName
where attribute.ForRenderingSubsystem == ""
|| attribute.ForRenderingSubsystem == RenderingSubsystemName
group attribute by attribute.Name into exports
select (from export in exports
orderby export.ForWindowingSubsystem.Length descending
orderby export.ForRenderingSubsystem.Length descending
select export).First().ModuleType into moduleType
select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors
where constructor.GetParameters().Length == 0 && !constructor.IsStatic
select constructor).Single() into constructor
select (Action)(() => constructor.Invoke(new object[0]));
Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke();
}

Expand Down
111 changes: 105 additions & 6 deletions src/Avalonia.Controls/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalS
private Styles _styles;
private IResourceDictionary _resources;

private CancellationTokenSource _mainLoopCancellationTokenSource;

/// <summary>
/// Initializes a new instance of the <see cref="Application"/> class.
/// </summary>
public Application()
{
Windows = new WindowCollection(this);

OnExit += OnExiting;
}

Expand Down Expand Up @@ -158,6 +162,40 @@ public IResourceDictionary Resources
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => null;

/// <summary>
/// Gets or sets the <see cref="ExitMode"/>. This property indicates whether the application exits explicitly or implicitly.
/// If <see cref="ExitMode"/> is set to OnExplicitExit the application is only closes if Exit is called.
/// The default is OnLastWindowClose
/// </summary>
/// <value>
/// The shutdown mode.
/// </value>
public ExitMode ExitMode { 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 existing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is existing; otherwise, <c>false</c>.
/// </value>
internal bool IsExiting { get; set; }

/// <summary>
/// Initializes the application by loading XAML etc.
/// </summary>
Expand All @@ -171,27 +209,88 @@ public virtual void Initialize()
/// <param name="closable">The closable to track</param>
public void Run(ICloseable closable)
{
var source = new CancellationTokenSource();
closable.Closed += OnExiting;
closable.Closed += (s, e) => source.Cancel();
Dispatcher.UIThread.MainLoop(source.Token);
if (_mainLoopCancellationTokenSource != null)
{
throw new Exception("Run should only called once");
}

closable.Closed += (s, e) => Exit();

_mainLoopCancellationTokenSource = new CancellationTokenSource();

Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);

// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
}

/// <summary>
/// Runs the application's main loop until some condition occurs that is specified by ExitMode.
/// </summary>
/// <param name="mainWindow">The main window</param>
public void Run(Window mainWindow)
{
if (_mainLoopCancellationTokenSource != null)
{
throw new Exception("Run should only called once");
}

_mainLoopCancellationTokenSource = new CancellationTokenSource();

if (MainWindow == null)
{
if (mainWindow == null)
{
throw new ArgumentNullException(nameof(mainWindow));
}

if (!mainWindow.IsVisible)
{
mainWindow.Show();
}

MainWindow = mainWindow;
}

Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);

// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
}

/// <summary>
/// Runs the application's main loop until the <see cref="CancellationToken"/> is cancelled.
/// Runs the application's main loop until the <see cref="CancellationToken"/> is canceled.
/// </summary>
/// <param name="token">The token to track</param>
public void Run(CancellationToken token)
{
Dispatcher.UIThread.MainLoop(token);

// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
}

/// <summary>
/// Exits the application
/// </summary>
public void Exit()
{
IsExiting = true;

Windows.Clear();

OnExit?.Invoke(this, EventArgs.Empty);

_mainLoopCancellationTokenSource?.Cancel();
}

/// <inheritdoc/>
Expand Down
26 changes: 26 additions & 0 deletions src/Avalonia.Controls/ExitMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.

namespace Avalonia
{
/// <summary>
/// Enum for ExitMode
/// </summary>
public enum ExitMode
{
/// <summary>
/// Indicates an implicit call to Application.Exit when the last window closes.
/// </summary>
OnLastWindowClose,

/// <summary>
/// Indicates an implicit call to Application.Exit when the main window closes.
/// </summary>
OnMainWindowClose,

/// <summary>
/// Indicates that the application only exits on an explicit call to Application.Exit.
/// </summary>
OnExplicitExit
}
}
51 changes: 31 additions & 20 deletions src/Avalonia.Controls/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ public enum SizeToContent
/// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
{
private static List<Window> s_windows = new List<Window>();

/// <summary>
/// Retrieves an enumeration of all Windows in the currently running application.
/// </summary>
public static IReadOnlyList<Window> OpenWindows => s_windows;

/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
/// </summary>
public static readonly StyledProperty<SizeToContent> SizeToContentProperty =
Expand All @@ -75,7 +67,7 @@ public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameSco
AvaloniaProperty.Register<Window, bool>(nameof(ShowInTaskbar), true);

/// <summary>
/// Enables or disables the taskbar icon
/// Represents the current window state (normal, minimized, maximized)
/// </summary>
public static readonly StyledProperty<WindowState> WindowStateProperty =
AvaloniaProperty.Register<Window, WindowState>(nameof(WindowState));
Expand Down Expand Up @@ -117,7 +109,7 @@ static Window()
BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue));
HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
(s, e) => s.PlatformImpl?.SetSystemDecorations((bool) e.NewValue));
(s, e) => s.PlatformImpl?.SetSystemDecorations((bool)e.NewValue));

ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));

Expand Down Expand Up @@ -149,7 +141,7 @@ public Window(IWindowImpl impl)
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
Screens = new Screens(PlatformImpl?.Screen);
}

/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
Expand Down Expand Up @@ -199,7 +191,7 @@ public bool HasSystemDecorations
get { return GetValue(HasSystemDecorationsProperty); }
set { SetValue(HasSystemDecorationsProperty, value); }
}

/// <summary>
/// Enables or disables the taskbar icon
/// </summary>
Expand Down Expand Up @@ -259,6 +251,26 @@ public WindowStartupLocation WindowStartupLocation
/// </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>
Expand Down Expand Up @@ -298,10 +310,9 @@ internal void Close(bool ignoreCancel)
finally
{
if (ignoreCancel || !cancelClosing)
{
s_windows.Remove(this);
{
PlatformImpl?.Dispose();
IsVisible = false;
HandleClosed();
}
}
}
Expand Down Expand Up @@ -359,7 +370,7 @@ public override void Show()
return;
}

s_windows.Add(this);
AddWindow(this);

EnsureInitialized();
SetWindowStartupLocation();
Expand Down Expand Up @@ -400,7 +411,7 @@ public Task<TResult> ShowDialog<TResult>()
throw new InvalidOperationException("The window is already being shown.");
}

s_windows.Add(this);
AddWindow(this);

EnsureInitialized();
SetWindowStartupLocation();
Expand All @@ -409,7 +420,7 @@ public Task<TResult> ShowDialog<TResult>()

using (BeginAutoSizing())
{
var affectedWindows = s_windows.Where(w => w.IsEnabled && w != this).ToList();
var affectedWindows = Application.Current.Windows.Where(w => w.IsEnabled && w != this).ToList();
var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
SetIsEnabled(affectedWindows, false);

Expand Down Expand Up @@ -513,8 +524,8 @@ protected override Size MeasureOverride(Size availableSize)

protected override void HandleClosed()
{
IsVisible = false;
s_windows.Remove(this);
RemoveWindow(this);

base.HandleClosed();
}

Expand Down
Loading