Is there a better way to click through menus?

Dec 12, 2009 at 2:14 PM

I'm so happy to have found White. It just made my life a lot easier (as described in this post).

I need to click through menus and am finding two issues.

I had to write a help function to avoid copy-pasting code. Maybe something that can be factored into a method in White?

public static Menu ClickThroughMenu(Menus m, string[] items) 
{ 
    List itemsArray = new List(items); 
    if (itemsArray.Count == 0) throw new ArgumentOutOfRangeException(); 
    Menu rootMenu = m.Find(itemsArray[0]); 
    if (rootMenu == null) throw new Exception(string.Format("Missing menu: ", itemsArray[0])); 
    itemsArray.RemoveAt(0); 
    return ClickThroughMenu(rootMenu, itemsArray.ToArray()); 
} 
  
public static Menu ClickThroughMenu(Menu m, string[] items) 
{ 
    m.Click(); 
    foreach (string item in items) 
    { 
        Menu itemMenu = m.ChildMenus.Find(item); 
        if (itemMenu == null) 
        { 
            throw new Exception(string.Format("Missing menu: {0}", item)); 
        } 
        itemMenu.Click(); 
        m = itemMenu; 
    } 
    return m; 
} 

The second problem is that It seems slow. After hitting a menu it takes 2-3 seconds to continue operation (to click on another menu).

Is there a better way?

Thanks otherwise for a fantastic library!

Coordinator
Dec 14, 2009 at 7:59 PM

Click through is already implemented in white. If you have a menubar then you can do:

 

menuBar.MenuItem("File", "New File");

Popup menu/ToolStrip also supports this.

I should add this to Menu class as well.

Regarding performance do you have a lot of menu items?

Dec 15, 2009 at 4:15 AM

The click-through worked. Indeed, it confused me not to find the MenuItem function in Menu.

I don't have "a lot of menus", maybe 20 total menu items under 5 menus. It takes 2-3 seconds per menu item, and I have basically to clck through every menu item and combinations thereof, so it adds up quickly. I could keep a reference to the top level menu or something like that, but every time you click the menu items disappear and are rebuilt. Any ideas welcome.

Thanks

 

Dec 15, 2009 at 4:16 AM

The click-through worked. Indeed, it confused me not to find the MenuItem function in Menu.

I don't have "a lot of menus", maybe 20 total menu items under 5 menus. It takes 2-3 seconds per menu item, and I have basically to clck through every menu item and combinations thereof, so it adds up quickly. I could keep a reference to the top level menu or something like that, but every time you click the menu items disappear and are rebuilt. Any ideas welcome.

Thanks

 

Coordinator
Dec 15, 2009 at 9:46 AM

is this happening with the white click-through mechanism? can you post the updated code.

Dec 15, 2009 at 1:57 PM

Here you go. The wait is not between each menu item, but for the entire click-through (eg. mainWindow.MenuBar.MenuItem("Edit", "Add", "Configurations", "Setup Configuration").Click(), 3-10 second delay before it actually). Another odd thing I noticed is that the delays are not uniform, ie. it feels like there's some kind of circular iteration and sometimes you're in the right point of the cirlce.

You can get all this code, including this unit test from http://dotnetinstaller.codeplex.com. This is in InstallerEditorUnitTests, TestAddSetupConfiguration.

            using (Application installerEditor = Application.Launch(InstallerEditorExeUtils.Executable))
            {
                Window mainWindow = installerEditor.GetWindow("Installer Editor", InitializeOption.NoCache);
                mainWindow.MenuBar.MenuItem("File", "New").Click();

                Tree configurationTree = mainWindow.Get("configurationTree");
                Assert.AreEqual(1, configurationTree.Nodes.Count);

                string[] componentsMenuItems = { "Msi Component", "Command Component", "Msu Component", "OpenFile Component" };
                string[] checksMenuItems = { "Installed Check Registry", "Installed Check File", "Installed Check Directory", 
                    "Installed Check Operator", "Installed Check ProductCode" };

                mainWindow.MenuBar.MenuItem("Edit", "Add", "Configurations", "Setup Configuration").Click();
                TreeNode configurationNode = configurationTree.SelectedNode;
                foreach (string componentMenuItem in componentsMenuItems)
                {
                    configurationNode.Select();
                    mainWindow.MenuBar.MenuItem("Edit", "Add", "Components", componentMenuItem).Click();

                    TreeNode componentNode = configurationTree.SelectedNode;
                    foreach (string checksMenuItem in checksMenuItems)
                    {
                        componentNode.Select();
                        mainWindow.MenuBar.MenuItem("Edit", "Add", "Checks", checksMenuItem).Click();
                    }

                    componentNode.Select();
                    mainWindow.MenuBar.MenuItem("Edit", "Delete").Click();
                }
            }
Dec 15, 2009 at 11:05 PM
Edited Dec 15, 2009 at 11:28 PM

Update: the problem here is locating mainWindow.MenuBar. IUIItem uiItem = currentContainerItemFactory.Find(searchCriteria, windowSession); takes a long time. SearchCriteria is {Name!=System Menu Bar,ControlType=menu bar}. Caching the menu bar object doesn't work, the second time you try to use it, you get a System.Windows.Automation.ElementNotAvailable exception. This is because The menu bar is a virtualized item, I could realize it with VirtualizedItemPattern if this was .NET 4.0, but it's not :)

Maybe there's another idea?

Dec 15, 2009 at 11:57 PM

Workaround: solved with this thread.

        private static DictionaryMappedItemFactory _factory = new DictionaryMappedItemFactory();

        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(UIItem item, string name) where T : UIItem
        {
            return (T)Find(item, name, 4);
        }

To find the menu bar:

MenuBar mainWindowMenuBar = UIAutomation.Find(mainWindow, "Application");

I would replace the lookup mechanisms in White with something like this, and be done with it.

Coordinator
Dec 25, 2009 at 4:31 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.