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
using TreeWalker (not able to read all elements)
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.
For the record, snoop can read the elements but VisualUIAVerify.exe is not.
EDIT 1 - Clarification: For me a control is not visible in automation API - if there is no ability to uniquely identify them to automate on. The identification factor can be anything from name, id, sibling, or parent.
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.
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.
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 + "\"");
}
}
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;
}
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With