In a previous post I had implemented clicking through menus with White. Someone pointed out that this was a solved problem and I didn’t need to write any code.
Window mainWindow = installerEditor.GetWindow("Installer Editor", InitializeOption.NoCache);
MenuBar mainWindowMenuBar = mainWindow.MenuBar;
mainWindowMenuBar.MenuItem("File", "Save");
What I noticed next is that it would take anywhere from 3 to 10 seconds to resolve mainWindow.MenuBar. I looked at the implementation and it does a descendents (DFS?) search. The menu bar is a child of the window, so we can avoid the recursion altogether.
private static DictionaryMappedItemFactory _factory = new DictionaryMappedItemFactory();
public static T Find<T>(UIItem item, string name) where T : UIItem
{
AutomationElementFinder finder = new AutomationElementFinder(item.AutomationElement);
AutomationElement found = finder.Child(AutomationSearchCondition.ByName("Application"));
return (T) _factory.Create(found, item.ActionListener);
}
Generalizing this further, a simple tree walker with a depth limit seems to be much more efficient for both scenarios of deep and wide trees. The following code comes from a defunct thread on CodePlex, I didn’t write it.
private static AutomationElement Find(AutomationElement element, string name, int maxDepth)
{
if (maxDepth == 0)
{
return null;
}
TreeWalker walker = TreeWalker.RawViewWalker;
AutomationElement current = walker.GetFirstChild(element);
while (current != null)
{
if ((string)current.GetCurrentPropertyValue(AutomationElement.NameProperty) == name)
{
return current;
}
current = walker.GetNextSibling(current);
}
current = walker.GetFirstChild(element);
while (current != null)
{
AutomationElement found = Find(current, name, maxDepth - 1);
if (found != null)
{
return found;
}
current = walker.GetNextSibling(current);
}
return null;
}
private static UIItem Find(UIItem item, string name, int maxDepth)
{
AutomationElement element = Find(item.AutomationElement, name, maxDepth);
if (element == null) return null;
return (UIItem)_factory.Create(element, item.ActionListener);
}
public static T Find<T>(UIItem item, string name) where T : UIItem
{
return (T)Find(item, name, 4);
}
The new test code executes magnitudes faster to click File –> Save.
Window mainWindow = installerEditor.GetWindow("Installer Editor", InitializeOption.NoCache);
UIAutomation.Find<MenuBar>(mainWindow, "Application").MenuItem("File", "Save").Click();
Another interesting aspect of this menu bar is that it is virtualized. This means that while you may be holding an instance of a MenuBar, it may have been hidden by another UI element at some point and will now throw an exception with the UIA_E_ELEMENTNOTAVAILABLE
error code if you try to use it. This is annoying: if I need to click on 10 menu items, I have to do 10 searches, starting from the top every time! To solve this, .NET 4.0 has introduced a new IUIAutomationVirtualizedItemPattern that has a Realize
method to materialize the object again.