Sunday, September 3, 2017

WPF UIAutomation Getting child control of user control

Leave a Comment

I am a user of NordVPN and using it without any issue. Now for some requirements I need to set some of its properties like protocol (checkbox) and clicking on buttons from some other application.

But that area of the application looks like a custom control and UIAutomation is not able to drill down into it.

Elements inside that custom control does not have any automation id.

So I need to know how we could traverse through user controls in wpf applications like other parts of application window using UIAutomation and White Framework.

What I have tried so far is

  1. using TreeWalker (not able to read all elements)

  2. And try to get the element from its location AutomationElement.FromPoint(), but it gives the whole custom control (determine from its bounds) again on which I can't traverse yet.

Any suggestion on how could I drill into custom control from UIAutomation.

enter image description here

For the record, snoop can read the elements but VisualUIAVerify.exe is not.

1 Answers

Answers 1

As expected, the absence of automation-ids have resulted in the controls to be not visible in UI Automation tree APIs.

In order to work around that, and knowing that they are visible in Snoop application - you can use the underlying logic (that Snoop uses) to programmitically automate these controls.

Steps

  1. Download the binaries for SnoopUI, and add them to your project. Make sure to keep the compile option as 'None' and are copied to output directory.

    enter image description here

  2. Next step would be add a helper method, that uses these binaries to inject your dll with automation logic into target application (which is NordVPN) in this case. Once the dll is injected into target process, the ManagedInjector also invokes the method that is sent as parameter.

    public class Helper {     public static void Inject(IntPtr windowHandle, Assembly assembly, string className, string methodName)     {         var location = Assembly.GetEntryAssembly().Location;         var directory = Path.GetDirectoryName(location);         var file = Path.Combine(directory, "HelperDlls", "ManagedInjectorLauncher" + "64-4.0" + ".exe");          Debug.WriteLine(file + " " + windowHandle + " \"" + assembly.Location + "\" \"" + className + "\" \"" + methodName + "\"");         Process.Start(file, windowHandle + " \"" + assembly.Location + "\" \"" + className + "\" \"" + methodName + "\"");     } } 
  3. After the automation dll is injected in the application, the access to Visual Tree is pretty simple using Dispatcher and PresentationSources.

    public class Setup {     public static bool Start()     {         Dispatcher dispatcher;         if (Application.Current == null)             dispatcher = Dispatcher.CurrentDispatcher;         else             dispatcher = Application.Current.Dispatcher;          dispatcher.Invoke(AutomateApp);         return true;     }      public static void AutomateApp()     {         Window root = null;         foreach (PresentationSource presentationSource in PresentationSource.CurrentSources)         {             root = presentationSource.RootVisual as Window;              if (root == null)                 continue;              if ("NordVPN ".Equals(root.Title))                 break;         } 
  4. Getting access to VisualTree is easy, but identifying the controls is not that simple, as there are no automation-id(s), or name(s) that can uniquely identify these controls. But fortunately, as they are using MVVM, it is possible to identify them using the binding(s) attached with them.

    public static T GetChildWithPath<T>(this DependencyObject depObj, DependencyProperty property = null, string pathName = null) where T : DependencyObject {     T toReturn = null;      for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)     {         var child = VisualTreeHelper.GetChild(depObj, i);         bool pathNameMatch = (child is T) && child.IsPathNameMatch<T>(property, pathName);         if (pathNameMatch)         {             toReturn = child as T;             break;         }         else             toReturn = GetChildWithPath<T>(child, property, pathName);          if (toReturn != null)             break;     }     return toReturn; } 
  5. Once you have access to the controls, it is now possible to either manipulate their properties directly, or access their corresponding automation peers, and providers to automate these controls.

    var checkBoxNames = new[] {     "CyberSec", "AutomaticUpdates", "AutoConnect",     "StartOnStartup", "KillSwitch", "ShowNotifications",     "StartMinimized", "ShowServerList", "ShowMap",     "UseCustomDns", "ObfuscatedServersOnly" };  foreach(var path in checkBoxNames) {     var chkBox = settingsView.GetChildWithPath<CheckBox>(CheckBox.IsCheckedProperty, path);     if(chkBox != null && chkBox.IsEnabled)         chkBox.SimulateClick(); } 

A complete working sample has been uploaded at Github repository.

enter image description here

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment