赢得 10 Excel 2016 无法解释的像素到点系数来定位用户窗体



序言

尝试将用户窗体定位在特定像素位置(存储在POINTAPI类型结构中)时,必须将像素坐标转换为点坐标,以便能够设置UserForm.LeftUserForm.TopVBA 属性。我们称这个系数为 K。

从我的测试中,我了解到,在我的情况下,用户窗体(LeftTopWidthHeight)的GetWindowRect和VBA定位属性包括包含MSForm UserForm控件的窗口周围的阴影(类"ThunderDFrame")。若要真正获取由边框分隔的窗口矩形,必须使用DwnGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, rcOutRECT, LenB(rcOutRECT)Win API。

用于定位 UserForm 的坐标系原点是像素 (0;0),因此无需担心ActiveWindow.PointsToScreenPixelsX/ActiveWindow.PointsToScreenPixelsY以及 Excel 窗口左上角与工作表网格左上角之间的偏移量(至少在Range.LeftRange.Top等属性发挥作用之前不会)。但是,有趣的是,ActiveWindow.PointsToScreenPixelsX的行为不像ActiveWindow.ActivePane.PointsToScreenPixelsX。第一个与像素有输入,而不是点,就像第二个一样。该方法的真实名称应该是ActiveWindow.WorksheetPixelsXToScreenPixelsX。您可以轻松验证它:

ActiveWindow.PointsToScreenPixelsX(1) - ActiveWindow.PointsToScreenPixelsX(0)

返回 1,而如果它真的在进行转换,它应该返回大于 1 的内容,因为 1 点在屏幕上占据了几个像素。(也不是真正的 1/K,因为像素的整数舍入)

问题

考虑到缩放系数为 1 以简化我的 MCV 示例,从我们希望显示的屏幕像素中的 (x; y) 位置确定用户窗体点中的.Left.Top属性的系数应该是:

72 / GetDeviceCaps(GetDC(0), LOGPIXELSX)
72 / GetDeviceCaps(GetDC(0), LOGPIXELSY)

这是

  • 0.75 DPI 传统显示器为 96(我已经使用 Win 7 + Excel 2007 在 PC 上尝试过
  • 0.375,我的Surface Pro 4平板电脑在Win 10 64位上运行,带有Excel 2016 32位

现在的问题是,在我的平板电脑上,虽然上面的计算返回 0.375,但通过将用户窗体转换为相应的点位置,将用户窗体定位在给定像素位置(例如GetCursorPosWin API 获得)的正确系数是 0.35我不知道这个价值从何而来???

当前进展

在平板电脑上:

注册键HKEY_CURRENT_USERControl PanelDesktopWindowMetricsAppliedDPI表示 192 和72 / 192 = 0.375

我还尝试了MSDN的Windows Destop应用程序UI参考中的高DPI参考中的一些功能:

  • GetDPIForWindow(我尝试使用Application.Hwnd和UserForm的窗口句柄)
  • GetDPIForMonitor

但一切都理所当然地返回 192。

最小、完整和可验证的示例

以下内容允许我在平板电脑上检索神秘的 K = 0.35 系数,但在另一台计算机上返回 0.75,正如预期的那样。

模块1.bas

Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, rcWindowRect As RECT) As Long
Private Declare Function GetCursorPos Lib "user32" (ptCursorPoint As POINTAPI) As Long
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type POINTAPI
X As Long
Y As Long
End Type
Sub test()
Dim rcUsfWindowRect As RECT
UserForm1.Show vbModeless
lRet& = GetWindowRect(UserForm1.hWnd, rcUsfWindowRect)
dblUsfRectWidth# = rcUsfWindowRect.Right - rcUsfWindowRect.Left
dblUsfRectHeight# = rcUsfWindowRect.Bottom - rcUsfWindowRect.Top
Debug.Print UserForm1.Width / dblUsfRectWidth
End Sub

用户表单1

Private Declare Function FindWindowA Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public hWnd As Long
Private Sub UserForm_Initialize()
hWnd = FindWindowA("ThunderDFrame", UserForm1.Caption)
End Sub

我有同样的问题。我在 64 位上尝试了您的代码,也收到了 0.35。

模块1

Option Explicit
Private Declare PtrSafe Function GetWindowRect Lib "user32" (ByVal hWnd As Long, rcWindowRect As RECT) As Long
Private Declare PtrSafe Function GetCursorPos Lib "user32" (ptCursorPoint As POINTAPI) As Long
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type POINTAPI
X As Long
Y As Long
End Type
Sub test()
Dim rcUsfWindowRect As RECT
Dim dblUsfRectWidth As Double
Dim dblUsfRectHeight As Double

UserForm1.Show vbModeless
Call GetWindowRect(UserForm1.hWnd, rcUsfWindowRect)
dblUsfRectWidth = rcUsfWindowRect.Right - rcUsfWindowRect.Left
dblUsfRectHeight = rcUsfWindowRect.Bottom - rcUsfWindowRect.Top
Debug.Print UserForm1.Width / dblUsfRectWidth
End Sub

用户表单1

Option Explicit
Private Declare PtrSafe Function FindWindowA Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public hWnd As Long
Private Sub UserForm_Initialize()
hWnd = FindWindowA("ThunderDFrame", UserForm1.Caption)
End Sub

奇怪的是,我下载了一个标尺应用程序,以便能够以像素为单位测量UserForm1的宽度。我将 Excel 中表单的宽度调整为 500,结果我得到的测量宽度(以像素为单位)为 1406。500/1406 = 0.356 而不是 0.375。 我还发现了为什么它是 0.356 而不是 0.35,这是因为 GetWindowRect 函数返回的宽度显然包括边框周围的阴影。(左边是 15 或 16 像素,右边是 10 像素)。如果没有这个,你的宽度将为 1406 + 15 + 10 = 1431 和 500/1431 = 0.349,更接近 0.35。

我在以前的笔记本电脑上没有这些问题,也没有带外接显示器的问题。这只发生在宽度高 DPI 显示器上。(可能是因为在 Windows 中的这些监视器上启用了显示虚拟化)。

编辑: 如果我将窗口放大更改为 100% 注销并重新登录,我会得到一个值 0.75,并且一切都按预期工作。

这不是一个答案,但我想发布一些我无法放在评论中的有趣数据......所以事情是这样的:我开始明白这个"错误"与特定的Windows设备无关,而是与单个Windows设置相关,即Win 10下显示器的文本比例。事实上,您将在下面看到,对于该设置的某些值,一切似乎都很正常,但对于其他人,例如 200%,结果不是预期的。由于 200% 是 Windows Surface Pro 4 平板电脑等高 DPI 设备的默认,我可以发现它。但我假设在任何设置为 200% 的设备上都会得到相同的结果(就像我在其他 2 台设备上所做的那样)。

我在 I、J 和 K 列中进行了推测......

而且我仍然缺乏解释...

但我怀疑,Office 开发人员已经包含某种调整因子,以避免 K 系数超过 2 个十进制数字(@175%:-7%,@200%:-7%,@225%:-10%)

窗口显示设置中的 A2 文本比例 GetDeviceCaps(hDC, LOGPIXELSX)= 72/C2= E2/D2= C2/F2= 72/G2/A3 100%96 0,75 0,75 1 96 0,75A4 125%120 0,6120 0,60,6A5 150%144 0,5 0,5 1 144 0,50,5175%0,399999995180,0000023 0,399999995 180A7 200%192 0,375 0,349999996 0,9333333321 205,7142883 0,3499999960,299999992 0,899999977 240,0000062 0,299999992 240 0,3A9 250%240 0,3 0,3000000081,000000026 239,9999939A10 300%288 0,25 0,25 1 2880,25
A1 B1 C1 D1 E1 F1 G1 H1 I1 J1 K1
MCVE有效 GetDeviceCaps(hDC, LOGPIXELSX)= 72I2=I2/$I$3
960,75100,0%
0,61120125,0%
144150,0%
A61680,4285714290,9333333210,4187,5%
205,71428830,349999996214,3%
A8225%2160,333333333250,0%
0,300000008240 0,3250,0%
288 0,25300,0%

相关内容

  • 没有找到相关文章

最新更新