序言
尝试将用户窗体定位在特定像素位置(存储在POINTAPI
类型结构中)时,必须将像素坐标转换为点坐标,以便能够设置UserForm.Left
和UserForm.Top
VBA 属性。我们称这个系数为 K。
从我的测试中,我了解到,在我的情况下,用户窗体(Left
、Top
、Width
、Height
)的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.Left
、Range.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,但通过将用户窗体转换为相应的点位置,将用户窗体定位在给定像素位置(例如GetCursorPos
Win 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%)
A1 | B1 | C1 | D1 E1 | F1 | G1 | H1 I1 J1 | K1 | |||
---|---|---|---|---|---|---|---|---|---|---|
MCVE | = E2/D2= C2/F2= 72/G2有效 GetDeviceCaps(hDC, LOGPIXELSX | ) | = 72 | /I2= | I2 | /$I$3 | ||||
96 | 0,75 | 100,0% | ||||||||
0,6 | 1 | 120 0,6120 | 0,6125,0% | |||||||
144 | 0,5 | 150,0% | ||||||||
A6 | 175%168 | 0,428571429 | 0,3999999950,933333321 | 180,0000023 0,399999995 1800,4 | 187,5 | % | ||||
205,7142883 | 0,349999996 | 214,3% | ||||||||
A8 | 225%216 | 0,333333333 | 0,299999992 0,899999977 240,0000062 0,299999992 240 0,3250,0 | % | ||||||
1,000000026 239,9999939 | 0,300000008 | 240 0,3 | 250,0 | % | ||||||
288 0,25 | 300,0 | % |