Win 10 Excel 2016 Unexplained PixelsToPoints coefficient to position UserForm
Asked Answered
M

2

2

Preambule

When trying to position a UserForm at a particular Pixel position (stored in a POINTAPI Type structure), one must convert the Pixel coordinates to Point coordinates to be able to set the UserForm.Left and UserForm.Top VBA properties. Let's call this coefficient K.

From my tests, I came to understand that in my case, GetWindowRect and the VBA positioning properties of a UserForm (Left, Top, Width, Height) includes the shadows around the window (of class "ThunderDFrame") containing the MSForm UserForm control. To really get the rectangle of the window delimited by the borders, DwnGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, rcOutRECT, LenB(rcOutRECT) Win API must be used.

The origin of the coordinate system to position a UserForm is pixel (0; 0), so no need to worry about ActiveWindow.PointsToScreenPixelsX / ActiveWindow.PointsToScreenPixelsY and the offset between the top left corner of the Excel window and the top left corner of the worksheet grid (at least not until Range.Left, Range.Top, etc. properties come into play). However, it is interesting to note that ActiveWindow.PointsToScreenPixelsX does not behave like ActiveWindow.ActivePane.PointsToScreenPixelsX. The first one works with pixels has input, not points, as the second does. The real name of the method should rather be ActiveWindow.WorksheetPixelsXToScreenPixelsX. You can verify it easily:

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

returns 1, while if it was really doing a convertion it should return something greater than 1 since 1 Point occupy several Pixels on screen. (not really 1/K either because of integer rounding of pixels)

Problem

Considering the zoom factor is 1 to simplify my MCV Example, the coefficients to determine the .Left and .Top properties in Points of the UserForm from the (x; y) position in Screen Pixels at which we want it displayed should be :

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

which is

  • 0.75 for a 96 DPI legacy display (I have tried it with a PC using Win 7 + Excel 2007
  • 0.375 with my Surface Pro 4 tablet running on Win 10 64-bit with Excel 2016 32-bit

Now the problem is that on my tablet, while the above calculation returns 0.375, the correct coefficient to position a UserForm at a given Pixel position (obtained from GetCursorPos Win API for example) by converting it to the corresponding Point position is 0.35. I have no idea where that value comes from...???

Current progress

On the tablet:

reg key HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics\AppliedDPI indicates 192 and 72 / 192 = 0.375

I have also tried some function from the High DPI Reference in MSDN's Windows Destop App UI Reference:

  • GetDPIForWindow (I tried with Application.Hwnd and UserForm's window handle)
  • GetDPIForMonitor

but everything rightfully returns 192.

The Minimal, Complete and Verifiable Example

The following allows me to retrieve the mysterious K = 0.35 coefficient on my tablet, but returns 0.75 on the other computer, as expected.

Module1.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

UserForm1

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
Mirilla answered 7/6, 2018 at 13:12 Comment(4)
Could somebody please test it with Windows 10 Build 1803 or later + Office Build 16.0.9330.1000 or later with the option "Optimize for compatibility" set in Options > General > User Interface options > When using multiple displays. My computer is a professional computer and the admin blocked the last updates. If it does not solve it, I will fulfill a request to Office developers directly.Mirilla
I think it might be related to this issue by another user: #51528444Mirilla
I have just tested it with the "Optimize for compatibility" option, it does not change a thing, I still get this weird 0.35 coefficient...Mirilla
Tested today with a new computer (Lenovo X1 Carbon 14" with Excel 365 32-bit 16.0.13328.20262) and no weird coefficient, I correctly get 0.75 with a text scale at 100% in windows display settings (former custom DPI settings), and 0.6 @ 125%Mirilla
H
0

I have the same issues. I tried your code on 64 bit and also received 0.35.

module1

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

UserForm1

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

The strange this is that I downloaded a ruler app to be able to measure the width of the UserForm1 in pixels. I adjusted the Width of the Form in Excel to 500 and as a result I get a measured width in pixels of 1406. And 500/1406 = 0.356 and not 0.375. I also found out why it's 0.356 and not 0.35, this is because the GetWindowRect function returns apparently a width including shadows around the Border. (left this is 15 or 16 px and right 10 px). Without this you would have a width of 1406 + 15 + 10 = 1431 and 500/1431 = 0.349, which is closer to the 0.35.

I didn't have these problems on my previous laptop and I don't have them with an external monitor. This happens only width high DPI monitors. (probably because display virtualization is enabled on these monitors in windows).

Edit: If I change the zoom in windows to 100% log out and log back in I get a value of 0.75 and everything works as expected.

Hulda answered 17/11, 2020 at 8:3 Comment(1)
Glad to know I'm not alone after all this time, thanks for the test :) Yes, that was my assumption, Microsoft must have broken something when adding support of High DPI monitors in Windows...Mirilla
M
0

This is not an answer but I wanted to post some interesting data that I could not place in a comment... So here it goes: I came to understand that this "bug" is not correlated to specific Windows devices, but rather to a single Windows setting, which is the text scale of the display under Win 10. Indeed, you will see below that for some value of that setting, everything seems normal, but for others, like 200%, the result is not the expected one. And since 200% is default on high DPI devices like the Windows Surface Pro 4 tablet, I could spot it. But I assume one will get the same result on any device set to 200% (as I did on 2 other devices).

I made speculations in columns I, J and K...

And I am still short on an explanation...

But I suspect, Office devs to have included some kind of adjustment factor to avoid having K coefficients with more than 2 decimal digits (@175%: -7%, @200%: -7%, @225%: -10%)

A1 B1 C1 D1 E1 F1 G1 H1 I1 J1 K1
A2 text scale in windows display settings expected GetDeviceCaps(hDC, LOGPIXELSX) = 72 / C2 MCVE = E2 / D2 = C2 / F2 = 72 / G2 effective GetDeviceCaps(hDC, LOGPIXELSX) = 72 / I2 = I2 / $I$3
A3 100% 96 0,75 0,75 1 96 0,75 96 0,75 100,0%
A4 125% 120 0,6 0,6 1 120 0,6 120 0,6 125,0%
A5 150% 144 0,5 0,5 1 144 0,5 144 0,5 150,0%
A6 175% 168 0,428571429 0,399999995 0,933333321 180,0000023 0,399999995 180 0,4 187,5%
A7 200% 192 0,375 0,349999996 0,933333321 205,7142883 0,349999996 205,7142883 0,349999996 214,3%
A8 225% 216 0,333333333 0,299999992 0,899999977 240,0000062 0,299999992 240 0,3 250,0%
A9 250% 240 0,3 0,300000008 1,000000026 239,9999939 0,300000008 240 0,3 250,0%
A10 300% 288 0,25 0,25 1 288 0,25 288 0,25 300,0%
Mirilla answered 19/6, 2021 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.