WildByDesign Posted August 6 Posted August 6 (edited) Per-Monitor v2 Scaling UDF This is still quite early stages of this UDF, so I am still trying to get everything organized. Please feel free to share any feedback and feel free to contribute as well if you would like. How To Use: At the top of your script, you need to include the PMv2 UDF file and initialize the Per-Monitor V2 DPI scaling (prior to creating your GUI): #include "PMv2.au3" _PM_Init() After creating all of your controls, but just before showing your GUI with GUISetState, you need to apply the initial scaling: _PM_Scale($hGUI) GUISetState(@SW_SHOW, $hGUI) Changelog: Spoiler PMv2 2026.08.09: Fixed an issue with font scaling for SysHeader32 control of a ListView Cleaned up code and structured more like a proper UDF Adding some of the functions created by ioa747 which provide more fallback mechanisms PMv2 2026.08.10: Added functions __PM_Cleanup() and __PM_Free() from ioa747 to assist with font cleanup Removed decimal places from control coordinates using Floor() Determine if GUI was centered initially and ensure that GUI is centered after scaling (first launch only) PMv2 2026.08.12: Added menubar and Statusbar control to Example test scaling engine Added current DPI, scaling percentage and DPI awareness context to Statusbar Statusbar updates part text on DPI changes Implemented method to have two GUIRegisterMsg for WM_DPICHANGED messages This allows user script (Statusbar in case of Example) to get updates on WM_DPICHANGED changes as well This is achieved by registering a custom window message $__PM_WM_DPICHANGED for user script to tap into UDF WM_DPICHANGED handler adds _SendMessage to this custom $__PM_WM_DPICHANGED passing DPI values Added PMv1 as a fallback option to _PM_Init() function - function updated by ioa747 Updated __PM_Free() function - function updated by ioa747 Added new _WinAPI_AreDpiAwarenessContextsEqual() and _GetDpiAwarenessContextName() functions - argumentum Engine skips scaling Statusbar control if awareness context is Per-MonitorV2 The OS autotmatically scales non-client area and common controls v6 (eg. Statusbar) in PMv2 We still need to scale in all other contexts PMv2 2026.08.13: Improvements to PMv1 scaling as fallback to PMv2 on systems without PMv2 Setting EnableNonClientDpiScaling on PMv1 to enable automatic non-client area scaling (PMv1 only) Added CBTHook for calling EnableNonClientDpiScaling during WM_NCCREATE (PMv1 only) Fixed issue with multiple GUI scenario where previous DPI scale was incorrect - thanks ioa747 PMv2 2026.08.15: Added AutoIt version and OS architecture to the window title of the Example Added _IsNativeControl() function to determine if control is a native AutoIt control - thanks ioa747 Fixed an issue where incorrect font handles were deleted in some multi-GUI scenarios PMv2 2026.08.16: Added support for scaling Edit/Input controls that have a buddy UpDown control Improvements to the way that the global map is managed - thanks ioa747 PMv2 2026.08.17: Improved initial font scaling when GUI first shows in situations where user has not explicitly set control font Essentially referring to controls using default control font MS Shell Dlg at default unspecified size PMv2 2026.08.22: Significant improvement in font scaling accuracy and control scaling Using _WinAPI_MulDiv() now to avoid previous rounding issues Exposing ioa747's _PM_PtToPx() as user facing function now when creating fonts for UDF created controls Example of using this function being used in Example.au3 now If Segoe UI font is used, it gets automatically upgraded to scaling friendly Segoe UI Variable now If no font is set for a control, most AutoIt controls use MS Shell Dlg font which generally maps to a Serif font Under this specific scenario, the default control font gets upgraded to Segoe UI Variable at 8.5 (only if not set) The UDF tests whether system has Segoe UI Variable installed and falls back to Segoe UI otherwise PMv2 2026.08.28: Added new function _PM_GetMenuFont to obtain the DPI-scaled menubar font This is helpful in projects that use an owner-drawn menubar (eg. GUIDarkTheme UDF) Significant improvements in how scaling is handled for ListBox controls If ListBox has LBS_NOINTEGRALHEIGHT set, scaling occurs as per normal If LBS_NOINTEGRALHEIGHT is not set (default), the scaling always maintains the same number of items in view This is due to how IntegralHeight works in ListBox controls by always snapping down control height to nearest full item height Added a ListBox control to the Example GUI to test whether or not this new code is working correctly Added scaling for individual buttons within a ToolbarWindow32 control More work still needs to be done to handle ImageLists in toolbar buttons Improved scaling for SysIPAddress32 controls This requires destroying and re-creating SysIPAddress32 since the child Edit controls cannot be scaled More work still needs to be done to further improve SysIPAddress32 scaling PMv2 2026.08.30: Removed the GUIRegisterMsg usage of $WM_DPICHANGED from the UDF $WM_DPICHANGED added to the existing window procedure __PM_WndProc() function This frees up $WM_DPICHANGED to be used by the user GUI script with GUIRegisterMsg if needed Updated Example to use GUIRegisterMsg with $WM_DPICHANGED to replicate user usage (if needed) Code cleanup Added many missing function headers, descriptions, etc. PMv2 2026.08.31: Added proper DPI scaling for SysMonthCal32 controls (both themed and unthemed) Added SysMonthCal32 control to Example GUI to show scaling of unthemed SysMonthCal32 control Added SysDateTimePick32 control to Example GUI to show scaling of themed SysMonthCal32 control PMv2-2026.08.28.zip PMv2-2026.08.30.zip PMv2-2026.08.31.zip Edited Monday at 12:01 PM by WildByDesign Updated UDF files argumentum, bladem2003 and ioa747 3
WildByDesign Posted August 9 Author Posted August 9 PMv2 2026.08.09: Fixed an issue with font scaling for SysHeader32 control of a ListView Cleaned up code and structured more like a proper UDF Adding some of the functions created by ioa747 which provide more fallback mechanisms I've spent some time cleaning up the initial code and fixing some minor bugs. I have also restructured PMv2.au3 to be more like a proper UDF file. I also added some functions that were suggested by (and created by) @ioa747. Those functions provide more fallback mechanisms in case of errors which would likely depend on OS versions as to what is supported or not. Thank you for sharing your code to help improve this. I will likely include more of the code if needed. ioa747 and argumentum 2
WildByDesign Posted August 9 Author Posted August 9 There may (or may not) be issues related to scaling of child controls of ComboBox or ReBarWindow32 controls which are parents to their own controls and may very well resize their own child controls once they are resized/scaled. I haven't tested that yet. So I'll have to test more scenarios to see how it handles them.
ioa747 Posted August 9 Posted August 9 I took a look at the udf and I have a couple of points to make One is that now the way you manage in _PM_Scale the Static $aCtrlFonts[0] (all together and not per window) this creates a problem if we call two or more GUI in our application another thing I noticed is the part ; Divide value by previous DPI factor to get original size, then multiply by new DPI factor (rounded to nearest .5) (I don't think this offers anything since we are dealing with pixels and there is no decimal in pixels) Thank you for your work WildByDesign 1 I know that I know nothing
argumentum Posted August 9 Posted August 9 (edited) 1 hour ago, ioa747 said: (rounded to nearest .5) ..ok: coding time expandcollapse popup#include <WindowsStylesConstants.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> #include <WindowsConstants.au3> Exit main() Func main() #Region ### START Koda GUI section ### Form= Local $iFormW = 400, $iFormH = 200, $aArray, $aLabels[5] = [4] Local $Form1 = GUICreate("Form1", $iFormW, $iFormH) For $n = 1 To $aLabels[0] $aLabels[$n] = GUICtrlCreateLabel("Label" & $n, 15, 15 + (($n - 1) * 35), $iFormW - 30, 33.9, BitOR($SS_CENTER, $SS_CENTERIMAGE), $WS_EX_STATICEDGE) Next GUISetState(@SW_SHOW) For $n = 1 To 2 $aArray = ControlGetPos(GUICtrlGetHandle($aLabels[$n]), "", 0) GUICtrlSetData($aLabels[$n], "Label" & $n & ': ' & $aArray[0] & ', ' & $aArray[1] & ', ' & $aArray[2] & ', ' & $aArray[3]) Next For $n = 3 To $aLabels[0] $aArray = ControlGetPos(GUICtrlGetHandle($aLabels[$n]), "", 0) GUICtrlSetPos($aLabels[$n], $aArray[0] + 0.9, $aArray[1] + 0.9, $aArray[2] + 0.9, $aArray[3] + 0.9) Sleep(10) $aArray = ControlGetPos(GUICtrlGetHandle($aLabels[$n]), "", 0) GUICtrlSetData($aLabels[$n], "Label" & $n & ': ' & $aArray[0] & ', ' & $aArray[1] & ', ' & $aArray[2] & ', ' & $aArray[3]) Next #EndRegion ### END Koda GUI section ### ; Floor() ; Returns a number rounded down to the closest integer. While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE GUIDelete() Exit EndSwitch WEnd EndFunc ;==>main ..and yes, it ignores the decimal. So if anything Floor() would be the func to use 🤔 ( not sure of anything so the code shows what the code does 😅 ) Edited August 9 by argumentum ioa747 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
WildByDesign Posted August 9 Author Posted August 9 1 hour ago, ioa747 said: One is that now the way you manage in _PM_Scale the Static $aCtrlFonts[0] (all together and not per window) this creates a problem if we call two or more GUI in our application You are absolutely right. I completely neglected to think of multiple GUI scenarios in my planning. Thank you for bringing this to my attention because it is very important. I will have to add the parent handle for each control to the array. Or as long as it happens to be the top-level window. I still have to figure out something for the child controls of ComboBox and ReBarWindow32 as well. Quite likely it will make sense for me to make this array Global instead of a Local Static. This way it can be cleaned on exit much easier as well. 1 hour ago, ioa747 said: ; Divide value by previous DPI factor to get original size, then multiply by new DPI factor (rounded to nearest .5) (I don't think this offers anything since we are dealing with pixels and there is no decimal in pixels) You are right, for sure. I kept losing my sense of measurement initially. Figuring out what "logical units" are and how they relate to pixels, points, etc. I had to learn a lot during this process. Some of it still confuses me. 😆 1 hour ago, ioa747 said: Thank you for your work You're welcome. Thank you as well. There are probably still going to be some bugs to work out with this UDF, but it seems to be promising so far. ioa747 1
ioa747 Posted August 9 Posted August 9 18 minutes ago, WildByDesign said: I still have to figure out something for the child controls of ComboBox and ReBarWindow32 as well. For $i = 1 To $aCtrls[0][0] $hCtrl = $aCtrls[$i][0] $sClass = $aCtrls[$i][1] ; Skip child controls that belong to other controls (e.g., Edit inside ComboBox, SysHeader32 inside ListView) If _WinAPI_GetParent($hCtrl) <> $hWnd Then ContinueLoop ... WildByDesign 1 I know that I know nothing
WildByDesign Posted August 9 Author Posted August 9 3 hours ago, ioa747 said: For $i = 1 To $aCtrls[0][0] $hCtrl = $aCtrls[$i][0] $sClass = $aCtrls[$i][1] ; Skip child controls that belong to other controls (e.g., Edit inside ComboBox, SysHeader32 inside ListView) If _WinAPI_GetParent($hCtrl) <> $hWnd Then ContinueLoop ... This ended up being perfect in several ways. I was changing the font for the SysHeader32 but it turns out that it was not necessary. Changing the font for the SysListView32 is enough to change the font for both. ioa747 1
WildByDesign Posted August 9 Author Posted August 9 3 hours ago, argumentum said: ..and yes, it ignores the decimal. So if anything Floor() would be the func to use Getting rid of the decimal makes sense, for sure. But the question is, does it make more sense to go up or down? We can raise it with Ceiling, lower it with Floor and use Round to go up or down. But to be honest, I'm not 100% sure which is best.
argumentum Posted August 9 Posted August 9 ..there's no best multiplying/dividing decimals. Floor it all 🤷♂️ WildByDesign 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
argumentum Posted August 9 Posted August 9 (edited) 4 hours ago, argumentum said: not sure of anything so the code shows what the code does ..yes, Floor() is the best you can do given that it works that way anyway ( hence the test code to prove it ) Edited August 9 by argumentum WildByDesign 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting
WildByDesign Posted August 9 Author Posted August 9 18 minutes ago, argumentum said: ..yes, Floor() is the best you can do given that it works that way anyway ( hence the test code to prove it ) I added Floor to the UDF and it works great. Consistent and great. argumentum 1
WildByDesign Posted August 10 Author Posted August 10 PMv2 2026.08.10: Added functions __PM_Cleanup() and __PM_Free() from ioa747 to assist with font cleanup Removed decimal places from control coordinates using Floor() Determine if GUI was centered initially and ensure that GUI is centered after scaling (first launch only) 20 hours ago, ioa747 said: One is that now the way you manage in _PM_Scale the Static $aCtrlFonts[0] (all together and not per window) this creates a problem if we call two or more GUI in our application I added some of your cleanup functions and also your global map. It's a bit more simplistic in mine because I am only storing the font handles but in this case with your map, the font handles are stored per window. I tested it with a script with multiple GUI's and it seemed to worked well. Also, I wanted to ensure that GUI's that were initially expected to be centered got centered properly after scaling. But I'm not sure if my implementation is robust or not. Local $iMoveX, $iMoveY, $iMoveW, $iDiffW, $bCentered = False If Not IsDllStruct($tRect) Then $tRect = _WinAPI_GetWindowRect($hWnd) ; Used to determine GUI position only after initial scaling $iMoveX = DllStructGetData($tRect, "Left") $iMoveY = DllStructGetData($tRect, "Top") ; Used to determine if initial GUI was centered at startup $iMoveW = DllStructGetData($tRect, "Right") - DllStructGetData($tRect, "Left") $iDiffW = $iMoveX - (@DesktopWidth - $iMoveW - $iMoveX) If $iDiffW < 10 And $iDiffW > - 10 Then $bCentered = True DllStructSetData($tRect, "Left", DllStructGetData($tRect, "Left") * $fNewDPI) DllStructSetData($tRect, "Top", DllStructGetData($tRect, "Top") * $fNewDPI) DllStructSetData($tRect, "Right", DllStructGetData($tRect, "Right") * $fNewDPI) DllStructSetData($tRect, "Bottom", DllStructGetData($tRect, "Bottom") * $fNewDPI) EndIf Basically, I am checking the measurements (before scaling) of both sides of the GUI compared to the desktop width. If they are equal, or less than 10, I assume that the GUI was centered. Usually the difference would be 1 or 2. Then, after scaling is complete, and only if this is initial GUI launch: If $bInitial Then ; On initial GUI launch, center if GUI was centered else use initial coordinates If $bCentered Then $aPos = WinGetPos($hWnd) WinMove($hWnd, "", (@DesktopWidth / 2 - ($aPos[2] / 2)), (@DesktopHeight / 2 - ($aPos[3] / 2))) Else WinMove($hWnd, "", $iMoveX, $iMoveY) EndIf EndIf This seems to work well enough. I tried checking vertical centering to compare as well but the difference was much more, possibly because of the task bar. So I skipped that check for now. argumentum and ioa747 2
WildByDesign Posted August 10 Author Posted August 10 @ioa747 I was thinking about your _PMA_Initialize function (which I have renamed to_PM_Init in the UDF) and the fallback mechanisms: Try PerMonitorV2 (-4) - Windows 10 Build 1607+ Fallback to SystemAware (-2) for Windows 10 Legacy Fallback for Windows 7 / 8 / 8.1 What do you think about adding PerMonitorV1 (-3) in there before we fallback to SystemAware? From my understanding, PerMonitorV1 was around since Windows 8.x days and is still able to utilize the WM_DPICHANGED message. SystemAware cannot utilize WM_DPICHANGED. If you think that this is appropriate, could you please update the function to include it? Thank you. ioa747 1
ioa747 Posted August 11 Posted August 11 (edited) 17 hours ago, WildByDesign said: could you please update the function to include it? expandcollapse popupFunc _PM_Init() ; Prevent multiple initialization calls Local Static $bInitialized = False If $bInitialized Then Return True Local $bSuccess = False Local Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4 Local Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = -3 Local Const $DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = -2 Local Const $PROCESS_PER_MONITOR_DPI_AWARE = 2 Local $iDpiContext = 0 ; 1. Try PerMonitorV2 (-4) - Windows 10 (Build 1703+) Local $aRet = DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) If Not @error And $aRet[0] Then $bSuccess = True $iDpiContext = -4 Else ; 2. Try PerMonitorV1 (-3) via User32.dll - Windows 10 (Build 1607+) $aRet = DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) If Not @error And $aRet[0] Then $bSuccess = True $iDpiContext = -3 Else ; 3. Try PerMonitorV1 via SHCore.dll - Windows 8.1 (Normalized to -3) $aRet = DllCall("SHCore.dll", "long", "SetProcessDpiAwareness", "int", $PROCESS_PER_MONITOR_DPI_AWARE) If Not @error And $aRet[0] = 0 Then $bSuccess = True $iDpiContext = -3 Else ; 4. Fallback to SystemAware (-2) - Windows 10 $aRet = DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) If Not @error And $aRet[0] Then $bSuccess = True $iDpiContext = -2 Else ; 5. Legacy Fallback for Windows Vista / 7 / 8 (SystemAware) $aRet = DllCall("User32.dll", "bool", "SetProcessDPIAware") If Not @error And $aRet[0] Then $bSuccess = True $iDpiContext = -2 EndIf EndIf EndIf EndIf EndIf OnAutoItExitRegister("__PM_Cleanup") GUIRegisterMsg($WM_DPICHANGED, "__PM_WM_DPICHANGED") $bInitialized = True Return SetError(0, $iDpiContext, $bSuccess) EndFunc ;==>_PM_Init I also suggest adding a new line to __PM_Free so that __PM_Free() ; Auto-garbage only clears HWnd (In case you want to put something else on the map) Func __PM_Free($hWnd = 0) Local $aKeys = MapKeys($__g_mPMA) If @error Or Not IsArray($aKeys) Then Return True Local $hTargetWnd, $aPreviousFonts, $hFont ; === CASE 1: AUTO GARBAGE COLLECTION FOR ALL DESTROYED WINDOWS === If $hWnd = 0 Then For $i = 0 To UBound($aKeys) - 1 $hTargetWnd = $aKeys[$i] If Not IsHWnd($hTargetWnd) Then ContinueLoop ; <-- New line --<< If Not WinExists(HWnd($hTargetWnd)) Then Edited August 11 by ioa747 add $iDpiContext WildByDesign 1 I know that I know nothing
WildByDesign Posted August 11 Author Posted August 11 9 hours ago, ioa747 said: Your updated _PM_Init() function is great. 9 hours ago, ioa747 said: I also suggest adding a new line to __PM_Free so that __PM_Free() ; Auto-garbage only clears HWnd (In case you want to put something else on the map) This is a good idea. I've added this as well. ioa747 1
WildByDesign Posted August 12 Author Posted August 12 (edited) PMv2 2026.08.12: Added menubar and Statusbar control to Example test scaling engine Added current DPI, scaling percentage and DPI awareness context to Statusbar Statusbar updates part text on DPI changes Implemented method to have two GUIRegisterMsg for WM_DPICHANGED messages This allows user script (Statusbar in case of Example) to get updates on WM_DPICHANGED changes as well This is achieved by registering a custom window message $__PM_WM_DPICHANGED for user script to tap into UDF WM_DPICHANGED handler adds _SendMessage to this custom $__PM_WM_DPICHANGED passing DPI values Added PMv1 as a fallback option to _PM_Init() function - function updated by ioa747 Updated __PM_Free() function - function updated by ioa747 Added new _WinAPI_AreDpiAwarenessContextsEqual() and _GetDpiAwarenessContextName() functions - argumentum Engine skips scaling Statusbar control if awareness context is Per-MonitorV2 The OS autotmatically scales non-client area and common controls v6 (eg. Statusbar) in PMv2 We still need to scale in all other contexts Edited August 12 by WildByDesign argumentum 1
WildByDesign Posted August 12 Author Posted August 12 This update takes care of how the Statusbar control is scaled. We skip scaling in PMv2 context because the OS does it automatically. Although we do scale the Statusbar if awareness context is not PMv2. I still have to figure out how to handle the Menubar scaling. It gets scaled automatically when context is PMv2 which is nice. I just don't know if I should handle the scaling of Menubar when not PMv2 context. And if I do handle it, that would involved scaling the main GUI font. So I'm not quite sure how to do that yet. I'm hoping that we can do that without having to use WM_MEASUREITEM and WM_DRAWITEM. I haven't tried yet but I'm sure it's not too difficult.
WildByDesign Posted August 12 Author Posted August 12 @ioa747 I remember a while back you had created a command line utility for changing system DPI percentage. Do you think that it would be worthwhile to have a DPI menu in the menubar of the Example to change the system DPI scaling percentage? This would allow us to change it from within the Example and not have to use the Settings app. It's just an idea. I'm not certain if it's a good idea or not. It would have to show only what is capable on that specific system though. For example, mine only allows 100%, 125%, 150% and 175%. So we would not want to show a value that the system is not capable of. I'm not sure how all of that stuff works though.
WildByDesign Posted August 12 Author Posted August 12 1 hour ago, WildByDesign said: So I'm not quite sure how to do that yet. I'm hoping that we can do that without having to use WM_MEASUREITEM and WM_DRAWITEM. I haven't tried yet but I'm sure it's not too difficult. I was able to successfully use EnableNonClientDpiScaling for the menubar on PMv1 but it only covers Win10 1607 and up. Anything lower is not going to be proper scaling. I am thinking that we should make Win10 version 1607 the lowest supported build for this UDF.
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now