4

I have been trying to get a list of menu sub-items from a standard Windows application using the UIAutomationCore library imported as a TLB into Delphi - i.e.

File -> New | Exit
Help -> About

I can get the application menu, and then the top-level items into a list (i.e. in the example above, 'File' and 'Help', but I cannot get a list of ANY controls that are under these menuitems. My code is as below - the FElement represents the actual menuitem I am checking.

The length of the collection returned by FindAll is always 0. I have tried expanding the menuitem prior to this code, but it seems to have no effect.

 UIAuto.CreateTrueCondition(condition);

 FItems := TObjectList<TAutomationMenuItem>.create;

 self.Expand;
 sleep(3000);

 // Find the elements
 self.FElement.FindAll(TreeScope_Descendants, condition, collection);

 collection.Get_Length(length);

 for count := 0 to length -1 do
 begin
   collection.GetElement(count, itemElement);
   itemElement.Get_CurrentControlType(retVal);

   if (retVal = UIA_MenuItemControlTypeId) then
   begin
     item := TAutomationMenuItem.Create(itemElement);
     FItems.Add(item);
   end;
 end;

I can see examples of this in C#, and they are not really doing anything different from the code above (as far as I can see)

Thanks in advance

Update : It looks very similar to this question

Update2 : In this example it is trying to do this for another Delphi application. However, if I try the same thing on notepad (for example), the same issue occurs.

Update3 : Using Inspect (and then using UI Automation), I have the following structure ...

Name = Exit Ancestors = File (menu) Form1 (pane)

I have also tried this after expanding the menu (file), and the same thing is happening (or not happening).

Community
  • 1
  • 1
mmmm
  • 2,431
  • 2
  • 35
  • 56
  • What is the target application? What does Inspect tell you about the target application's menus? Does your code work as expected when you use it on, for instance, Notepad? – David Heffernan Apr 17 '15 at 10:06
  • See update2 for more details – mmmm Apr 17 '15 at 11:54
  • I think that you do need to expand the menu item to obtain its contents. Try looking at the app with Inspect. – David Heffernan Apr 17 '15 at 11:57
  • I've tried to help and worked on your example but without success. I've been able to programmatically expand menu, but even after that, submenus were not visible by the UIAutomation. Something is wrong here. – Wodzu Apr 17 '15 at 12:36
  • @Wodzu You can see them when you expand with Inspect – David Heffernan Apr 17 '15 at 14:38
  • I suspect that it might a caching issue in the library (based on no knowledge at all). If I open the menu and search for the text in any child items, I get nothing. In fact there are no child items at all. – mmmm Apr 20 '15 at 08:24

1 Answers1

2

I think you have the following two issues:

  1. The menus will not list the sub menu items unless the menu is expanded
  2. If you're trying to automate your own application, you have to do it in a thread.

The following works for me:

// Careful: the code might not be 100% threadsafe, but should work for the purpose of demonstration
const
  UIA_MenuItemControlTypeId =   50011;
  UIA_ControlTypePropertyId =   30003;
  UIA_NamePropertyId    =   30005;
  UIA_ExpandCollapsePatternId   =   10005;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TThread.CreateAnonymousThread(procedure begin CoInitializeEx(nil, 2); FindItems(true); CoUninitialize; end).Start;
end;

procedure TForm1.FindItems(Recurse: Boolean);
var
  UIAuto: TCUIAutomation;
  condition: IUIAutomationCondition;
  collection: IUIAutomationElementArray;
  Length: Integer;
  Count: Integer;
  itemElement: IUIAutomationElement;
  retVal: Integer;
  val: WideString;

  ExpandCollapsePattern: IUIAutomationExpandCollapsePattern;
  FElement: IUIAutomationElement;
begin
  UIAuto := TCUIAutomation.Create(nil);   

  UIAuto.CreateTrueCondition(condition);

  // Find the elements
  UIAuto.ElementFromHandle(Pointer(Handle), FElement);

  FElement.FindAll(TreeScope_Descendants, condition, collection);

  collection.Get_Length(length);

  for Count := 0 to length - 1 do
  begin
    collection.GetElement(Count, itemElement);
    itemElement.Get_CurrentControlType(retVal);

    if (retVal = UIA_MenuItemControlTypeId) then
    begin
      ItemElement.Get_CurrentName(val);
      TThread.Synchronize(nil,
        procedure
        begin
          memo1.lines.Add(val);
        end);

      itemElement.GetCurrentPattern(UIA_ExpandCollapsePatternId, IInterface(ExpandCollapsePattern));
      if Assigned(ExpandCollapsePattern) then
      begin
        ExpandCollapsePattern.Expand;
        if Recurse = True then
          FindItems(False);
      end;
    end;
  end;
  UIAuto.Free;
end;
Sebastian Z
  • 4,520
  • 1
  • 15
  • 30
  • Sebastian - I will give this a try. The menu is expanded, the application being automated and the automating program are different applications, and in one of the tests, I have been trying to automate notepad.exe, to make sure it is not a 'Delphi-thing'. – mmmm Apr 21 '15 at 09:28
  • Great, your example works, the major difference is that you've created component wrappers, I will try the same and hopefully this is the solution. – mmmm Apr 21 '15 at 10:21
  • Right - I had to refactor my code (so that it was correct), and now I get the list of all the menu items, rather than each menu individually, starting with the parent form of the menu. It isn't quite what I had before, but I can certainly work with what I end up with. Thanks - bounty is all yours! – mmmm Apr 21 '15 at 11:20
  • I wonder if this example works because `UIAuto` is created over and over again. – Wodzu Apr 22 '15 at 06:04
  • It is better to create a COM object in the thread that is using it. That is why I did it that way. I also could have done `TThread.CreateAnonymousThread(procedure begin CoInitializeEx(nil, 2); UIAuto := TCUIAutomation.Create(nil); FindItems(true); UIAuto.Free; CoUninitialize; end).Start;` and it still works. – Sebastian Z Apr 22 '15 at 06:29
  • Separate thread has nothing to do with this issue, as OP said he tried his solution on a different application, so as I. The only difference is that you are creating `UIAuto` after expanding the menu. Perhaps this creates some snapshot of data or something like that... – Wodzu Apr 22 '15 at 07:30
  • It appears to be 2 things - first looking for descendants of the form, rather than the menubar AND have to wait for a period whilst the menu is shown. – mmmm Apr 22 '15 at 11:04
  • I've awarded the bounty now, as I now have it working – mmmm Apr 22 '15 at 11:04