Browse Source

Merge pull request #10510 from workgroupengineering/fixes/DevTools/WithoutApplicationLifetime

fix(DevTools): Debug Application without ApplicationLifetime
pull/11304/head
Max Katz 3 years ago
committed by GitHub
parent
commit
2130689cb5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Avalonia.Desktop.slnf
  2. 6
      Avalonia.sln
  3. 7
      samples/AppWithoutLifetime/App.axaml
  4. 12
      samples/AppWithoutLifetime/App.axaml.cs
  5. 21
      samples/AppWithoutLifetime/AppWithoutLifetime.csproj
  6. 13
      samples/AppWithoutLifetime/MainWindow.axaml
  7. 30
      samples/AppWithoutLifetime/MainWindow.axaml.cs
  8. 28
      samples/AppWithoutLifetime/Program.cs
  9. 9
      samples/AppWithoutLifetime/Sub.axaml
  10. 24
      samples/AppWithoutLifetime/Sub.axaml.cs
  11. 18
      samples/AppWithoutLifetime/app.manifest
  12. 4
      samples/IntegrationTestApp/MainWindow.axaml.cs
  13. 13
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  14. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  15. 1
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  16. 5
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  17. 41
      src/Avalonia.Diagnostics/Diagnostics/Controls/TopLevelGroup.cs
  18. 143
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  19. 56
      src/Avalonia.Diagnostics/Diagnostics/IDevToolsTopLevelGroup.cs
  20. 87
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

1
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",

6
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}

7
samples/AppWithoutLifetime/App.axaml

@ -0,0 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AppWithoutLifetime.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

12
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);
}
}

21
samples/AppWithoutLifetime/AppWithoutLifetime.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

13
samples/AppWithoutLifetime/MainWindow.axaml

@ -0,0 +1,13 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AppWithoutLifetime.MainWindow"
Title="AppWithoutLifetime">
<StackPanel>
<TextBlock Text="Welcome to Avalonia!"/>
<CheckBox Content="Welcome to Avalonia!"/>
<Button Click="Open" Content="Open"/>
</StackPanel>
</Window>

30
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);
}
}

28
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<App>()
.UsePlatformDetect()
.LogToTrace();
}

9
samples/AppWithoutLifetime/Sub.axaml

@ -0,0 +1,9 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AppWithoutLifetime.Sub"
Title="Window1">
Welcome to Avalonia Sub!
</Window>

24
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();
}
}

18
samples/AppWithoutLifetime/app.manifest

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="AppWithoutLifetime"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

4
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)

13
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<Window> _windows = new();
private readonly AvaloniaList<Window> _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; }
/// <inheritdoc />
public IReadOnlyList<Window> Windows => _windows.ToArray();
public IReadOnlyList<Window> 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)
{

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -15,6 +15,7 @@
<InternalsVisibleTo Include="Avalonia.Controls.ItemsRepeater, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)"/>
<InternalsVisibleTo Include="Avalonia.LeakTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Headless, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Native, PublicKey=$(AvaloniaPublicKey)" />

1
src/Avalonia.Controls/DesktopApplicationExtensions.cs

@ -1,6 +1,5 @@
using System;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;

5
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<ThemeVariant?> RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
public Application(Avalonia.Application application)
public Application(ClassicDesktopStyleApplicationLifetimeTopLevelGroup group, Avalonia.Application application)
: base(group)
{
_application = application;

41
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<TopLevel> Items => Group.Items;
public event EventHandler<TopLevel>? Added;
public event EventHandler<TopLevel>? Removed;
}

143
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<AvaloniaObject, MainWindow> s_open =
new Dictionary<AvaloniaObject, MainWindow>();
private static bool s_attachedToApplication;
private static readonly Dictionary<IDevToolsTopLevelGroup, MainWindow> 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!);
}
}
}

56
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<TopLevel> Items { get; }
}
internal class ClassicDesktopStyleApplicationLifetimeTopLevelGroup : IDevToolsTopLevelGroup
{
private readonly IClassicDesktopStyleApplicationLifetime _lifetime;
public ClassicDesktopStyleApplicationLifetimeTopLevelGroup(IClassicDesktopStyleApplicationLifetime lifetime)
{
_lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
}
public IReadOnlyList<TopLevel> 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<TopLevel> Items { get; }
public override int GetHashCode()
{
return _topLevel.GetHashCode();
}
public override bool Equals(object? obj)
{
return obj is SingleViewTopLevelGroup g && g._topLevel == _topLevel;
}
}

87
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<TreeNode> 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()

Loading…
Cancel
Save