diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 329b757ec4..acd9534d14 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -45,14 +45,6 @@ namespace Avalonia
private Styles _styles;
private IResourceDictionary _resources;
- ///
- /// Initializes a new instance of the class.
- ///
- public Application()
- {
- Windows = new WindowCollection(this);
- }
-
///
public event EventHandler ResourcesChanged;
@@ -159,14 +151,6 @@ namespace Avalonia
///
IResourceNode IResourceNode.ResourceParent => null;
-
- ///
- /// Gets the open windows of the application.
- ///
- ///
- /// The windows.
- ///
- public WindowCollection Windows { get; }
///
/// Application lifetime, use it for things like setting the main window and exiting the app from code
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
index d6d5c56537..abca7a64ee 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
@@ -1,21 +1,46 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Interactivity;
namespace Avalonia.Controls.ApplicationLifetimes
{
- public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime
+ public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
{
private readonly Application _app;
private int _exitCode;
private CancellationTokenSource _cts;
private bool _isShuttingDown;
+ private HashSet _windows = new HashSet();
+
+ private static ClassicDesktopStyleApplicationLifetime _activeLifetime;
+ static ClassicDesktopStyleApplicationLifetime()
+ {
+ Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened);
+ Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent);
+ }
+
+ private static void WindowClosedEvent(object sender, RoutedEventArgs e)
+ {
+ _activeLifetime?._windows.Remove((Window)sender);
+ _activeLifetime?.HandleWindowClosed((Window)sender);
+ }
+
+ private static void OnWindowOpened(object sender, RoutedEventArgs e)
+ {
+ _activeLifetime?._windows.Add((Window)sender);
+ }
public ClassicDesktopStyleApplicationLifetime(Application app)
{
+ if (_activeLifetime != null)
+ throw new InvalidOperationException(
+ "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
_app = app;
- app.Windows.OnWindowClosed += HandleWindowClosed;
+ _activeLifetime = this;
}
///
@@ -29,6 +54,8 @@ namespace Avalonia.Controls.ApplicationLifetimes
///
public Window MainWindow { get; set; }
+ public IReadOnlyList Windows => _windows.ToList();
+
private void HandleWindowClosed(Window window)
{
if (window == null)
@@ -37,7 +64,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
if (_isShuttingDown)
return;
- if (ShutdownMode == ShutdownMode.OnLastWindowClose && _app.Windows.Count == 0)
+ if (ShutdownMode == ShutdownMode.OnLastWindowClose && _windows.Count == 0)
Shutdown();
else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow)
Shutdown();
@@ -56,7 +83,8 @@ namespace Avalonia.Controls.ApplicationLifetimes
try
{
- _app.Windows.CloseAll();
+ foreach (var w in Windows)
+ w.Close();
var e = new ControlledApplicationLifetimeExitEventArgs(exitCode);
Exit?.Invoke(this, e);
_exitCode = e.ApplicationExitCode;
@@ -79,6 +107,12 @@ namespace Avalonia.Controls.ApplicationLifetimes
Environment.ExitCode = _exitCode;
return _exitCode;
}
+
+ public void Dispose()
+ {
+ if (_activeLifetime == this)
+ _activeLifetime = null;
+ }
}
}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
index 6d809c6714..a1006d907b 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace Avalonia.Controls.ApplicationLifetimes
{
@@ -24,5 +25,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// The main window.
///
Window MainWindow { get; set; }
+
+ IReadOnlyList Windows { get; }
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 7ae0380ba0..5c117f508b 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using System.ComponentModel;
+using Avalonia.Interactivity;
namespace Avalonia.Controls
{
@@ -97,6 +98,20 @@ namespace Avalonia.Controls
public static readonly StyledProperty CanResizeProperty =
AvaloniaProperty.Register(nameof(CanResize), true);
+ ///
+ /// Routed event that can be used for global tracking of window destruction
+ ///
+ public static readonly RoutedEvent WindowClosedEvent =
+ RoutedEvent.Register("WindowClosed", RoutingStrategies.Direct);
+
+ ///
+ /// Routed event that can be used for global tracking of opening windows
+ ///
+ public static readonly RoutedEvent WindowOpenedEvent =
+ RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct);
+
+
+
private readonly NameScope _nameScope = new NameScope();
private object _dialogResult;
private readonly Size _maxPlatformClientSize;
@@ -249,26 +264,6 @@ namespace Avalonia.Controls
///
public event EventHandler 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);
- }
-
///
/// Closes the window.
///
@@ -376,7 +371,7 @@ namespace Avalonia.Controls
return;
}
- AddWindow(this);
+ this.RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
EnsureInitialized();
IsVisible = true;
@@ -438,7 +433,7 @@ namespace Avalonia.Controls
throw new InvalidOperationException("The window is already being shown.");
}
- AddWindow(this);
+ RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));
EnsureInitialized();
IsVisible = true;
@@ -551,7 +546,7 @@ namespace Avalonia.Controls
protected override void HandleClosed()
{
- RemoveWindow(this);
+ RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
base.HandleClosed();
}
diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs
deleted file mode 100644
index aa076a1808..0000000000
--- a/src/Avalonia.Controls/WindowCollection.cs
+++ /dev/null
@@ -1,111 +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;
-using System.Collections.Generic;
-
-using Avalonia.Controls;
-
-namespace Avalonia
-{
- public class WindowCollection : IReadOnlyList
- {
- private readonly Application _application;
- private readonly List _windows = new List();
- public event Action OnWindowClosed;
-
- public WindowCollection(Application application)
- {
- _application = application;
- }
-
- ///
- ///
- /// Gets the number of elements in the collection.
- ///
- public int Count => _windows.Count;
-
- ///
- ///
- /// Gets the at the specified index.
- ///
- ///
- /// The .
- ///
- /// The index.
- ///
- public Window this[int index] => _windows[index];
-
- ///
- ///
- /// Returns an enumerator that iterates through the collection.
- ///
- ///
- /// An enumerator that can be used to iterate through the collection.
- ///
- public IEnumerator GetEnumerator()
- {
- return _windows.GetEnumerator();
- }
-
- ///
- ///
- /// Returns an enumerator that iterates through a collection.
- ///
- ///
- /// An object that can be used to iterate through the collection.
- ///
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- ///
- /// Adds the specified window.
- ///
- /// The window.
- internal void Add(Window window)
- {
- if (window == null)
- {
- return;
- }
-
- _windows.Add(window);
- }
-
- ///
- /// Removes the specified window.
- ///
- /// The window.
- internal void Remove(Window window)
- {
- if (window == null)
- {
- return;
- }
-
- _windows.Remove(window);
-
- OnRemoveWindow(window);
- }
-
- ///
- /// Closes all windows and removes them from the underlying collection.
- ///
- public void CloseAll()
- {
- while (_windows.Count > 0)
- {
- _windows[0].Close(true);
- }
- }
-
- private void OnRemoveWindow(Window window)
- {
- if (window != null)
- OnWindowClosed?.Invoke(window);
- }
- }
-}
diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
index ee27d6493b..74523d4193 100644
--- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.UnitTests;
+using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -14,9 +16,8 @@ namespace Avalonia.Controls.UnitTests
public void Should_Set_ExitCode_After_Shutdown()
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
- var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
- Dispatcher.UIThread.InvokeAsync(() => lifetime.Shutdown(1337));
lifetime.Shutdown(1337);
var exitCode = lifetime.Start(Array.Empty());
@@ -30,6 +31,7 @@ namespace Avalonia.Controls.UnitTests
public void Should_Close_All_Remaining_Open_Windows_After_Explicit_Exit_Call()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
var windows = new List { new Window(), new Window(), new Window(), new Window() };
@@ -37,9 +39,10 @@ namespace Avalonia.Controls.UnitTests
{
window.Show();
}
- new ClassicDesktopStyleApplicationLifetime(Application.Current).Shutdown();
+ Assert.Equal(4, lifetime.Windows.Count);
+ lifetime.Shutdown();
- Assert.Empty(Application.Current.Windows);
+ Assert.Empty(lifetime.Windows);
}
}
@@ -47,8 +50,8 @@ namespace Avalonia.Controls.UnitTests
public void Should_Only_Exit_On_Explicit_Exit()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
- var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
lifetime.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var hasExit = false;
@@ -81,8 +84,8 @@ namespace Avalonia.Controls.UnitTests
public void Should_Exit_After_MainWindow_Closed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
- var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
lifetime.ShutdownMode = ShutdownMode.OnMainWindowClose;
var hasExit = false;
@@ -109,8 +112,8 @@ namespace Avalonia.Controls.UnitTests
public void Should_Exit_After_Last_Window_Closed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
{
- var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current);
lifetime.ShutdownMode = ShutdownMode.OnLastWindowClose;
var hasExit = false;
@@ -134,6 +137,77 @@ namespace Avalonia.Controls.UnitTests
Assert.True(hasExit);
}
}
+
+ [Fact]
+ public void Show_Should_Add_Window_To_OpenWindows()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+ {
+ var window = new Window();
+
+ window.Show();
+
+ Assert.Equal(new[] { window }, lifetime.Windows);
+ }
+ }
+
+ [Fact]
+ public void Window_Should_Be_Added_To_OpenWindows_Only_Once()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+ {
+ var window = new Window();
+
+ window.Show();
+ window.Show();
+ window.IsVisible = true;
+
+ Assert.Equal(new[] { window }, lifetime.Windows);
+
+ window.Close();
+ }
+ }
+
+ [Fact]
+ public void Close_Should_Remove_Window_From_OpenWindows()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+ {
+ var window = new Window();
+
+ window.Show();
+ Assert.Equal(1, lifetime.Windows.Count);
+ window.Close();
+
+ Assert.Empty(lifetime.Windows);
+ }
+ }
+
+ [Fact]
+ public void Impl_Closing_Should_Remove_Window_From_OpenWindows()
+ {
+ var windowImpl = new Mock();
+ windowImpl.SetupProperty(x => x.Closed);
+ windowImpl.Setup(x => x.Scaling).Returns(1);
+
+ var services = TestServices.StyledWindow.With(
+ windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
+
+ using (UnitTestApplication.Start(services))
+ using(var lifetime = new ClassicDesktopStyleApplicationLifetime(Application.Current))
+ {
+ var window = new Window();
+
+ window.Show();
+ Assert.Equal(1, lifetime.Windows.Count);
+ windowImpl.Object.Closed();
+
+ Assert.Empty(lifetime.Windows);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index 35f60e92cd..f4d9a91d0c 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -121,75 +121,6 @@ namespace Avalonia.Controls.UnitTests
}
}
- [Fact]
- public void Show_Should_Add_Window_To_OpenWindows()
- {
- using (UnitTestApplication.Start(TestServices.StyledWindow))
- {
- ClearOpenWindows();
- var window = new Window();
-
- window.Show();
-
- Assert.Equal(new[] { window }, Application.Current.Windows);
- }
- }
-
- [Fact]
- public void Window_Should_Be_Added_To_OpenWindows_Only_Once()
- {
- using (UnitTestApplication.Start(TestServices.StyledWindow))
- {
- ClearOpenWindows();
- var window = new Window();
-
- window.Show();
- window.Show();
- window.IsVisible = true;
-
- Assert.Equal(new[] { window }, Application.Current.Windows);
-
- window.Close();
- }
- }
-
- [Fact]
- public void Close_Should_Remove_Window_From_OpenWindows()
- {
- using (UnitTestApplication.Start(TestServices.StyledWindow))
- {
- ClearOpenWindows();
- var window = new Window();
-
- window.Show();
- window.Close();
-
- Assert.Empty(Application.Current.Windows);
- }
- }
-
- [Fact]
- public void Impl_Closing_Should_Remove_Window_From_OpenWindows()
- {
- var windowImpl = new Mock();
- windowImpl.SetupProperty(x => x.Closed);
- windowImpl.Setup(x => x.Scaling).Returns(1);
-
- var services = TestServices.StyledWindow.With(
- windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
-
- using (UnitTestApplication.Start(services))
- {
- ClearOpenWindows();
- var window = new Window();
-
- window.Show();
- windowImpl.Object.Closed();
-
- Assert.Empty(Application.Current.Windows);
- }
- }
-
[Fact]
public void Closing_Should_Only_Be_Invoked_Once()
{
@@ -420,12 +351,5 @@ namespace Avalonia.Controls.UnitTests
x.Scaling == 1 &&
x.CreateRenderer(It.IsAny()) == renderer.Object);
}
-
- private void ClearOpenWindows()
- {
- // HACK: We really need a decent way to have "statics" that can be scoped to
- // AvaloniaLocator scopes.
- Application.Current.Windows.CloseAll();
- }
}
}