序言
当尝试将用户窗体定位在特定像素位置(存储在POINTAPI
类型结构),必须将 Pixel 坐标转换为 Point 坐标才能设置UserForm.Left
and UserForm.Top
VBA 属性。我们称这个系数为K。
从我的测试中,我开始了解到,就我而言,GetWindowRect
以及 UserForm 的 VBA 定位属性 (Left
, Top
, Width
, Height
)包括包含 MSForm UserForm 控件的窗口(属于“ThunderDFrame”类)周围的阴影。要真正获得由边框界定的窗口矩形,DwnGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, rcOutRECT, LenB(rcOutRECT)
必须使用Win API。
定位 UserForm 的坐标系原点是 Pixel (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 Point 占用屏幕上的多个像素。 (由于像素的整数舍入,也不是真正的 1/K)
Problem
考虑到缩放系数为 1 以简化我的 MCV 示例,确定的系数.Left
and .Top
我们希望其显示的屏幕像素中 (x; y) 位置的用户窗体的 Points 属性应该是:
72 / GetDeviceCaps(GetDC(0), LOGPIXELSX)
72 / GetDeviceCaps(GetDC(0), LOGPIXELSY)
这是
- 96 DPI 传统显示屏为 0.75(我已在使用 Win 7 + Excel 2007 的 PC 上尝试过)
- 0.375,我的 Surface Pro 4 平板电脑在 Win 10 64 位和 Excel 2016 32 位上运行
现在的问题是,在我的平板电脑上,虽然上述计算返回 0.375,定位用户窗体的正确系数在给定的像素位置(从GetCursorPos
以Win API为例)通过将其转换为对应的Point位置is 0.35. 我不知道这个价值从哪里来......???
现在的进展
在平板电脑上:
reg key HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics\AppliedDPI
表示 192 和72 / 192 = 0.375
我还尝试了 MSDN Windows 桌面应用程序 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