Browse Source

fix(DevTools): Debug Application without ApplicationLifetime

Giuseppe Lippolis 2 years ago
parent
commit
7f9cbb6aed

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

+ 12 - 5
Avalonia.sln

@@ -244,13 +244,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepe
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators", "src\tools\Avalonia.Generators\Avalonia.Generators.csproj", "{DDA28789-C21A-4654-86CE-D01E81F095C5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Generators", "src\tools\Avalonia.Generators\Avalonia.Generators.csproj", "{DDA28789-C21A-4654-86CE-D01E81F095C5}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Fonts.Inter", "src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj", "{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Fonts.Inter", "src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj", "{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generators.Sandbox", "samples\Generators.Sandbox\Generators.Sandbox.csproj", "{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}"
+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}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -595,6 +597,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
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -661,10 +667,11 @@ Global
 		{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+		{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
-		{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+		{F8928267-688E-4A51-989C-612A72446D33} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 7 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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>

+ 1 - 0
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)" />
   </ItemGroup>
 </Project>

+ 0 - 1
src/Avalonia.Controls/DesktopApplicationExtensions.cs

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

+ 38 - 40
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@@ -9,7 +9,7 @@ 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>();
@@ -28,7 +28,7 @@ namespace Avalonia.Diagnostics
         {
             if (s_attachedToApplication == true)
             {
-                throw new ArgumentException("DevTools already attached to application", nameof(root));
+                throw new ArgumentException("DevTools already attached to application.", nameof(root));
             }
 
             void PreviewKeyDown(object? sender, KeyEventArgs e)
@@ -45,50 +45,31 @@ namespace Avalonia.Diagnostics
                 RoutingStrategies.Tunnel);
         }
 
-        public static IDisposable Open(TopLevel root) => 
-            Open(Application.Current,new DevToolsOptions(),root as Window);
+        private static IDisposable Open(TopLevel root, DevToolsOptions options) =>
+             Open(default, options, root);
 
-        public static IDisposable Open(TopLevel root, DevToolsOptions options) => 
-            Open(Application.Current, options, root as Window);
-
-        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 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
+            if (!Design.IsDesignMode
                 && !s_attachedToApplication)
             {
 
                 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.");
                 }
 
+                var owner = TopLevel.GetTopLevel(lifeTime.MainWindow)
+                    ?? throw new ArgumentException(nameof(application), "It can't retrieve TopLevel.");
+
                 if (application.InputManager is { })
                 {
                     s_attachedToApplication = true;
@@ -102,20 +83,23 @@ namespace Avalonia.Diagnostics
                             openedDisposable.Disposable = Open(application, options, owner);
                         }
                     }));
-
                 }
             }
             return result;
         }
 
-        private static IDisposable Open(Application? application, DevToolsOptions options, Window? owner = default)
+        private static IDisposable Open(Application? application, DevToolsOptions options, TopLevel owner)
         {
             var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control;
-            if (application is null)
+            AvaloniaObject root = owner;
+            AvaloniaObject key = owner;
+            if (application is not null)
             {
-                throw new ArgumentNullException(nameof(application));
+                root = new Controls.Application(application);
+                key = application;
             }
-            if (s_open.TryGetValue(application, out var window))
+
+            if (s_open.TryGetValue(key, out var window))
             {
                 window.Activate();
                 window.SelectedControl(focussedControl);
@@ -124,17 +108,17 @@ namespace Avalonia.Diagnostics
             {
                 window = new MainWindow
                 {
-                    Root = new Controls.Application(application),
+                    Root = root,
                     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 { })
+                s_open.Add(key, window);
+                if (options.ShowAsChildWindow && owner is Window ow)
                 {
-                    window.Show(owner);
+                    window.Show(ow);
                 }
                 else
                 {
@@ -143,5 +127,19 @@ namespace Avalonia.Diagnostics
             }
             return Disposable.Create(() => window?.Close());
         }
+
+        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!);
+            }
+        }
     }
 }