diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index aba22a4af6..816356bee9 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -94,6 +94,11 @@ namespace ControlCatalog
singleViewLifetime.MainView = new MainView();
base.OnFrameworkInitializationCompleted();
+
+ this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ {
+ StartupScreenIndex = 1,
+ });
}
}
}
diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs
index bdf5b8fbee..a1383b9107 100644
--- a/samples/ControlCatalog/DecoratedWindow.xaml.cs
+++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs
@@ -11,7 +11,6 @@ namespace ControlCatalog
public DecoratedWindow()
{
this.InitializeComponent();
- this.AttachDevTools();
}
void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index dd62698cc7..20591103b7 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -17,10 +17,7 @@ namespace ControlCatalog
public MainWindow()
{
this.InitializeComponent();
- this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
- {
- StartupScreenIndex = 1,
- });
+
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
diff --git a/src/Avalonia.Diagnostics/DevToolsExtensions.cs b/src/Avalonia.Diagnostics/DevToolsExtensions.cs
index aa585dca40..68deaf8335 100644
--- a/src/Avalonia.Diagnostics/DevToolsExtensions.cs
+++ b/src/Avalonia.Diagnostics/DevToolsExtensions.cs
@@ -37,5 +37,49 @@ namespace Avalonia
{
DevTools.Attach(root, options);
}
+
+ ///
+ /// Attaches DevTools to a Application, to be opened with the specified options.
+ ///
+ /// The Application to attach DevTools to.
+ public static void AttachDevTools(this Application application)
+ {
+ DevTools.Attach(application, new DevToolsOptions());
+ }
+
+ ///
+ /// Attaches DevTools to a Application, to be opened with the specified options.
+ ///
+ /// The Application to attach DevTools to.
+ /// Additional settings of DevTools.
+ ///
+ /// Attach DevTools should only be called after application initialization is complete. A good point is
+ ///
+ ///
+ ///
+ /// public class App : Application
+ /// {
+ /// public override void OnFrameworkInitializationCompleted()
+ /// {
+ /// if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+ /// {
+ /// desktopLifetime.MainWindow = new MainWindow();
+ /// }
+ /// else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
+ /// singleViewLifetime.MainView = new MainView();
+ ///
+ /// base.OnFrameworkInitializationCompleted();
+ /// this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ /// {
+ /// StartupScreenIndex = 1,
+ /// });
+ /// }
+ /// }
+ ///
+ ///
+ public static void AttachDevTools(this Application application, DevToolsOptions options)
+ {
+ DevTools.Attach(application, options);
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs b/src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs
new file mode 100644
index 0000000000..beccf022d7
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs
@@ -0,0 +1,47 @@
+namespace Avalonia.Diagnostics.Behaviors
+{
+ ///
+ /// See discussion https://github.com/AvaloniaUI/Avalonia/discussions/6773
+ ///
+ static class ColumnDefinition
+ {
+ private readonly static Avalonia.Controls.GridLength ZeroWidth =
+ new Avalonia.Controls.GridLength(0, Avalonia.Controls.GridUnitType.Pixel);
+
+ private readonly static AttachedProperty LastWidthProperty =
+ AvaloniaProperty.RegisterAttached("LastWidth"
+ , typeof(ColumnDefinition)
+ , default);
+
+ public readonly static AttachedProperty IsVisibleProperty =
+ AvaloniaProperty.RegisterAttached("IsVisible"
+ , typeof(ColumnDefinition)
+ , true
+ , coerce: (element, visibility) =>
+ {
+
+ var lastWidth = element.GetValue(LastWidthProperty);
+ if (visibility == true && lastWidth is { })
+ {
+ element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, lastWidth);
+ }
+ else if (visibility == false)
+ {
+ element.SetValue(LastWidthProperty, element.GetValue(Avalonia.Controls.ColumnDefinition.WidthProperty));
+ element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, ZeroWidth);
+ }
+ return visibility;
+ }
+ );
+
+ public static bool GetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition)
+ {
+ return columnDefinition.GetValue(IsVisibleProperty);
+ }
+
+ public static void SetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition, bool visibility)
+ {
+ columnDefinition.SetValue(IsVisibleProperty, visibility);
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
new file mode 100644
index 0000000000..b2f2974440
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
@@ -0,0 +1,109 @@
+using System;
+using Avalonia.Controls;
+using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
+using App = Avalonia.Application;
+
+namespace Avalonia.Diagnostics.Controls
+{
+ class Application : AvaloniaObject
+ , Input.ICloseable
+
+ {
+ private readonly App _application;
+ private static readonly Version s_version = typeof(IAvaloniaObject).Assembly?.GetName()?.Version
+ ?? Version.Parse("0.0.00");
+ public event EventHandler? Closed;
+
+ public Application(App application)
+ {
+ _application = application;
+
+ if (_application.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller)
+ {
+ EventHandler eh = default!;
+ eh = (s, e) =>
+ {
+ controller.Exit -= eh;
+ Closed?.Invoke(s, e);
+ };
+ controller.Exit += eh;
+ }
+
+ }
+
+ internal App Instance => _application;
+
+ ///
+ /// Defines the property.
+ ///
+ public object? DataContext =>
+ _application.DataContext;
+
+ ///
+ /// Gets or sets the application's global data templates.
+ ///
+ ///
+ /// The application's global data templates.
+ ///
+ public Avalonia.Controls.Templates.DataTemplates DataTemplates =>
+ _application.DataTemplates;
+
+ ///
+ /// Gets the application's focus manager.
+ ///
+ ///
+ /// The application's focus manager.
+ ///
+ public Input.IFocusManager? FocusManager =>
+ _application.FocusManager;
+
+ ///
+ /// Gets the application's input manager.
+ ///
+ ///
+ /// The application's input manager.
+ ///
+ public Input.InputManager? InputManager =>
+ _application.InputManager;
+
+ ///
+ /// Gets the application clipboard.
+ ///
+ public Input.Platform.IClipboard? Clipboard =>
+ _application.Clipboard;
+
+ ///
+ /// Gets the application's global resource dictionary.
+ ///
+ public IResourceDictionary Resources =>
+ _application.Resources;
+
+ ///
+ /// Gets the application's global styles.
+ ///
+ ///
+ /// The application's global styles.
+ ///
+ ///
+ /// Global styles apply to all windows in the application.
+ ///
+ public Styling.Styles Styles =>
+ _application.Styles;
+
+ ///
+ /// Application lifetime, use it for things like setting the main window and exiting the app from code
+ /// Currently supported lifetimes are:
+ /// -
+ /// -
+ /// -
+ ///
+ public Lifetimes.IApplicationLifetime? ApplicationLifetime =>
+ _application.ApplicationLifetime;
+
+ ///
+ /// Application name to be used for various platform-specific purposes
+ ///
+ public string? Name =>
+ _application.Name;
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index 2a386f106e..683c2e6549 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -1,17 +1,21 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
+using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Diagnostics.Views;
using Avalonia.Input;
+using Avalonia.Input.Raw;
using Avalonia.Interactivity;
namespace Avalonia.Diagnostics
{
public static class DevTools
{
- private static readonly Dictionary s_open =
- new Dictionary();
+ private static readonly Dictionary s_open =
+ new Dictionary();
+
+ private static bool s_attachedToApplication;
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
@@ -23,6 +27,11 @@ 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))
@@ -37,45 +46,95 @@ namespace Avalonia.Diagnostics
RoutingStrategies.Tunnel);
}
- public static IDisposable Open(TopLevel root) => Open(root, new DevToolsOptions());
+ 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)
+ private static void DevToolsClosed(object? sender, EventArgs e)
{
- if (s_open.TryGetValue(root, out var window))
+ 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)
+ {
+ if (application is null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+ var result = Disposable.Empty;
+ // Skip if call on Design Mode
+ if (!Avalonia.Controls.Design.IsDesignMode
+ && !s_attachedToApplication)
+ {
+
+ var lifeTime = application.ApplicationLifetime
+ as Avalonia.Controls.ApplicationLifetimes.IControlledApplicationLifetime;
+
+ if (lifeTime is null)
+ {
+ throw new ArgumentNullException(nameof(Application.ApplicationLifetime));
+ }
+
+ if (application.InputManager is { })
+ {
+ s_attachedToApplication = true;
+
+ application.InputManager.PreProcess.OfType().Subscribe(e =>
+ {
+ if (options.Gesture.Matches(e))
+ {
+ result = Open(application, options, owner);
+ }
+ });
+
+ }
+ }
+ return result;
+ }
+
+ private static IDisposable Open(Application? application, DevToolsOptions options, Window? owner = default)
+ {
+ if (application is null)
+ {
+ throw new ArgumentNullException(nameof(application));
+ }
+ if (s_open.TryGetValue(application, out var window))
+ {
window.Activate();
}
else
{
window = new MainWindow
{
- Root = root,
+ Root = new Controls.Application(application),
Width = options.Size.Width,
Height = options.Size.Height,
};
window.SetOptions(options);
window.Closed += DevToolsClosed;
- s_open.Add(root, window);
-
- if (options.ShowAsChildWindow && root is Window inspectedWindow)
+ s_open.Add(application, window);
+ if (options.ShowAsChildWindow && owner is { })
{
- window.Show(inspectedWindow);
+ window.Show(owner);
}
else
{
window.Show();
}
}
-
return Disposable.Create(() => window?.Close());
}
-
- private static void DevToolsClosed(object? sender, EventArgs e)
- {
- var window = (MainWindow)sender!;
- s_open.Remove(window.Root!);
- window.Closed -= DevToolsClosed;
- }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index 5336dca65b..4181989388 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -16,6 +16,7 @@ namespace Avalonia.Diagnostics
/// Gets or sets a value indicating whether DevTools should be displayed as a child window
/// of the window being inspected. The default value is true.
///
+ /// This setting is ignored if DevTools is attached to
public bool ShowAsChildWindow { get; set; } = true;
///
diff --git a/src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs b/src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs
new file mode 100644
index 0000000000..bb3ebc4708
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs
@@ -0,0 +1,28 @@
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+
+namespace Avalonia.Diagnostics
+{
+ static class KeyGestureExtesions
+ {
+ public static bool Matches(this KeyGesture gesture, RawKeyEventArgs keyEvent) =>
+ keyEvent != null &&
+ (KeyModifiers)(keyEvent.Modifiers & RawInputModifiers.KeyboardMask) == gesture.KeyModifiers &&
+ ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(gesture.Key);
+
+ private static Key ResolveNumPadOperationKey(Key key)
+ {
+ switch (key)
+ {
+ case Key.Add:
+ return Key.OemPlus;
+ case Key.Subtract:
+ return Key.OemMinus;
+ case Key.Decimal:
+ return Key.OemPeriod;
+ default:
+ return key;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index 16293e35d2..c96172d5bb 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{
- private readonly IVisual _control;
+ private readonly IAvaloniaObject _avaloniaObject;
private IDictionary