diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
new file mode 100644
index 0000000000..b41755f239
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Reflection;
+using Avalonia.Controls;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Diagnostics
+{
+ static class Convetions
+ {
+ public static string DefaultScreenshotsRoot =>
+ System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),
+ "Screenshots");
+
+ public static IScreenshotHandler DefaultScreenshotHandler { get; } =
+ new Screenshots.FilePickerHandler();
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index 4181989388..33166f1147 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -1,4 +1,5 @@
-using Avalonia.Input;
+using System;
+using Avalonia.Input;
namespace Avalonia.Diagnostics
{
@@ -28,5 +29,12 @@ namespace Avalonia.Diagnostics
/// Get or set the startup screen index where the DevTools window will be displayed.
///
public int? StartupScreenIndex { get; set; }
+
+ ///
+ /// Allow to customizze SreenshotHandler
+ ///
+ /// Default handler is
+ public IScreenshotHandler ScreenshotHandler { get; set; }
+ = Convetions.DefaultScreenshotHandler;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
new file mode 100644
index 0000000000..3503ab56b3
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
@@ -0,0 +1,17 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+
+namespace Avalonia.Diagnostics
+{
+ ///
+ /// Allowed to define custom handler for Shreeshot
+ ///
+ public interface IScreenshotHandler
+ {
+ ///
+ /// Handle the Screenshot
+ ///
+ ///
+ Task Take(IControl control);
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
new file mode 100644
index 0000000000..c111b045d3
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
@@ -0,0 +1,29 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+
+namespace Avalonia.Diagnostics.Screenshots
+{
+ ///
+ /// Base class for render Screenshto to stream
+ ///
+ public abstract class BaseRenderToStreamHandler : IScreenshotHandler
+ {
+
+ ///
+ /// Get stream
+ ///
+ ///
+ /// stream to render the control
+ protected abstract Task GetStream(IControl control);
+
+ public async Task Take(IControl control)
+ {
+ using var output = await GetStream(control);
+ if (output is { })
+ {
+ control.RenderTo(output);
+ await output.FlushAsync();
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
new file mode 100644
index 0000000000..fb57058ae9
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
@@ -0,0 +1,85 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Diagnostics.Screenshots
+{
+ ///
+ /// Show a FileSavePicker to select where save screenshot
+ ///
+ public sealed class FilePickerHandler : BaseRenderToStreamHandler
+ {
+ ///
+ /// Instance FilePickerHandler
+ ///
+ public FilePickerHandler()
+ {
+
+ }
+ ///
+ /// Instance FilePickerHandler with specificated parameter
+ ///
+ /// SaveFilePicker Title
+ ///
+ public FilePickerHandler(string? title
+ , string? screenshotRoot = default
+ )
+ {
+ if (title is { })
+ Title = title;
+ if (screenshotRoot is { })
+ ScreenshotsRoot = screenshotRoot;
+ }
+ ///
+ /// Get the root folder where screeshots well be stored.
+ /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
+ ///
+ public string ScreenshotsRoot { get; }
+ = Convetions.DefaultScreenshotsRoot;
+
+ ///
+ /// SaveFilePicker Title
+ ///
+ public string Title { get; } = "Save Screenshot to ...";
+
+ Window GetWindow(IControl control)
+ {
+ var window = control.VisualRoot as Window;
+ var app = Application.Current;
+ if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow);
+ }
+ return window!;
+ }
+
+ protected async override Task GetStream(IControl control)
+ {
+ Stream? output = default;
+ var result = await new SaveFileDialog()
+ {
+ Title = Title,
+ Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } },
+ Directory = ScreenshotsRoot,
+ }.ShowAsync(GetWindow(control));
+ if (!string.IsNullOrWhiteSpace(result))
+ {
+ var foldler = Path.GetDirectoryName(result);
+ // Directory information for path, or null if path denotes a root directory or is
+ // null. Returns System.String.Empty if path does not contain directory information.
+ if (!string.IsNullOrWhiteSpace(foldler))
+ {
+ if (!Directory.Exists(foldler))
+ {
+ Directory.CreateDirectory(foldler);
+ }
+ output = new FileStream(result, FileMode.Create);
+ }
+ }
+ return output;
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index 8bc13a9525..c96ddf129e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -1,8 +1,11 @@
using System;
using System.ComponentModel;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
+using Avalonia.Metadata;
using Avalonia.Threading;
using System.Reactive.Linq;
using System.Linq;
@@ -26,7 +29,9 @@ namespace Avalonia.Diagnostics.ViewModels
private bool _freezePopups;
private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot;
+ private IScreenshotHandler? _screenshotHandler;
private bool _showPropertyType;
+
public MainViewModel(AvaloniaObject root)
{
_root = root;
@@ -285,9 +290,38 @@ namespace Avalonia.Diagnostics.ViewModels
}
public int? StartupScreenIndex { get; private set; } = default;
+
+ [DependsOn(nameof(TreePageViewModel.SelectedNode))]
+ [DependsOn(nameof(Content))]
+ bool CanShot(object? parameter)
+ {
+ return Content is TreePageViewModel tree
+ && tree.SelectedNode != null
+ && tree.SelectedNode.Visual is VisualTree.IVisual visual
+ && visual.VisualRoot != null;
+ }
+
+ async void Shot(object? parameter)
+ {
+ if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control
+ && _screenshotHandler is { }
+ )
+ {
+ try
+ {
+ await _screenshotHandler.Take(control);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine(ex.Message);
+ //TODO: Notify error
+ }
+ }
+ }
public void SetOptions(DevToolsOptions options)
{
+ _screenshotHandler = options.ScreenshotHandler;
StartupScreenIndex = options.StartupScreenIndex;
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index 381fc68cf3..91c8eb0983 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -6,6 +6,36 @@