Получение списка основных пунктов меню через UIAutomation с Delphi

Я пытался получить список подпунктов меню из стандартного приложения Windows, используя библиотеку UIAutomationCore, импортированную как TLB в Delphi, т.е.

File -> New | Exit
Help -> About

Я могу получить меню приложения, а затем элементы верхнего уровня в список (например, в приведенном выше примере «Файл» и «Справка», но я не могу получить список ЛЮБЫХ элементов управления, которые находятся под этими элементами меню. Мой код как показано ниже - FElement представляет фактический пункт меню, который я проверяю.

Длина коллекции, возвращаемой FindAll, всегда равна 0. Я пытался расширить элемент меню до этого кода, но, похоже, это не дало никакого эффекта.

 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;

Я вижу примеры этого на С#, и они на самом деле ничем не отличаются от приведенного выше кода (насколько я вижу)

заранее спасибо

Обновление: это очень похоже на этот вопрос.

Update2: в этом примере это делается для другого приложения Delphi. Однако, если я попробую то же самое в блокноте (например), возникнет та же проблема.

Update3: используя Inspect (а затем используя UI Automation), у меня есть следующая структура...

Имя = Выход Предки = Файл (меню) Форма1 (панель)

Я также пробовал это после расширения меню (файла), и то же самое происходит (или не происходит).


person mmmm    schedule 17.04.2015    source источник
comment
Какое целевое приложение? Что Inspect сообщает вам о меню целевого приложения? Работает ли ваш код должным образом, когда вы используете его, например, в Блокноте?   -  person David Heffernan    schedule 17.04.2015
comment
См. обновление 2 для более подробной информации.   -  person mmmm    schedule 17.04.2015
comment
Я думаю, что вам нужно расширить пункт меню, чтобы получить его содержимое. Попробуйте посмотреть приложение с помощью Inspect.   -  person David Heffernan    schedule 17.04.2015
comment
Я пытался помочь и работал над вашим примером, но безуспешно. Мне удалось программно расширить меню, но даже после этого подменю не были видны UIAutomation. Что-то здесь не так.   -  person Wodzu    schedule 17.04.2015
comment
@Wodzu Вы можете увидеть их, когда расширитесь с помощью Inspect   -  person David Heffernan    schedule 17.04.2015
comment
Я подозреваю, что это может быть проблема с кэшированием в библиотеке (не зная вообще). Если я открою меню и найду текст в любых дочерних элементах, я ничего не получу. На самом деле дочерних элементов нет вообще.   -  person mmmm    schedule 20.04.2015


Ответы (1)


Я думаю, что у вас есть следующие две проблемы:

  1. В меню не будут перечислены элементы подменю, если меню не развернуто.
  2. Если вы пытаетесь автоматизировать свое собственное приложение, вы должны делать это в потоке.

Для меня работает следующее:

// 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;
person Sebastian Z    schedule 21.04.2015
comment
Себастьян, я попробую. Меню расширено, автоматизируемое приложение и программа автоматизации — разные приложения, и в одном из тестов я пытался автоматизировать notepad.exe, чтобы убедиться, что это не «делфи-вещь». - person mmmm; 21.04.2015
comment
Отлично, ваш пример работает, главное отличие в том, что вы создали обертки компонентов, я попробую то же самое, и, надеюсь, это решение. - person mmmm; 21.04.2015
comment
Правильно — пришлось рефакторить свой код (чтобы он был корректным), и теперь я получаю список всех пунктов меню, а не каждого меню в отдельности, начиная с родительской формы меню. Это не совсем то, что у меня было раньше, но я определенно могу работать с тем, что у меня получится. Спасибо - награда за вас! - person mmmm; 21.04.2015
comment
Интересно, работает ли этот пример, потому что UIAuto создается снова и снова. - person Wodzu; 22.04.2015
comment
Лучше создавать COM-объект в потоке, который его использует. Вот почему я сделал это таким образом. Я также мог бы сделать TThread.CreateAnonymousThread(procedure begin CoInitializeEx(nil, 2); UIAuto := TCUIAutomation.Create(nil); FindItems(true); UIAuto.Free; CoUninitialize; end).Start;, и это все еще работает. - person Sebastian Z; 22.04.2015
comment
Отдельный поток не имеет ничего общего с этой проблемой, поскольку ОП сказал, что пробовал свое решение в другом приложении, как и я. Единственное отличие состоит в том, что вы создаете UIAuto после расширения меню. Возможно, это создает какой-то снимок данных или что-то в этом роде... - person Wodzu; 22.04.2015
comment
Похоже, это две вещи: сначала ищут потомков формы, а не строку меню, И нужно подождать некоторое время, пока отображается меню. - person mmmm; 22.04.2015
comment
Я присудил награду сейчас, так как теперь она у меня работает - person mmmm; 22.04.2015