【问题标题】:Getting list of underlying menu items via UIAutomation with Delphi通过 UIAutomation 和 Delphi 获取底层菜单项列表
【发布时间】:2025-12-05 18:50:02
【问题描述】:

我一直在尝试使用作为 TLB 导入 Delphi 的 UIAutomationCore 库从标准 Windows 应用程序中获取菜单子项列表 - 即

File -> New | Exit
Help -> About

我可以获得应用程序菜单,然后将*项目放入列表中(即在上面的示例中,“文件”和“帮助”,但我无法获得这些菜单项下的任何控件的列表。我的代码如下 - FElement 代表我正在检查的实际菜单项。

FindAll 返回的集合长度始终为 0。我在这段代码之前尝试过扩展 menuitem,但似乎没有任何效果。

 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;

我可以在 C# 中看到这样的示例,它们并没有真正做与上面的代码不同的任何事情(据我所知)

提前致谢

更新:和question很像

Update2 :在此示例中,它试图为另一个 Delphi 应用程序执行此操作。但是,如果我在记事本上尝试同样的事情(例如),就会出现同样的问题。

Update3:使用 Inspect(然后使用 UI 自动化),我有以下结构 ...

名称 = 退出 祖先 = 文件(菜单)Form1(窗格)

我在展开菜单(文件)后也试过这个,同样的事情正在发生(或没有发生)。

【问题讨论】:

  • 目标应用程序是什么? Inspect 告诉您有关目标应用程序菜单的哪些信息?当您在记事本等设备上使用代码时,您的代码是否按预期工作?
  • 查看更新2了解更多详情
  • 我认为您确实需要展开菜单项以获取其内容。尝试使用 Inspect 查看应用。
  • 我试图帮助并处理您的示例,但没有成功。我已经能够以编程方式展开菜单,但即使在那之后,UIAutomation 也看不到子菜单。这里出了点问题。
  • @Wodzu 使用 Inspect 扩展时可以看到它们

标签: delphi microsoft-ui-automation


【解决方案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;

【讨论】:

  • 塞巴斯蒂安 - 我会试一试。菜单已展开,正在自动化的应用程序和自动化程序是不同的应用程序,在其中一项测试中,我一直在尝试使 notepad.exe 自动化,以确保它不是“Delphi-thing”。
  • 太好了,您的示例有效,主要区别在于您创建了组件包装器,我将尝试相同的方法,希望这是解决方案。
  • 对 - 我必须重构我的代码(使其正确),现在我得到了所有菜单项的列表,而不是单独的每个菜单,从菜单的父窗体开始.这不是我以前所拥有的,但我当然可以使用我最终得到的东西。谢谢 - 赏金全是你的!
  • 我想知道这个例子是否有效,因为UIAuto 是一遍又一遍地创建的。
  • 最好在使用它的线程中创建一个COM对象。这就是我这样做的原因。我也可以完成 TThread.CreateAnonymousThread(procedure begin CoInitializeEx(nil, 2); UIAuto := TCUIAutomation.Create(nil); FindItems(true); UIAuto.Free; CoUninitialize; end).Start; 并且它仍然有效。
最近更新 更多