Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8-bom
trim_trailing_whitespace = true
insert_final_newline = false

[*.{cs,vb}]
# Indentation preferences
indent_size = 4
indent_style = space
tab_width = 4

# Newline preferences
csharp_new_line_before_open_brace = none
csharp_new_line_before_else = false
csharp_new_line_before_catch = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

# Indentation options
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current

# Spacing options
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_name_and_open_parenthesis = false:none
csharp_space_between_method_call_name_and_opening_parenthesis = false:none

# Code style defaults
csharp_using_directive_placement = outside_namespace:suggestion
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = false:silent
csharp_prefer_braces = true:suggestion
csharp_style_prefer_primary_constructors = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion

# Expression-level preferences
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:suggestion
csharp_style_var_elsewhere = false:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion

# Naming conventions
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case

dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case

dotnet_naming_symbols.non_field_members.applicable_kinds = property, method, field, event, delegate
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =

dotnet_naming_rule.private_fields_should_be_camel_case_with_underscore.severity = suggestion
dotnet_naming_rule.private_fields_should_be_camel_case_with_underscore.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_camel_case_with_underscore.style = camel_case_with_underscore

dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_symbols.private_fields.required_modifiers =

dotnet_naming_style.camel_case_with_underscore.required_prefix = _
dotnet_naming_style.camel_case_with_underscore.required_suffix =
dotnet_naming_style.camel_case_with_underscore.word_separator =
dotnet_naming_style.camel_case_with_underscore.capitalization = camel_case

[*.xaml]
indent_size = 4
indent_style = space
7 changes: 4 additions & 3 deletions src/FlaUInspect.sln
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio Version 18
VisualStudioVersion = 18.3.11312.210 d18.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlaUInspect", "FlaUInspect\FlaUInspect.csproj", "{B1C3A9D6-326F-4A42-A2B8-C7605D111F47}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E443DA57-D2BF-49DA-A583-DCE901F5EB84}"
ProjectSection(SolutionItems) = preProject
..\README.md = ..\README.md
.editorconfig = .editorconfig
..\LICENSE = ..\LICENSE
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
Expand Down
35 changes: 16 additions & 19 deletions src/FlaUInspect/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,24 @@ private void ApplicationStart(object sender, StartupEventArgs e) {
string applicationVersion = versionAttribute?.Version ?? "N/A";
InternalLogger logger = new ();

#if AUTOMATION_UIA3
MainViewModel mainViewModel = new (AutomationType.UIA3, applicationVersion, logger);
MainWindow mainWindow = new () { DataContext = mainViewModel };

//Re-enable normal shutdown mode.
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
#elif AUTOMATION_UIA2
MainViewModel mainViewModel = new (AutomationType.UIA2, applicationVersion, logger);
MainWindow mainWindow = new() { DataContext = mainViewModel };

//Re-enable normal shutdown mode.
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
#else
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
ChooseVersionWindow dialog = new ();
#if AUTOMATION_UIA3
dialog.SelectedAutomationType = AutomationType.UIA3;
#elif AUTOMATION_UIA2
dialog.SelectedAutomationType = AutomationType.UIA2;
#else
var args = Environment.GetCommandLineArgs();
if (args.Any(a=>a.Equals("--uia2", StringComparison.OrdinalIgnoreCase)))
dialog.SelectedAutomationType = AutomationType.UIA2;
else if (args.Any(a=>a.Equals("--uia3", StringComparison.OrdinalIgnoreCase)))
dialog.SelectedAutomationType = AutomationType.UIA3;
else if (dialog.ShowDialog() != true)
return;
#endif


if (dialog.ShowDialog() == true) {

MainViewModel mainViewModel = new (dialog.SelectedAutomationType, applicationVersion, logger);
MainWindow mainWindow = new () { DataContext = mainViewModel };
Expand All @@ -42,7 +39,7 @@ private void ApplicationStart(object sender, StartupEventArgs e) {
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
}
#endif


}
}
17 changes: 17 additions & 0 deletions src/FlaUInspect/Core/FindByType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace FlaUInspect.Core;

public enum FindByType {
ByAutomationId,
ByName,
ByClassName,
ByControlType,
FindFirstByXPath,
ByText,
ByFrameworkId,
ByLocalizedControlType,
ByValue,
}

public static class FindByTypeValues {
public static FindByType[] All { get; } = Enum.GetValues<FindByType>();
}
16 changes: 14 additions & 2 deletions src/FlaUInspect/Core/PatternItemsFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Drawing;
using System.Drawing;
using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Identifiers;
Expand Down Expand Up @@ -56,6 +56,7 @@ public class PatternItemsFactory(AutomationBase? automationBase) {
new (FlaUI.UIA3.Patterns.TablePattern.Pattern, AddTablePatternDetails),
new (FlaUI.UIA3.Patterns.TextPattern.Pattern, AddTextPatternDetails),
new (FlaUI.UIA3.Patterns.TogglePattern.Pattern, AddTogglePatternDetails),
new (FlaUI.UIA3.Patterns.VirtualizedItemPattern.Pattern, AddVirtualizedPatternDetails),
new (FlaUI.UIA3.Patterns.ValuePattern.Pattern, AddValuePatternDetails),
new (FlaUI.UIA3.Patterns.WindowPattern.Pattern, AddWindowPatternDetails),
new (InvokePattern.Pattern, AddInvokePatternDetails)
Expand Down Expand Up @@ -101,7 +102,15 @@ private static IEnumerable<PatternItem> AddTogglePatternDetails(AutomationElemen
yield break;
}
ITogglePattern pattern = element.Patterns.Toggle.Pattern;
yield return PatternItem.FromAutomationProperty("ToggleState", pattern.ToggleState);
yield return new PatternItem("IsToggled", pattern.ToggleState.IsSupported ? pattern.ToggleState.ToString() : "Not Supported");
yield return new PatternItem("ToggleState", "Toggle", pattern.Toggle);
}
private static IEnumerable<PatternItem> AddVirtualizedPatternDetails(AutomationElement? element) {
if (element == null) {
yield break;
}
IVirtualizedItemPattern pattern = element.Patterns.VirtualizedItem.Pattern;
yield return new PatternItem("Virtualized", "Realize", pattern.Realize);
}

private static IEnumerable<PatternItem> AddTextPatternDetails(AutomationElement? element) {
Expand Down Expand Up @@ -164,6 +173,9 @@ private static IEnumerable<PatternItem> AddSelectionItemPatternDetails(Automatio
ISelectionItemPattern pattern = element.Patterns.SelectionItem.Pattern;
yield return PatternItem.FromAutomationProperty("IsSelected", pattern.IsSelected);
yield return PatternItem.FromAutomationProperty("SelectionContainer", pattern.SelectionContainer);
yield return new PatternItem("AddToSelection", "AddToSelection", pattern.AddToSelection);
yield return new PatternItem("RemoveFromSelection", "RemoveFromSelection", pattern.RemoveFromSelection);
yield return new PatternItem("Select", "Select", pattern.Select);
}

private static IEnumerable<PatternItem> AddScrollPatternDetails(AutomationElement? element) {
Expand Down
2 changes: 1 addition & 1 deletion src/FlaUInspect/Resources/RibbonIcons.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
<RectangleGeometry Rect="0.0,0.0,512.0,512.0" />
</Canvas.Clip>
<Canvas UseLayoutRounding="False">
<Path Fill="#ff000000">
<Path Fill="{Binding Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentControl}}">
<Path.Data>
<PathGeometry
Figures="m 234.5 468.297 c -8.4501 -0.77576 -27.431 -4.50654 -37.1218 -7.29646 C 162.622 450.994 132.017 432.984 106.467 407.503 C 100.399 401.451 93.1994 393.626 90.4673 390.113 L 85.5 383.725 L 85 398.281 c -0.489257 14.2428 -0.565411 14.6486 -3.5444 18.8874 c -8.66513 12.3296 -26.2531 12.3275 -34.9112 -0.004 L 43.5 412.828 V 362.578 V 312.329 l 2.73377 -3.9145 c 1.50358 -2.15297 4.3956 -5.0395 6.42673 -6.4145 l 3.69296 -2.5 h 50.2371 h 50.2371 l 4.33614 3.0444 c 12.3324 8.65857 12.3338 26.2479 0.003 34.9112 l -4.3333 3.0444 l -24.0041 0.30109 c -21.0368 0.26387 -23.9317 0.48987 -23.4181 1.82819 c 0.3223 0.8399 3.26463 5.42941 6.53852 10.1989 c 47.3738 69.0155 138.231 93.2387 214.05 57.0676 c 34.61 -16.5114 63.8128 -45.7907 79.9727 -80.1822 c 4.3681 -9.29626 12.0133 -30.856 12.0371 -33.9451 c 0.005 -0.69767 1.09625 -3.60202 2.42417 -6.45412 c 5.63929 -12.112 21.5905 -15.6886 31.9362 -7.16078 c 8.11114 6.68588 9.45139 13.395 5.66193 28.3429 c -12.622 49.7885 -44.2653 94.2692 -87.5321 123.043 c -27.6496 18.3877 -61.6457 30.7117 -95 34.4388 c -8.43052 0.94204 -36.0773 1.13781 -45 0.31865 z M 64.5 234.095 C 57.6872 232.368 52.6687 228.42 49.7598 222.5 C 46.9296 216.74 46.9529 213.398 49.9046 201.754 C 62.6136 151.618 94.1189 107.277 137.5 78.4721 C 172.915 54.9562 212.733 43 255.632 43 c 50.8901 0 98.3714 17.2367 137.54 49.9297 c 9.74855 8.13697 23.149 21.8171 29.0564 29.6628 c 1.79961 2.3901 3.57551 4.35954 3.94644 4.37655 c 0.37093 0.017 0.82093 -6.23228 1 -13.8873 c 0.31955 -13.6603 0.38201 -13.9985 3.36998 -18.25 c 8.66528 -12.3295 26.2531 -12.3273 34.9112 0.0044 L 468.5 99.1723 V 149.336 V 199.5 l -2.65608 3.84392 c -1.46084 2.11416 -4.38584 5.03916 -6.5 6.5 L 455.5 212.5 h -50.1639 h -50.1639 l -4.33614 -3.0444 c -12.3324 -8.65857 -12.3338 -26.2479 -0.003 -34.9112 l 4.3333 -3.0444 l 23.9167 -0.30052 C 392.237 171.034 403 170.556 403 170.137 c 0 -0.9217 -10.9698 -17.0925 -14.581 -21.4941 c -5.75972 -7.02043 -20.4021 -21.2302 -27.919 -27.094 c -10.3362 -8.06321 -17.211 -12.3819 -29.6354 -18.6168 c -46.8604 -23.5157 -104.246 -23.2791 -151.96 0.62637 c -32.6254 16.3459 -61.438 45.809 -76.7442 78.477 c -4.98963 10.6493 -9.89839 24.2948 -12.1061 33.6527 c -1.78771 7.57775 -5.98994 13.6246 -11.1908 16.1032 c -5.27882 2.51569 -10.3327 3.32644 -14.3634 2.30419 z"
Expand Down
71 changes: 70 additions & 1 deletion src/FlaUInspect/ViewModels/ElementViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
using FlaUInspect.Core;
using FlaUInspect.Core.Extensions;
using FlaUInspect.Core.Logger;

using System.Collections.ObjectModel;
using System.Windows.Input;
using MenuItem = System.Windows.Controls.MenuItem;
using User32=FlaUI.Core.WindowsAPI.User32;
namespace FlaUInspect.ViewModels;

public class ElementViewModel(AutomationElement? automationElement, ILogger? logger) : ObservableObject {
private readonly object _lockObject = new ();
public AutomationElement? AutomationElement { get; } = automationElement;
private RelayCommand? _refreshItemCommand;
private RelayCommand? _focusCommand;
private RelayCommand? _focusNativeCommand;

public bool IsExpanded {
get => GetProperty<bool>();
Expand All @@ -36,6 +42,69 @@ public bool IsSelected {
public ExtendedObservableCollection<ElementViewModel?> Children { get; set; } = [];


public ICommand RefreshItemCommand =>
_refreshItemCommand ??= new((_) => {
Children.Clear();
IsExpanded = true;
});

public ICommand FocusCommand =>
_focusCommand ??= CreateDelayedSafeCommand(AutomationElement.Focus);

public ICommand FocusNativeCommand =>
_focusNativeCommand ??= CreateDelayedSafeCommand(DoNativeFocus);

private async void DoNativeFocus() {
try{
//await Task.Delay(250);
if (AutomationElement.Properties.ProcessId.TryGetValue(out int processId)){
var proc = System.Diagnostics.Process.GetProcessById(processId);
var res = User32.SetForegroundWindow(proc.MainWindowHandle);
await Task.Delay(100);
}
AutomationElement.FocusNative();
}catch(Exception ex){
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}

private static RelayCommand CreateDelayedSafeCommand(Action action, int delayMs=250){
var delayedAction = CreateDelayedSafeAction(action,delayMs);
return new RelayCommand(_ => delayedAction());
}
/// <summary>
/// Mouse/focus related commands need a delay to allow our mosue action t obe handled first or we resteal the mouse
/// </summary>
/// <param name="action"></param>
/// <param name="delayMs"></param>
/// <returns></returns>
public static Action CreateDelayedSafeAction(Action action, int delayMs=250) {
return async () => {
try {
await Task.Delay(delayMs);
action();
} catch { }
};
}

private ObservableCollection<MenuItem>? _mouseActions;
public ObservableCollection<MenuItem> MouseActions { get => _mouseActions ??= BuildMouseActions(); }

private ObservableCollection<MenuItem> BuildMouseActions() {
return [
CreateMenuItem("Left Click", CreateDelayedSafeAction(() => AutomationElement?.Click())),
CreateMenuItem("Right Click", CreateDelayedSafeAction(() => AutomationElement?.RightClick())),
CreateMenuItem("Double Click", CreateDelayedSafeAction(() => AutomationElement?.DoubleClick())),
];
}
private MenuItem CreateMenuItem(string header, Action<object> value) => CreateMenuItem(header,()=>value(default));
private MenuItem CreateMenuItem(string header, Action value) {
return new MenuItem {
Header = header,
Command = new RelayCommand(_ => value())
};
}

public string XPath => AutomationElement == null ? string.Empty : Debug.GetXPathToElement(AutomationElement);
public event Action<ElementViewModel>? SelectionChanged;

Expand Down
Loading