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,6 +207,17 @@ 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;
}

private bool CheckSetup { get; set; } = true;

/// <summary>
Expand All @@ -223,20 +232,20 @@ internal TAppBuilder IgnoreSetupCheck()
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
118 changes: 112 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,95 @@ 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();

Dispatcher.UIThread.InvokeAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be done via an invoke?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found a better solution to show the main window and start up the main loop. The invoke queues the action for execution and gets executed when the main loop runs.

Copy link
Member

@grokys grokys Jun 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried changing this to:

            mainWindow.Show();
            MainWindow = mainWindow;
            Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);

And it seems to work. What am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If everything works its fine. Not sure about side effects. My solution only runs after the main loop is running. Maybe show does the same. At some point something has to be invoked on the main thread. Is it possible to call run on a different thread or even Window.Show? My solution would avoid that issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried what you did and it didn't work. My approach always runs. Don't know why yours work on your environment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried it with Skia or Direct2D1? Yours works with Skia but not with Direct2D1 on my machine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it with Win32/D2D but it also works with skia here. Strange that it doesn't work with D2D on your machine, I don't see how the rendering backend would affect whether a window gets shown or not - that should depend on the windowing backend.

It's also strange because this is how it works on master - Window.Show is called from AppBuilderBase.Start before the main loop is started.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont understand it either has probably something to do with the. net runtime. With .Net Core I dont need invoke. Will remove the invoke call and just hope others don't have the problem.

() =>
{
if (mainWindow == null)
{
return;
}

if (MainWindow != null)
{
return;
}

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

MainWindow = mainWindow;
},
DispatcherPriority.Send);

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
12 changes: 12 additions & 0 deletions src/Avalonia.Controls/ExitMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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
{
public enum ExitMode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some XML documentation to this enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add some comments

{
OnLastWindowClose,
OnMainWindowClose,
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