【问题标题】:AccessibleObjectFromPoint and per-monitor DPIAccessibleObjectFromPoint 和每个显示器的 DPI
【发布时间】:2017-01-19 13:23:15
【问题描述】:

我正在使用带有AccessibleObjectFromPoint 功能的辅助功能,我希望它能够在每个显示器的 DPI 环境中正常工作。不幸的是,我无法让它工作。我尝试了很多东西,目前的情况是:

  • 我的应用在清单中被标记为可感知每个显示器的 DPI。 (True/PM)
  • 我使用GetCursorPos,然后使用AccessibleObjectFromPoint

如何重现问题:

  • 有两台显示器,一台为 100% DPI,另一台为 125%。
  • 在 125% 的显示器上运行 Chrome。
  • 在其中一个选项卡名称上使用AccessibleObjectFromPoint,它不起作用。

它适用于某些应用程序(DPI 感知,似乎像资源管理器),但不适用于其他应用程序。我尝试了几个相关的功能,例如GetPhysicalCursorPosPhysicalToLogicalPointForPerMonitorDPI,但没有任何效果。

值得注意的是,Microsoft 的inspect.exe 按预期工作。

【问题讨论】:

  • 操作系统是 8.1、10.0.10240、10.0.10586 还是 10.0.14393。 api不断变化。在 win 7 中,这一切都适用于物理坐标。我有一些在 10586 上工作的代码在 14393 上中断。如果窗口最大化,您可以获得监视器信息,减去监视器原点,然后乘以窗口 dpi 除以监视器 dpi。然后重新添加监视器原点。如果窗口最大化,这适用于 14393。我有同样的问题,仍在调查中。
  • 我提到三个版本的 Windows 10 的原因是例如Win32 API 调用 GetDpiForWindow() 仅从 14393 开始可用。我的客户的客户有 10.0.10240 和 10.0.10586 的 LTS 版本,所以这些仍然存在至少好几个月。所以我不得不在 C++/CLI 中编写一个 .NET 包装器,它适用于从 Vista 到 10.0.14393 的所有内容。在运行时动态加载 User32.dll 和 Shcore.dll 并拉入函数指针(或 NULL)以确定您正在执行的操作系统变体,并做出相应的反应。
  • P.S.由于 14393 和线程 DPI 意识,使用清单现在已经过时了。请参阅msdn.microsoft.com/en-us/library/windows/desktop/… 如果在 8.1+ 上调用 SetProcessDpiAwareness,如果在 Win 7 或 Vista 上调用 SetProcessDPIAware()。

标签: windows winapi accessibility uiaccessibility


【解决方案1】:

我已经为这个完全相同的问题苦苦挣扎了几个星期,现在可以告诉你我的发现。不幸的是,我只能给你一点代码提示,因为我正在进行的项目是专有的。

问题始于 Windows 8.1。该问题在 Windows 7 或 Vista 上不存在,因为 AccessibleObjectFromPoint 始终使用原始物理坐标,如此处所述:https://msdn.microsoft.com/en-us/library/windows/desktop/dd317984(v=vs.85).aspx
“Microsoft Active Accessibility 不使用逻辑坐标。以下方法和函数要么返回物理坐标,要么将它们作为参数。”自 Windows 8.1 以来,情况并非如此。

AccessibleObjectFromPoint 现在使用有缺陷的计算,由于与我的问题类似的原因,无法始终找到正确的窗口:High DPI scaling, mouse hooks and WindowFromPoint。 我的发现使我得出一个结论:API 已损坏。但这并不意味着不可能。

我已经部分测试过似乎可行的可能解决方案。 先决条件是你

1/.让您的流程了解每个监视器 DPI,而不是使用清单(稍后会详细介绍)。

2/.确定要查询的窗口的 hWnd(WindowFromPoint() 变体)

3/.确定查询到的hWnd的显示器DPI

4/.确定流程的 DPI

5/.确定查询到的hWnd的DPI

6/.确定查询到的hWnd的监听原点和偏移量(MonitorFromWindow()和GetMonitorInfo())

接下来,取决于你的平台

Windows 10.0.14393+

编写一个从顶层窗口找到 IAccessible (AccessibleObjectFromWindow() ) 的函数,然后递归调用 IAccessible::accHitTest 直到到达最底层的 IAccessible 和可能的 ChildID 数据。就好像你会调用 AccessibleObjectFromPoint 一样返回它。

要成功调用它,您需要使用上面列表中获取的 DPI 和坐标将 (x,y) 坐标缩放到查询的 hWnd 的比例系统中。注意监视器大小不同或监视器部分偏移或上下偏移的系统。

现在对于 10.0.14393 的重要部分 - 将您的线程设置为与您正在查询的 hWnd 相同的 DPI_AWARENESS_CONTEXT。现在调用你的新函数。现在恢复你的线程来监控 DPI 感知,瞧,它可以工作,即使窗口没有最大化。这就是为什么你不能使用清单。

如果您使用的是 Windows 8.1 到 10.0.10586,那么您将面临更艰巨的任务。 而不是调用 accHitTest,如上所述,您必须递归调用 AccessibleChildren 并迭代调用 IAccessible::accLocation 以确定您的测试点是否在每个孩子内。这很棘手,当你到达例如时开始变得非常混乱。 Office 等产品中的组合框,仅支持系统 DPI。

我现在只能给你这些了。

要在多平台上成功完成(我的必须从 Vista 到 Windows-Current),唯一真正安全的选择是用 C++ 编写一个包装 DLL,它可以在运行时确定它所在的操作系统并更改代码路径因此。您想在 C++ 中执行此操作的原因是避免通过 .Net/非托管编组边界传递 IAccessible 对象。您可以在不需要返回非托管端的对象上调用 IUnknown::Release。您可以在 .Net 中完成所有操作,但速度会很慢。

附:还要注意 Chrome 返回无限树,其中父母是父母的孩子,需要进行一些检查。此外,Chrome 不会正确返回 accRole,并且会给你 HTML 标签而不是 VT_I4。

祝你好运

【讨论】:

  • P.S.我在 .Net 与 C++ 中所做的基准测试在 C++ 中的速度要快 20-50 倍。
【解决方案2】:

一个相当可行的解决方案如下,在您的IAccessible 递归函数中:

  • 使用getwindowrect捕获主窗口的物理权限

  • 在循环中使用accChild.accLocation 捕获每个对象的left 和Width

添加这个简单的测试

If l > rct2r.Right And l > arrIACC.x2 Then
    arrIACC.x2 = l + w
End If
if dpi = 100 then no Object is furter out than physical right
if dpi > 100 then closebutton is...x pix offset

使用差异重新调整您正在使用的所有值的宽度

arrIACC.w1 = CInt(((-rct2r.Left + arrIACC.w1) / arrIACC.x2) * rct2r.Right)

这个解决方案来自我开发的一个 Excel 插件,我正在测试快速访问工具栏 qat 的宽度,无论任何 DPI,我的结果都是 +- 5 像素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-18
    • 2017-08-31
    • 2013-09-10
    • 2013-09-26
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    相关资源
    最近更新 更多