AccessibleObjectFromPoint和每个监视器DPI

时间:2017-01-19 13:23:15

标签: windows winapi accessibility uiaccessibility

我正在使用AccessibleObjectFromPoint功能的辅助功能,我希望它能够在每个监视器的DPI环境中正常工作。不幸的是,我无法让它发挥作用。我尝试了很多东西,现在的情况是:

  • 我的应用在清单中标记为per-monitor-DPI-aware。 (True/PM
  • 我使用GetCursorPos然后使用AccessibleObjectFromPoint

如何重现问题:

  • 有两台显示器,一台是100%DPI,另一台是125%。
  • 在125%显示器上运行Chrome。
  • 在其中一个标签名称上使用AccessibleObjectFromPoint,它无法正常使用。

它适用于某些应用程序(DPI识别,似乎像探险家一样),但不能与其他人一起工作。我尝试了几个相关的功能,例如GetPhysicalCursorPosPhysicalToLogicalPointForPerMonitorDPI,但没有任何作用。

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

2 个答案:

答案 0 :(得分: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中完成所有操作,但速度很慢。

P.S。还要注意Chrome返回无限的树木,父母是他们父母的孩子,需要进行一些狡猾的检查。此外,Chrome不会正确返回accRole,并且会为您提供HTML标记而不是VT_I4。

祝你好运

答案 1 :(得分:0)

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

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

  • 在循环中使用accChild.accLocation捕获每个对象的左侧和宽度

添加此简单测试

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

使用差异来重新缩放您正在使用的Width的所有值

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

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