From 7b8122302dc25ac046380c9caca5352cf2d9ba9e Mon Sep 17 00:00:00 2001 From: "Konstiantyn.Usenko" Date: Sun, 2 Nov 2025 18:01:03 +0100 Subject: [PATCH 01/17] Add UI elements for copying current element state and display notification --- src/FlaUInspect/Resources/RibbonIcons.xaml | 245 ++++++++++++----- src/FlaUInspect/ViewModels/MainViewModel.cs | 49 ++++ src/FlaUInspect/Views/MainWindow.xaml | 286 +++++++++++++------- src/FlaUInspect/Views/MainWindow.xaml.cs | 10 + 4 files changed, 415 insertions(+), 175 deletions(-) diff --git a/src/FlaUInspect/Resources/RibbonIcons.xaml b/src/FlaUInspect/Resources/RibbonIcons.xaml index 6eca9c6..64a96ff 100644 --- a/src/FlaUInspect/Resources/RibbonIcons.xaml +++ b/src/FlaUInspect/Resources/RibbonIcons.xaml @@ -2,43 +2,47 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> - + - + - + - + - + @@ -46,155 +50,246 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FlaUInspect/ViewModels/MainViewModel.cs b/src/FlaUInspect/ViewModels/MainViewModel.cs index 2cbdae8..c05cabc 100644 --- a/src/FlaUInspect/ViewModels/MainViewModel.cs +++ b/src/FlaUInspect/ViewModels/MainViewModel.cs @@ -3,8 +3,10 @@ using System.Drawing; using System.Drawing.Imaging; using System.Reflection; +using System.Windows; using System.Windows.Data; using System.Windows.Input; +using System.Xml.Linq; using FlaUI.Core; using FlaUI.Core.AutomationElements; using FlaUI.Core.Identifiers; @@ -37,6 +39,7 @@ public class MainViewModel : ObservableObject { private AutomationElement? _rootElement; private RelayCommand? _startNewInstanceCommand; private ITreeWalker? _treeWalker; + private RelayCommand? _currentElementSaveStateCommand; public MainViewModel(AutomationType automationType, string applicationVersion, InternalLogger logger) { _logger = logger; @@ -179,6 +182,52 @@ public string? ApplicationVersion { public ICommand CloseInfoCommand => _closeInfoCommand ??= new RelayCommand(_ => { IsInfoVisible = false; }); + + public event Action? CopiedNotificationRequested; + + public ICommand CurrentElementSaveStateCommand => _currentElementSaveStateCommand ??= new RelayCommand(_ => { + if (SelectedItem?.AutomationElement == null) { + return; + } + + try { + XDocument document = new (); + document.Add(new XElement("Root")); + + foreach (ElementPatternItem elementPatternItem in ElementPatterns) { + XElement patternNode = new ("Pattern", + new XAttribute("Name", elementPatternItem.PatternName), + new XAttribute("Id", elementPatternItem.PatternIdName)); + + foreach (PatternItem patternItem in elementPatternItem.Children) { + XElement itemNode = new ("Item", + new XAttribute("Key", patternItem.Key), + new XAttribute("Value", patternItem.Value ?? string.Empty)); + patternNode.Add(itemNode); + } + + if (patternNode.HasElements) { + document.Root!.Add(patternNode); + } + } + Clipboard.SetText(document.ToString()); + CopiedNotificationRequested?.Invoke(); + } catch (Exception e) { + _logger?.LogError(e.ToString()); + } + }); + + public ICommand CollapseAllDetailsCommand => new RelayCommand(_ => { + foreach (ElementPatternItem pattern in ElementPatterns) { + pattern.IsExpanded = false; + } + }); + + public ICommand ExpandAllDetailsCommand => new RelayCommand(_ => { + foreach (ElementPatternItem pattern in ElementPatterns) { + pattern.IsExpanded = true; + } + }); private void ReadPatternsForSelectedItem(AutomationElement? selectedItemAutomationElement) { if (SelectedItem?.AutomationElement == null || selectedItemAutomationElement == null) { diff --git a/src/FlaUInspect/Views/MainWindow.xaml b/src/FlaUInspect/Views/MainWindow.xaml index 824f95d..b8f5c6a 100644 --- a/src/FlaUInspect/Views/MainWindow.xaml +++ b/src/FlaUInspect/Views/MainWindow.xaml @@ -114,6 +114,18 @@ HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/FlaUInspect/Views/MainWindow.xaml.cs b/src/FlaUInspect/Views/MainWindow.xaml.cs index 121e820..d0aaf2c 100644 --- a/src/FlaUInspect/Views/MainWindow.xaml.cs +++ b/src/FlaUInspect/Views/MainWindow.xaml.cs @@ -1,5 +1,6 @@ using System.Windows; using System.Windows.Controls; +using System.Windows.Media.Animation; using FlaUInspect.ViewModels; namespace FlaUInspect.Views; @@ -13,6 +14,7 @@ public MainWindow() { private void MainWindow_Loaded(object sender, EventArgs e) { if (DataContext is MainViewModel mainViewModel) { mainViewModel.Initialize(); + mainViewModel.CopiedNotificationRequested += ShowCopiedNotification; } } @@ -36,4 +38,12 @@ private void InvokePatternActionHandler(object sender, RoutedEventArgs e) { }); } } + + private async void ShowCopiedNotification() { + CopiedNotificationGrid.Visibility = Visibility.Visible; + var animation = new DoubleAnimation(1, 0, TimeSpan.FromSeconds(1)); + CopiedNotificationGrid.BeginAnimation(UIElement.OpacityProperty, animation); + await Task.Delay(1000); + CopiedNotificationGrid.Visibility = Visibility.Collapsed; + } } \ No newline at end of file From 616330b63786bd8c3c501a85d9ee0124a9df6a2f Mon Sep 17 00:00:00 2001 From: "Konstiantyn.Usenko" Date: Sun, 2 Nov 2025 18:44:04 +0100 Subject: [PATCH 02/17] Add clipboard functionality and notification for copying element state --- src/FlaUInspect/ViewModels/MainViewModel.cs | 82 ++++++++++++++++++--- src/FlaUInspect/Views/MainWindow.xaml | 34 ++++++--- src/FlaUInspect/Views/MainWindow.xaml.cs | 9 +++ 3 files changed, 102 insertions(+), 23 deletions(-) diff --git a/src/FlaUInspect/ViewModels/MainViewModel.cs b/src/FlaUInspect/ViewModels/MainViewModel.cs index c05cabc..e82861a 100644 --- a/src/FlaUInspect/ViewModels/MainViewModel.cs +++ b/src/FlaUInspect/ViewModels/MainViewModel.cs @@ -28,6 +28,8 @@ public class MainViewModel : ObservableObject { private AutomationBase? _automation; private RelayCommand? _captureSelectedItemCommand; private RelayCommand? _closeInfoCommand; + private RelayCommand? _copyDetailsToClipboardCommand; + private RelayCommand? _currentElementSaveStateCommand; private ObservableCollection? _elementPatterns = []; private FocusTrackingMode? _focusTrackingMode; private HoverMode? _hoverMode; @@ -39,7 +41,6 @@ public class MainViewModel : ObservableObject { private AutomationElement? _rootElement; private RelayCommand? _startNewInstanceCommand; private ITreeWalker? _treeWalker; - private RelayCommand? _currentElementSaveStateCommand; public MainViewModel(AutomationType automationType, string applicationVersion, InternalLogger logger) { _logger = logger; @@ -182,14 +183,41 @@ public string? ApplicationVersion { public ICommand CloseInfoCommand => _closeInfoCommand ??= new RelayCommand(_ => { IsInfoVisible = false; }); - - public event Action? CopiedNotificationRequested; public ICommand CurrentElementSaveStateCommand => _currentElementSaveStateCommand ??= new RelayCommand(_ => { if (SelectedItem?.AutomationElement == null) { return; } + try { + XDocument document = new (); + document.Add(new XElement("Root")); + ExportElement(document.Root!, SelectedItem); + + Clipboard.SetText(document.ToString()); + CopiedNotificationCurrentElementSaveStateRequested?.Invoke(); + } catch (Exception e) { + _logger?.LogError(e.ToString()); + } + }); + + public ICommand CollapseAllDetailsCommand => new RelayCommand(_ => { + foreach (ElementPatternItem pattern in ElementPatterns) { + pattern.IsExpanded = false; + } + }); + + public ICommand ExpandAllDetailsCommand => new RelayCommand(_ => { + foreach (ElementPatternItem pattern in ElementPatterns) { + pattern.IsExpanded = true; + } + }); + + public ICommand CopyDetailsToClipboardCommand => _copyDetailsToClipboardCommand ??= new RelayCommand(_ => { + if (SelectedItem?.AutomationElement == null) { + return; + } + try { XDocument document = new (); document.Add(new XElement("Root")); @@ -217,17 +245,49 @@ public string? ApplicationVersion { } }); - public ICommand CollapseAllDetailsCommand => new RelayCommand(_ => { - foreach (ElementPatternItem pattern in ElementPatterns) { - pattern.IsExpanded = false; + public event Action? CopiedNotificationRequested; + public event Action? CopiedNotificationCurrentElementSaveStateRequested; + + private void ExportElement(XElement parent, ElementViewModel element) { + XElement xElement = CreateXElement(element); + parent.Add(xElement); + + try { + foreach (ElementViewModel children in element.Children!) { + try { + xElement.Add(CreateXElement(children!)); + } catch { + // ignored + } + } + + foreach (ElementViewModel children in element.Children.Where(x => x is { IsExpanded: true }).Where(x => x != null)!) { + try { + ExportElement(xElement, children!); + } catch { + // ignored + } + } + } catch { + // ignored } - }); + } - public ICommand ExpandAllDetailsCommand => new RelayCommand(_ => { - foreach (ElementPatternItem pattern in ElementPatterns) { - pattern.IsExpanded = true; + private XElement CreateXElement(ElementViewModel element) { + + List attrs = [ + new ("Name", element.Name), + new ("AutomationId", element.AutomationId), + new ("ControlType", element.ControlType) + ]; + + if (EnableXPath) { + attrs.Add(new XAttribute("XPath", element.XPath)); } - }); + + XElement xElement = new ("Element", attrs); + return xElement; + } private void ReadPatternsForSelectedItem(AutomationElement? selectedItemAutomationElement) { if (SelectedItem?.AutomationElement == null || selectedItemAutomationElement == null) { diff --git a/src/FlaUInspect/Views/MainWindow.xaml b/src/FlaUInspect/Views/MainWindow.xaml index b8f5c6a..1871b83 100644 --- a/src/FlaUInspect/Views/MainWindow.xaml +++ b/src/FlaUInspect/Views/MainWindow.xaml @@ -114,17 +114,27 @@ HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal"> - + + + + + +