Browse Source

Initial

pull/1662/head
Benedikt Schroeder 8 years ago
parent
commit
e576ec178c
  1. 45
      src/Avalonia.Controls/AppBuilderBase.cs
  2. 118
      src/Avalonia.Controls/Application.cs
  3. 12
      src/Avalonia.Controls/ExitMode.cs
  4. 51
      src/Avalonia.Controls/Window.cs
  5. 134
      src/Avalonia.Controls/WindowCollection.cs
  6. 107
      tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
  7. 10
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

45
src/Avalonia.Controls/AppBuilderBase.cs

@ -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>
@ -92,7 +92,7 @@ namespace Avalonia.Controls
};
}
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"/>.
@ -125,7 +125,6 @@ namespace Avalonia.Controls
var window = new TMainWindow();
if (dataContextProvider != null)
window.DataContext = dataContextProvider();
window.Show();
Instance.Run(window);
}
@ -143,7 +142,6 @@ namespace Avalonia.Controls
if (dataContextProvider != null)
mainWindow.DataContext = dataContextProvider();
mainWindow.Show();
Instance.Run(mainWindow);
}
@ -209,6 +207,17 @@ namespace Avalonia.Controls
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>
@ -223,20 +232,20 @@ namespace Avalonia.Controls
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();
}

118
src/Avalonia.Controls/Application.cs

@ -43,11 +43,15 @@ namespace Avalonia
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;
}
@ -158,6 +162,40 @@ namespace Avalonia
/// <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>
@ -171,19 +209,81 @@ namespace Avalonia
/// <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(
() =>
{
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>
@ -191,7 +291,13 @@ namespace Avalonia
/// </summary>
public void Exit()
{
IsExiting = true;
Windows.Clear();
OnExit?.Invoke(this, EventArgs.Empty);
_mainLoopCancellationTokenSource?.Cancel();
}
/// <inheritdoc/>

12
src/Avalonia.Controls/ExitMode.cs

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

51
src/Avalonia.Controls/Window.cs

@ -49,14 +49,6 @@ namespace Avalonia.Controls
/// </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 =
@ -75,7 +67,7 @@ namespace Avalonia.Controls
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));
@ -117,7 +109,7 @@ namespace Avalonia.Controls
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));
@ -149,7 +141,7 @@ namespace Avalonia.Controls
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
Screens = new Screens(PlatformImpl?.Screen);
}
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
@ -199,7 +191,7 @@ namespace Avalonia.Controls
get { return GetValue(HasSystemDecorationsProperty); }
set { SetValue(HasSystemDecorationsProperty, value); }
}
/// <summary>
/// Enables or disables the taskbar icon
/// </summary>
@ -259,6 +251,26 @@ 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>
@ -298,10 +310,9 @@ namespace Avalonia.Controls
finally
{
if (ignoreCancel || !cancelClosing)
{
s_windows.Remove(this);
{
PlatformImpl?.Dispose();
IsVisible = false;
HandleClosed();
}
}
}
@ -359,7 +370,7 @@ namespace Avalonia.Controls
return;
}
s_windows.Add(this);
AddWindow(this);
EnsureInitialized();
SetWindowStartupLocation();
@ -400,7 +411,7 @@ namespace Avalonia.Controls
throw new InvalidOperationException("The window is already being shown.");
}
s_windows.Add(this);
AddWindow(this);
EnsureInitialized();
SetWindowStartupLocation();
@ -409,7 +420,7 @@ namespace Avalonia.Controls
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);
@ -513,8 +524,8 @@ namespace Avalonia.Controls
protected override void HandleClosed()
{
IsVisible = false;
s_windows.Remove(this);
RemoveWindow(this);
base.HandleClosed();
}

134
src/Avalonia.Controls/WindowCollection.cs

@ -0,0 +1,134 @@
// 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();
}
}
private void OnRemoveWindow(Window window)
{
if (window == null)
{
return;
}
if (_application.IsExiting)
{
return;
}
switch (_application.ExitMode)
{
case ExitMode.OnLastWindowClose:
if (Count == 0)
{
_application.Exit();
}
break;
case ExitMode.OnMainWindowClose:
if (window == _application.MainWindow)
{
_application.Exit();
}
break;
}
}
}
}

107
tests/Avalonia.Controls.UnitTests/ApplicationTests.cs

@ -0,0 +1,107 @@
// 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.Generic;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class ApplicationTests
{
[Fact]
public void Should_Exit_After_MainWindow_Closed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.ExitMode = ExitMode.OnMainWindowClose;
var mainWindow = new Window();
mainWindow.Show();
Application.Current.MainWindow = mainWindow;
var window = new Window();
window.Show();
mainWindow.Close();
Assert.True(Application.Current.IsExiting);
}
}
[Fact]
public void Should_Exit_After_Last_Window_Closed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.ExitMode = ExitMode.OnLastWindowClose;
var windowA = new Window();
windowA.Show();
var windowB = new Window();
windowB.Show();
windowA.Close();
Assert.False(Application.Current.IsExiting);
windowB.Close();
Assert.True(Application.Current.IsExiting);
}
}
[Fact]
public void Should_Only_Exit_On_Explicit_Exit()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.ExitMode = ExitMode.OnExplicitExit;
var windowA = new Window();
windowA.Show();
var windowB = new Window();
windowB.Show();
windowA.Close();
Assert.False(Application.Current.IsExiting);
windowB.Close();
Assert.False(Application.Current.IsExiting);
Application.Current.Exit();
Assert.True(Application.Current.IsExiting);
}
}
[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.Exit();
Assert.Empty(Application.Current.Windows);
}
}
}
}

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

@ -129,7 +129,7 @@ namespace Avalonia.Controls.UnitTests
window.Show();
Assert.Equal(new[] { window }, Window.OpenWindows);
Assert.Equal(new[] { window }, Application.Current.Windows);
}
}
@ -145,7 +145,7 @@ namespace Avalonia.Controls.UnitTests
window.Show();
window.IsVisible = true;
Assert.Equal(new[] { window }, Window.OpenWindows);
Assert.Equal(new[] { window }, Application.Current.Windows);
window.Close();
}
@ -162,7 +162,7 @@ namespace Avalonia.Controls.UnitTests
window.Show();
window.Close();
Assert.Empty(Window.OpenWindows);
Assert.Empty(Application.Current.Windows);
}
}
@ -184,7 +184,7 @@ namespace Avalonia.Controls.UnitTests
window.Show();
windowImpl.Object.Closed();
Assert.Empty(Window.OpenWindows);
Assert.Empty(Application.Current.Windows);
}
}
@ -339,7 +339,7 @@ namespace Avalonia.Controls.UnitTests
{
// HACK: We really need a decent way to have "statics" that can be scoped to
// AvaloniaLocator scopes.
((IList<Window>)Window.OpenWindows).Clear();
Application.Current.Windows.Clear();
}
}
}

Loading…
Cancel
Save