diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 73e38f8cb9..92c924f107 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -3,6 +3,7 @@
"path": "Avalonia.sln",
"projects": [
"packages\\Avalonia\\Avalonia.csproj",
+ "samples\\AppWithoutLifetime\\AppWithoutLifetime.csproj",
"samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
"samples\\ControlCatalog\\ControlCatalog.csproj",
"samples\\GpuInterop\\GpuInterop.csproj",
diff --git a/Avalonia.sln b/Avalonia.sln
index 8015a5a007..9670327d67 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -250,6 +250,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Fonts.Inter", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators.Sandbox", "samples\Generators.Sandbox\Generators.Sandbox.csproj", "{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppWithoutLifetime", "samples\AppWithoutLifetime\AppWithoutLifetime.csproj", "{F8928267-688E-4A51-989C-612A72446D33}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo", "samples\SafeAreaDemo\SafeAreaDemo.csproj", "{6B60A970-D5D2-49C2-8BAB-F9C7973B74B6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.Android", "samples\SafeAreaDemo.Android\SafeAreaDemo.Android.csproj", "{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D}"
@@ -613,6 +614,10 @@ Global
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8928267-688E-4A51-989C-612A72446D33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8928267-688E-4A51-989C-612A72446D33}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8928267-688E-4A51-989C-612A72446D33}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8928267-688E-4A51-989C-612A72446D33}.Release|Any CPU.Build.0 = Release|Any CPU
{6B60A970-D5D2-49C2-8BAB-F9C7973B74B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B60A970-D5D2-49C2-8BAB-F9C7973B74B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B60A970-D5D2-49C2-8BAB-F9C7973B74B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -727,6 +732,7 @@ Global
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {F8928267-688E-4A51-989C-612A72446D33} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{6B60A970-D5D2-49C2-8BAB-F9C7973B74B6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098}
diff --git a/samples/AppWithoutLifetime/App.axaml b/samples/AppWithoutLifetime/App.axaml
new file mode 100644
index 0000000000..5f86b8be93
--- /dev/null
+++ b/samples/AppWithoutLifetime/App.axaml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/samples/AppWithoutLifetime/App.axaml.cs b/samples/AppWithoutLifetime/App.axaml.cs
new file mode 100644
index 0000000000..9cc99929c4
--- /dev/null
+++ b/samples/AppWithoutLifetime/App.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia;
+using Avalonia.Markup.Xaml;
+
+namespace AppWithoutLifetime;
+
+public partial class App : Application
+{
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
diff --git a/samples/AppWithoutLifetime/AppWithoutLifetime.csproj b/samples/AppWithoutLifetime/AppWithoutLifetime.csproj
new file mode 100644
index 0000000000..fce12af298
--- /dev/null
+++ b/samples/AppWithoutLifetime/AppWithoutLifetime.csproj
@@ -0,0 +1,21 @@
+
+
+ WinExe
+ net6.0
+ enable
+ app.manifest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/AppWithoutLifetime/MainWindow.axaml b/samples/AppWithoutLifetime/MainWindow.axaml
new file mode 100644
index 0000000000..3f31cb7fae
--- /dev/null
+++ b/samples/AppWithoutLifetime/MainWindow.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/samples/AppWithoutLifetime/MainWindow.axaml.cs b/samples/AppWithoutLifetime/MainWindow.axaml.cs
new file mode 100644
index 0000000000..d27d5d5653
--- /dev/null
+++ b/samples/AppWithoutLifetime/MainWindow.axaml.cs
@@ -0,0 +1,30 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace AppWithoutLifetime;
+
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ protected override void OnLoaded()
+ {
+ this.AttachDevTools();
+ base.OnLoaded();
+ }
+
+ public void Open(object sender, RoutedEventArgs e)
+ {
+ new Sub().Show(this);
+ }
+}
diff --git a/samples/AppWithoutLifetime/Program.cs b/samples/AppWithoutLifetime/Program.cs
new file mode 100644
index 0000000000..9404b9f01a
--- /dev/null
+++ b/samples/AppWithoutLifetime/Program.cs
@@ -0,0 +1,28 @@
+using Avalonia;
+using Avalonia.Controls;
+using System;
+
+namespace AppWithoutLifetime;
+
+class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args)
+ {
+ BuildAvaloniaApp().Start(AppMain, args);
+ }
+
+ private static void AppMain(Application app, string[] args)
+ {
+ app.Run(new MainWindow());
+ }
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace();
+}
diff --git a/samples/AppWithoutLifetime/Sub.axaml b/samples/AppWithoutLifetime/Sub.axaml
new file mode 100644
index 0000000000..d0061c432c
--- /dev/null
+++ b/samples/AppWithoutLifetime/Sub.axaml
@@ -0,0 +1,9 @@
+
+ Welcome to Avalonia Sub!
+
diff --git a/samples/AppWithoutLifetime/Sub.axaml.cs b/samples/AppWithoutLifetime/Sub.axaml.cs
new file mode 100644
index 0000000000..50c770b3a2
--- /dev/null
+++ b/samples/AppWithoutLifetime/Sub.axaml.cs
@@ -0,0 +1,24 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace AppWithoutLifetime;
+
+public partial class Sub : Window
+{
+ public Sub()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ protected override void OnLoaded()
+ {
+ this.AttachDevTools();
+ base.OnLoaded();
+ }
+}
diff --git a/samples/AppWithoutLifetime/app.manifest b/samples/AppWithoutLifetime/app.manifest
new file mode 100644
index 0000000000..d653ddd4b4
--- /dev/null
+++ b/samples/AppWithoutLifetime/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 39497f1811..7130b3602a 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -202,7 +202,7 @@ namespace IntegrationTestApp
{
var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
- foreach (var window in lifetime.Windows)
+ foreach (var window in lifetime.Windows.ToArray())
{
window.Activate();
}
@@ -212,7 +212,7 @@ namespace IntegrationTestApp
{
var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!;
- foreach (var window in lifetime.Windows)
+ foreach (var window in lifetime.Windows.ToArray())
{
window.Show();
if (window.WindowState == WindowState.Minimized)
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
index 9cc36e6a81..77f3b93efa 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
+using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
@@ -16,7 +17,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
private int _exitCode;
private CancellationTokenSource? _cts;
private bool _isShuttingDown;
- private readonly HashSet _windows = new();
+ private readonly AvaloniaList _windows = new();
private static ClassicDesktopStyleApplicationLifetime? s_activeLifetime;
@@ -35,7 +36,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
private static void OnWindowOpened(object? sender, RoutedEventArgs e)
{
- s_activeLifetime?._windows.Add((Window)sender!);
+ var window = (Window)sender!;
+ if (s_activeLifetime is not null && !s_activeLifetime._windows.Contains(window))
+ {
+ s_activeLifetime._windows.Add(window);
+ }
}
public ClassicDesktopStyleApplicationLifetime()
@@ -67,7 +72,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
public Window? MainWindow { get; set; }
///
- public IReadOnlyList Windows => _windows.ToArray();
+ public IReadOnlyList Windows => _windows;
private void HandleWindowClosed(Window? window)
{
@@ -161,7 +166,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
// 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 Windows)
+ foreach (var w in Windows.ToArray())
{
if (w.Owner is null)
{
diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index 42e965409a..48761ca8b8 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs
index ddaed6bb0d..ce246f0532 100644
--- a/src/Avalonia.Controls/DesktopApplicationExtensions.cs
+++ b/src/Avalonia.Controls/DesktopApplicationExtensions.cs
@@ -1,6 +1,5 @@
using System;
using System.Threading;
-using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
index fb0504ba1e..0791815120 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
@@ -5,7 +5,7 @@ using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Diagnostics.Controls
{
- class Application : AvaloniaObject
+ internal class Application : TopLevelGroup
, Input.ICloseable, IDisposable
{
@@ -16,7 +16,8 @@ namespace Avalonia.Diagnostics.Controls
public static readonly StyledProperty RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner();
- public Application(Avalonia.Application application)
+ public Application(ClassicDesktopStyleApplicationLifetimeTopLevelGroup group, Avalonia.Application application)
+ : base(group)
{
_application = application;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/TopLevelGroup.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/TopLevelGroup.cs
new file mode 100644
index 0000000000..d43b5b07b0
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/TopLevelGroup.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.Controls;
+
+namespace Avalonia.Diagnostics.Controls;
+
+internal class TopLevelGroup : AvaloniaObject
+{
+ public TopLevelGroup(IDevToolsTopLevelGroup group)
+ {
+ Group = group;
+ if (Group.Items is INotifyCollectionChanged incc)
+ {
+ incc.CollectionChanged += (_, args) =>
+ {
+ if (args.OldItems is not null)
+ {
+ foreach (TopLevel oldItem in args.OldItems)
+ {
+ Removed?.Invoke(this, oldItem);
+ }
+ }
+
+ if (args.NewItems is not null)
+ {
+ foreach (TopLevel newItem in args.NewItems)
+ {
+ Added?.Invoke(this, newItem);
+ }
+ }
+ };
+ }
+ }
+
+ public IDevToolsTopLevelGroup Group { get; }
+
+ public IReadOnlyList Items => Group.Items;
+ public event EventHandler? Added;
+ public event EventHandler? Removed;
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index e8cdcfa37b..cb896fd633 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Diagnostics.Views;
using Avalonia.Input;
@@ -9,12 +11,9 @@ using Avalonia.Reactive;
namespace Avalonia.Diagnostics
{
- public static class DevTools
+ internal static class DevTools
{
- private static readonly Dictionary s_open =
- new Dictionary();
-
- private static bool s_attachedToApplication;
+ private static readonly Dictionary s_open = new();
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
@@ -26,11 +25,6 @@ namespace Avalonia.Diagnostics
public static IDisposable Attach(TopLevel root, DevToolsOptions options)
{
- if (s_attachedToApplication == true)
- {
- throw new ArgumentException("DevTools already attached to application", nameof(root));
- }
-
void PreviewKeyDown(object? sender, KeyEventArgs e)
{
if (options.Gesture.Matches(e))
@@ -39,109 +33,116 @@ namespace Avalonia.Diagnostics
}
}
- return root.AddDisposableHandler(
+ return (root ?? throw new ArgumentNullException(nameof(root))).AddDisposableHandler(
InputElement.KeyDownEvent,
PreviewKeyDown,
RoutingStrategies.Tunnel);
}
- public static IDisposable Open(TopLevel root) =>
- Open(Application.Current,new DevToolsOptions(),root as Window);
-
- public static IDisposable Open(TopLevel root, DevToolsOptions options) =>
- Open(Application.Current, options, root as Window);
+ public static IDisposable Open(TopLevel root, DevToolsOptions options) =>
+ Open(new SingleViewTopLevelGroup(root), options, root as Window, null);
- private static void DevToolsClosed(object? sender, EventArgs e)
- {
- var window = (MainWindow)sender!;
- window.Closed -= DevToolsClosed;
- if (window.Root is Controls.Application host)
- {
- s_open.Remove(host.Instance);
- }
- else
- {
- s_open.Remove(window.Root!);
- }
- }
+ internal static IDisposable Open(IDevToolsTopLevelGroup group, DevToolsOptions options) =>
+ Open(group, options, null, null);
- internal static IDisposable Attach(Application? application, DevToolsOptions options, Window? owner = null)
+ internal static IDisposable Attach(Application application, DevToolsOptions options)
{
- if (application is null)
- {
- throw new ArgumentNullException(nameof(application));
- }
-
var openedDisposable = new SerialDisposableValue();
var result = new CompositeDisposable(2);
result.Add(openedDisposable);
-
+
// Skip if call on Design Mode
- if (!Avalonia.Controls.Design.IsDesignMode
- && !s_attachedToApplication)
+ if (!Design.IsDesignMode)
{
-
var lifeTime = application.ApplicationLifetime
- as Avalonia.Controls.ApplicationLifetimes.IControlledApplicationLifetime;
+ as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime;
if (lifeTime is null)
{
- throw new ArgumentNullException(nameof(Application.ApplicationLifetime));
+ throw new ArgumentNullException(nameof(application), "DevTools can only attach to applications that support IClassicDesktopStyleApplicationLifetime.");
}
- if (application.InputManager is { })
+ if (application.InputManager is not null)
{
- s_attachedToApplication = true;
-
result.Add(application.InputManager.PreProcess.Subscribe(e =>
{
+ var owner = lifeTime.MainWindow;
+
if (e is RawKeyEventArgs keyEventArgs
&& keyEventArgs.Type == RawKeyEventType.KeyUp
&& options.Gesture.Matches(keyEventArgs))
{
- openedDisposable.Disposable = Open(application, options, owner);
+ openedDisposable.Disposable =
+ Open(new ClassicDesktopStyleApplicationLifetimeTopLevelGroup(lifeTime), options,
+ owner, application);
+ e.Handled = true;
}
}));
-
}
}
return result;
}
- private static IDisposable Open(Application? application, DevToolsOptions options, Window? owner = default)
+ private static IDisposable Open(IDevToolsTopLevelGroup topLevelGroup, DevToolsOptions options,
+ Window? owner, Application? app)
{
var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control;
- if (application is null)
+ AvaloniaObject root = topLevelGroup switch
{
- throw new ArgumentNullException(nameof(application));
- }
- if (s_open.TryGetValue(application, out var window))
+ ClassicDesktopStyleApplicationLifetimeTopLevelGroup gr => new Controls.Application(gr, app ?? Application.Current!),
+ SingleViewTopLevelGroup gr => gr.Items.First(),
+ _ => new Controls.TopLevelGroup(topLevelGroup)
+ };
+
+ // If single static toplevel is already visible in another devtools window, focus it.
+ if (s_open.TryGetValue(topLevelGroup, out var mainWindow))
{
- window.Activate();
- window.SelectedControl(focussedControl);
+ mainWindow.Activate();
+ mainWindow.SelectedControl(focussedControl);
+ return Disposable.Empty;
}
- else
+ if (topLevelGroup.Items.Count == 1 && topLevelGroup.Items is not INotifyCollectionChanged)
{
- window = new MainWindow
- {
- Root = new Controls.Application(application),
- Width = options.Size.Width,
- Height = options.Size.Height,
- };
- window.SetOptions(options);
- window.SelectedControl(focussedControl);
- window.Closed += DevToolsClosed;
- s_open.Add(application, window);
- if (options.ShowAsChildWindow && owner is { })
+ var singleTopLevel = topLevelGroup.Items.First();
+
+ foreach (var group in s_open)
{
- window.Show(owner);
- }
- else
- {
- window.Show();
+ if (group.Key.Items.Contains(singleTopLevel))
+ {
+ group.Value.Activate();
+ group.Value.SelectedControl(focussedControl);
+ return Disposable.Empty;
+ }
}
}
- return Disposable.Create(() => window?.Close());
+
+ var window = new MainWindow
+ {
+ Root = root,
+ Width = options.Size.Width,
+ Height = options.Size.Height,
+ Tag = topLevelGroup
+ };
+ window.SetOptions(options);
+ window.SelectedControl(focussedControl);
+ window.Closed += DevToolsClosed;
+ s_open.Add(topLevelGroup, window);
+ if (options.ShowAsChildWindow && owner is not null)
+ {
+ window.Show(owner);
+ }
+ else
+ {
+ window.Show();
+ }
+ return Disposable.Create(() => window.Close());
+ }
+
+ private static void DevToolsClosed(object? sender, EventArgs e)
+ {
+ var window = (MainWindow)sender!;
+ window.Closed -= DevToolsClosed;
+ s_open.Remove((IDevToolsTopLevelGroup)window.Tag!);
}
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/IDevToolsTopLevelGroup.cs b/src/Avalonia.Diagnostics/Diagnostics/IDevToolsTopLevelGroup.cs
new file mode 100644
index 0000000000..401b986e0d
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/IDevToolsTopLevelGroup.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Diagnostics;
+
+internal interface IDevToolsTopLevelGroup
+{
+ IReadOnlyList Items { get; }
+}
+
+internal class ClassicDesktopStyleApplicationLifetimeTopLevelGroup : IDevToolsTopLevelGroup
+{
+ private readonly IClassicDesktopStyleApplicationLifetime _lifetime;
+
+ public ClassicDesktopStyleApplicationLifetimeTopLevelGroup(IClassicDesktopStyleApplicationLifetime lifetime)
+ {
+ _lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
+ }
+
+ public IReadOnlyList Items => _lifetime.Windows;
+
+ public override int GetHashCode()
+ {
+ return _lifetime.GetHashCode();
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is ClassicDesktopStyleApplicationLifetimeTopLevelGroup g && g._lifetime == _lifetime;
+ }
+}
+
+internal class SingleViewTopLevelGroup : IDevToolsTopLevelGroup
+{
+ private readonly TopLevel _topLevel;
+
+ public SingleViewTopLevelGroup(TopLevel topLevel)
+ {
+ _topLevel = topLevel;
+ Items = new[] { topLevel ?? throw new ArgumentNullException(nameof(topLevel)) };
+ }
+
+ public IReadOnlyList Items { get; }
+
+ public override int GetHashCode()
+ {
+ return _topLevel.GetHashCode();
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is SingleViewTopLevelGroup g && g._topLevel == _topLevel;
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
index 3048431af9..3558f916f8 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Diagnostics.ViewModels
Children = avaloniaObject switch
{
ILogical logical => new LogicalTreeNodeCollection(this, logical),
- Controls.Application host => new ApplicationHostLogical(this, host),
+ Controls.TopLevelGroup host => new TopLevelGroupHostLogical(this, host),
_ => TreeNodeCollection.Empty
};
}
@@ -55,70 +55,55 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- internal class ApplicationHostLogical : TreeNodeCollection
+ internal class TopLevelGroupHostLogical : TreeNodeCollection
{
- readonly Controls.Application _application;
- CompositeDisposable _subscriptions = new CompositeDisposable(2);
- public ApplicationHostLogical(TreeNode owner, Controls.Application host) :
+ private readonly Controls.TopLevelGroup _group;
+ private readonly CompositeDisposable _subscriptions = new(1);
+
+ public TopLevelGroupHostLogical(TreeNode owner, Controls.TopLevelGroup host) :
base(owner)
{
- _application = host;
+ _group = host;
}
protected override void Initialize(AvaloniaList nodes)
{
- if (_application.ApplicationLifetime is Lifetimes.ISingleViewApplicationLifetime single &&
- single.MainView is not null)
+ for (var i = 0; i < _group.Items.Count; i++)
{
- nodes.Add(new LogicalTreeNode(single.MainView, Owner));
+ var window = _group.Items[i];
+ if (window is Views.MainWindow)
+ {
+ continue;
+ }
+ nodes.Add(new LogicalTreeNode(window, Owner));
}
- if (_application.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime classic)
+ void GroupOnAdded(object? sender, TopLevel e)
{
-
- for (int i = 0; i < classic.Windows.Count; i++)
+ if (e is Views.MainWindow)
{
- var window = classic.Windows[i];
- if (window is Views.MainWindow)
- {
- continue;
- }
- nodes.Add(new LogicalTreeNode(window, Owner));
+ return;
}
- _subscriptions = new CompositeDisposable(2)
+
+ nodes.Add(new LogicalTreeNode(e, Owner));
+ }
+ void GroupOnRemoved(object? sender, TopLevel e)
+ {
+ if (e is Views.MainWindow)
{
- Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=>
- {
- if (s is Views.MainWindow)
- {
- return;
- }
- nodes.Add(new LogicalTreeNode((AvaloniaObject)s!,Owner));
- }),
- Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=>
- {
- if (s is Views.MainWindow)
- {
- return;
- }
- var item = nodes.FirstOrDefault(node=>object.ReferenceEquals(node.Visual,s));
- if(!(item is null))
- {
- nodes.Remove(item);
- }
- if(nodes.Count == 0)
- {
- if (Avalonia.Application.Current?.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller)
- {
- controller.Shutdown();
- }
- else
- {
- Environment.Exit(0);
- }
- }
- }),
- };
+ return;
+ }
+
+ nodes.Add(new LogicalTreeNode(e, Owner));
}
+
+ _group.Added += GroupOnAdded;
+ _group.Removed += GroupOnRemoved;
+
+ _subscriptions.Add(new Disposable.AnonymousDisposable(() =>
+ {
+ _group.Added -= GroupOnAdded;
+ _group.Removed -= GroupOnRemoved;
+ }));
}
public override void Dispose()