Jump to content

Recommended Posts

Posted (edited)

Per-Monitor v2 DPI Awareness Scaling Test:

Have this GUI running and test by changing your DPI scaling settings from: Settings app > System > Display > Scale

Work in progress, but might become more interesting. Possible UDF if everything goes well.

EDIT: My initial script forgot to set the initial DPI scaled values. 🤦‍♂️

#include <GuiEdit.au3>
#include <FontConstants.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPISysWin.au3>
#include <WinAPIGdi.au3>
#include <GUIConstantsEx.au3>
#include <AutoItConstants.au3>
#include <StructureConstants.au3>

; Apply PER_MONITOR_AWARE_V2 DPI Awareness
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", -4)

Global $g_iScale = _WinAPI_GetDPIForWindow(WinGetHandle(AutoItWinGetTitle())) / 96
If @error Then $g_iScale = 1
Global $g_hCtrlFont = 0

Global Const $fDefFontSize = 8.5, $fDefPixel = $fDefFontSize * 96 / 72 

Global Const $WM_DPICHANGED = 0x02E0
Global Const $WM_GETDPISCALEDSIZE = 0x02E4
Global Const $WM_DPICHANGED_BEFOREPARENT = 0x02E2
Global Const $WM_DPICHANGED_AFTERPARENT = 0x02E3

OnAutoItExitRegister("_Cleanup")

Example()

Func Example()
    Local $iCtrlSpaceV = 15
    Local $sAppName = "Testing PerMonitorV2 DPI"
    Local $hGUI = GUICreate($sAppName, 440, 340, -1, -1)

    Local $idTestName = GUICtrlCreateLabel("Test Name:", 15, 20, -1, -1)
    Local $aPos = ControlGetPos($hGUI, "", $idTestName)
    Local $iPrev = $aPos[1] + $aPos[3] + $iCtrlSpaceV

    Local $idTestInput = GUICtrlCreateInput("Test Input", 15, $iPrev, 300, 22 * $g_iScale)
    Local $aPos = ControlGetPos($hGUI, "", $idTestInput)
    Local $iPrev = $aPos[1] + $aPos[3] + $iCtrlSpaceV

    Local $idCheckbox1 = GUICtrlCreateCheckbox("Test Checkbox1 ", 15, $iPrev)
    Local $aPos = ControlGetPos($hGUI, "", $idCheckbox1)
    Local $iPrev = $aPos[1] + $aPos[3] + $iCtrlSpaceV

    Local $idCheckbox2 = GUICtrlCreateCheckbox("Test Checkbox2 ", 15, $iPrev)
    Local $aPos = ControlGetPos($hGUI, "", $idCheckbox2)
    Local $iPrev = $aPos[1] + $aPos[3] + $iCtrlSpaceV

    _GUICtrlEdit_Create($hGUI, "This is a test" & @CRLF & "Another Line", 15, $iPrev, 394, 64 * $g_iScale)

    GUISetState(@SW_SHOW, $hGUI)

    GUIRegisterMsg($WM_DPICHANGED, _WM_DPICHANGED)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

        EndSwitch
    WEnd

    GUIDelete($hGUI)
EndFunc   ;==>Example

Func _WM_DPICHANGED($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam

    Local Static $iInitDPI = $g_iScale
    Local Static $iPrevDPI = $g_iScale

    ; Obtain new DPI values
    Local $iDPI = _WinAPI_LoWord($wParam)
    Local $iNewDPI = _WinAPI_LoWord($wParam) / 96

    Local $hFont = $g_hCtrlFont

    ; Create new scaled font
    $g_hCtrlFont = _WinAPI_CreateFont(-Round($fDefPixel * $iNewDPI), 0, 0, 0, $FW_NORMAL, False, False, False, _
            $DEFAULT_CHARSET, $OUT_DEFAULT_PRECIS, $CLIP_DEFAULT_PRECIS, $PROOF_QUALITY, $DEFAULT_PITCH, "Segoe UI")

    ; Lock window drawing for smoother transition
    _WinAPI_LockWindowUpdate($hWnd)

    ; Enumerate child windows for sizing adjustments
    Local $aCtrls = _WinAPI_EnumChildWindows($hWnd, False)
    Local $hCtrl, $sClass, $aPos, $tPoint, $iX, $iY, $iW, $iH, $iOrigDPI
    If IsArray($aCtrls) Then
        For $i = 1 To $aCtrls[0][0]
            $hCtrl = $aCtrls[$i][0]
            $sClass = $aCtrls[$i][1]
            $aPos = WinGetPos($hCtrl)
            $tPoint = DllStructCreate("int X;int Y")
            DllStructSetData($tPoint, "X", $aPos[0])
            DllStructSetData($tPoint, "Y", $aPos[1])

            ; Convert screen coordinates to client
            _WinAPI_ScreenToClient($hWnd, $tPoint)
            $iX = DllStructGetData($tPoint, "X")
            $iY = DllStructGetData($tPoint, "Y")
            $iW = _WinAPI_GetWindowWidth($hCtrl)
            $iH = _WinAPI_GetWindowHeight($hCtrl)

            ; Divide value by previous DPI factor to get original size, then multiply by new DPI factor (rounded to nearest .5)
            $iX = Round((($iX / $iPrevDPI) * $iNewDPI) / 0.5) * 0.5
            $iY = Round((($iY / $iPrevDPI) * $iNewDPI) / 0.5) * 0.5
            $iW = Round((($iW / $iPrevDPI) * $iNewDPI) / 0.5) * 0.5
            $iH = Round((($iH / $iPrevDPI) * $iNewDPI) / 0.5) * 0.5

            ; Set new position/sizes for control
            Local $iCtrlID = _WinAPI_GetDlgCtrlID($hCtrl)
            If $iCtrlID < 10000 Then
                GUICtrlSetResizing($iCtrlID, $GUI_DOCKLEFT + $GUI_DOCKTOP + $GUI_DOCKWIDTH + $GUI_DOCKHEIGHT)
                GUICtrlSetPos($iCtrlID, $iX, $iY, $iW, $iH) ; working for GUICtrlCreate* controls only
            ElseIf $iCtrlID >= 10000 Then
                ControlMove($hWnd, "", $iCtrlID, $iX, $iY, $iW, $iH) ; working for UDF-created controls only
            ElseIf Not $iCtrlID Then
                ; probably custom SysLink or something which has no CtrlID
                ; can't move without CtrlID
                ; only need to modify X here
            EndIf

            ; Set new scaled font to each control
            _SendMessage($hCtrl, $WM_SETFONT, $g_hCtrlFont, True)
        Next
    EndIf

    ; Resize GUI window
    Local $tRECT = DllStructCreate($tagRECT, $lParam)
    Local $iX = $tRECT.left, $iY = $tRECT.top, $iW = $tRECT.right - $iX, $iH = $tRECT.bottom - $iY
    _WinAPI_SetWindowPos($hWnd, 0, $iX, $iY, $iW, $iH, BitOR($SWP_NOZORDER, $SWP_NOACTIVATE))

    ;
    ; IMPORTANT: Desperately need a GUICtrlGetResizing() function to restore original control resizing behavior
    ;

    ; Update previous DPI with the now current DPI
    $iPrevDPI = $iNewDPI

    _WinAPI_LockWindowUpdate(0)

    ; Delete previous font
    If $hFont Then _WinAPI_DeleteObject($hFont)

    Return 0
EndFunc   ;==>_WM_DPICHANGED

Func _Cleanup()
    _WinAPI_DeleteObject($g_hCtrlFont)
EndFunc

Func _WinAPI_GetDPIForWindow($hWnd) ; UEZ
    Local $aResult = DllCall('user32.dll', "uint", "GetDpiForWindow", "hwnd", $hWnd) ;requires Win10 v1607+ / no server support
    If Not IsArray($aResult) Or @error Then Return SetError(1, @extended, 0)
    If Not $aResult[0] Then Return SetError(2, @extended, 0)
    Return $aResult[0]
EndFunc   ;==>_WinAPI_GetDPIForWindow

 

Edited by WildByDesign
Posted

It is rather cumbersome to have to wrap and then compute each control individually. At the very least, I have not been able to think of a better approach—for instance, one that would allow the interface to become fully adaptive simply by adding a function or a parameter.

#include <GuiEdit.au3>
#include <FontConstants.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPISysWin.au3>
#include <WinAPIGdi.au3>
#include <GUIConstantsEx.au3>
#include <AutoItConstants.au3>
#include <StructureConstants.au3>

; Apply PER_MONITOR_AWARE_V2 DPI Awareness
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", -4)

Global $g_iScale = 1
Global $g_hCtrlFont = 0

Global Const $fDefFontSize = 8.5, $fDefPixel = $fDefFontSize * 96 / 72

Global Const $WM_DPICHANGED = 0x02E0
Global Const $WM_GETDPISCALEDSIZE = 0x02E4
Global Const $WM_DPICHANGED_BEFOREPARENT = 0x02E2
Global Const $WM_DPICHANGED_AFTERPARENT = 0x02E3

Global $g_aCtrlDesign[1][5] = [[0]]
Global $g_iDesignClientW = 440, $g_iDesignClientH = 340
Global $g_iFrameW = 0, $g_iFrameH = 0

OnAutoItExitRegister("_Cleanup")

Example()

Func Example()
    Local $sAppName = "Testing PerMonitorV2 DPI"
    Local $hGUI = GUICreate($sAppName, $g_iDesignClientW, $g_iDesignClientH)

    _DPICtrlCreateLabel("Test Name:", 15, 20)
    _DPICtrlCreateInput("Test Input", 15, 50, 300, 24)
    _DPICtrlCreateCheckbox("Test Checkbox1 ", 15, 85)
    _DPICtrlCreateCheckbox("Test Checkbox2 ", 15, 110)
    _DPICtrlEditCreate($hGUI, "This is a test" & @CRLF & "Another Line", 15, 140, 394, 80)

    $g_iScale = _WinAPI_GetDPIForWindow($hGUI) / 96
    If @error Then $g_iScale = 1

    Local $aWin = WinGetPos($hGUI)
    Local $aClient = WinGetClientSize($hGUI)
    $g_iFrameW = $aWin[2] - $aClient[0]
    $g_iFrameH = $aWin[3] - $aClient[1]

    _ApplyDPI($hGUI, $g_iScale * 96)

    GUISetState(@SW_SHOW, $hGUI)

    GUIRegisterMsg($WM_DPICHANGED, _WM_DPICHANGED)

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd

    GUIDelete($hGUI)
EndFunc   ;==>Example

; ------------------------------------------------------------------------------
; Wrapper functions – auto‑register each control with its design size
; ------------------------------------------------------------------------------

Func _DPICtrlCreateLabel($sText, $iX, $iY, $iW = -1, $iH = -1, $iStyle = -1, $iExStyle = -1)
    Local $id = GUICtrlCreateLabel($sText, $iX, $iY, $iW, $iH, $iStyle, $iExStyle)
    Local $hCtrl = GUICtrlGetHandle($id)
    Local $iRealW = $iW, $iRealH = $iH
    If $iW = -1 Or $iH = -1 Then
        Local $aRect = _WinAPI_GetWindowRect($hCtrl)
        If Not @error And IsArray($aRect) Then
            If $iW = -1 Then $iRealW = $aRect[2] - $aRect[0]
            If $iH = -1 Then $iRealH = $aRect[3] - $aRect[1]
        Else
            If $iW = -1 Then $iRealW = 100
            If $iH = -1 Then $iRealH = 20
        EndIf
    EndIf
    _AddDesignCtrl($hCtrl, $iX, $iY, $iRealW, $iRealH)
    Return $id
EndFunc

Func _DPICtrlCreateInput($sText, $iX, $iY, $iW, $iH, $iStyle = -1, $iExStyle = -1)
    Local $id = GUICtrlCreateInput($sText, $iX, $iY, $iW, $iH, $iStyle, $iExStyle)
    _AddDesignCtrl(GUICtrlGetHandle($id), $iX, $iY, $iW, $iH)
    Return $id
EndFunc

Func _DPICtrlCreateCheckbox($sText, $iX, $iY, $iW = -1, $iH = -1, $iStyle = -1)
    Local $id = GUICtrlCreateCheckbox($sText, $iX, $iY, $iW, $iH, $iStyle)
    Local $hCtrl = GUICtrlGetHandle($id)
    Local $iRealW = $iW, $iRealH = $iH
    If $iW = -1 Or $iH = -1 Then
        Local $aRect = _WinAPI_GetWindowRect($hCtrl)
        If Not @error And IsArray($aRect) Then
            If $iW = -1 Then $iRealW = $aRect[2] - $aRect[0]
            If $iH = -1 Then $iRealH = $aRect[3] - $aRect[1]
        Else
            If $iW = -1 Then $iRealW = 120
            If $iH = -1 Then $iRealH = 20
        EndIf
    EndIf
    _AddDesignCtrl($hCtrl, $iX, $iY, $iRealW, $iRealH)
    Return $id
EndFunc

Func _DPICtrlEditCreate($hGUI, $sText, $iX, $iY, $iW, $iH, $iStyle = -1, $iExStyle = -1)
    Local $hEdit = _GUICtrlEdit_Create($hGUI, $sText, $iX, $iY, $iW, $iH, $iStyle, $iExStyle)
    _AddDesignCtrl($hEdit, $iX, $iY, $iW, $iH)
    Return $hEdit
EndFunc

; Store control’s design dimensions (96 DPI)
Func _AddDesignCtrl($hCtrlOrID, $iX, $iY, $iW, $iH)
    Local $hCtrl = GUICtrlGetHandle($hCtrlOrID)
    If $hCtrl = 0 Then $hCtrl = $hCtrlOrID
    Local $iCount = UBound($g_aCtrlDesign, 1)
    ReDim $g_aCtrlDesign[$iCount + 1][5]
    $g_aCtrlDesign[$iCount][0] = $hCtrl
    $g_aCtrlDesign[$iCount][1] = $iX
    $g_aCtrlDesign[$iCount][2] = $iY
    $g_aCtrlDesign[$iCount][3] = $iW
    $g_aCtrlDesign[$iCount][4] = $iH
    $g_aCtrlDesign[0][0] = $iCount
EndFunc

; ------------------------------------------------------------------------------
; DPI change handler – keeps your original variable naming and logic flow
; ------------------------------------------------------------------------------

Func _WM_DPICHANGED($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam

    Local Static $iInitDPI = $g_iScale
    Local Static $iPrevDPI = $g_iScale

    ; Obtain new DPI value (raw)
    Local $iDPI = _WinAPI_LoWord($wParam)
    Local $iNewDPI = $iDPI / 96

    _ApplyDPI($hWnd, $iDPI, DllStructCreate($tagRECT, $lParam))

    Return 0
EndFunc   ;==>_WM_DPICHANGED

; ------------------------------------------------------------------------------
; Core scaling function
; ------------------------------------------------------------------------------

Func _ApplyDPI($hWnd, $iDPI, $tRect = 0)
    Local $iNewDPI = $iDPI / 96
    If $iNewDPI == $g_iScale And $g_hCtrlFont Then Return

    Local $hFont = $g_hCtrlFont
    $g_hCtrlFont = _WinAPI_CreateFont(-Round($fDefPixel * $iNewDPI), 0, 0, 0, $FW_NORMAL, False, False, False, _
            $DEFAULT_CHARSET, $OUT_DEFAULT_PRECIS, $CLIP_DEFAULT_PRECIS, $PROOF_QUALITY, $DEFAULT_PITCH, "Segoe UI")

    _WinAPI_LockWindowUpdate($hWnd)

    ; Adjust window size
    If IsDllStruct($tRect) Then
        ; Use suggested rectangle from the system
        Local $iX = DllStructGetData($tRect, "Left")
        Local $iY = DllStructGetData($tRect, "Top")
        Local $iW = DllStructGetData($tRect, "Right") - $iX
        Local $iH = DllStructGetData($tRect, "Bottom") - $iY
        _WinAPI_SetWindowPos($hWnd, 0, $iX, $iY, $iW, $iH, $SWP_NOZORDER)

        ; Recalculate frame difference after system resize
        Local $aNewWin = WinGetPos($hWnd)
        Local $aNewClient = WinGetClientSize($hWnd)
        $g_iFrameW = $aNewWin[2] - $aNewClient[0]
        $g_iFrameH = $aNewWin[3] - $aNewClient[1]
    Else
        ; First‑time scaling: compute new client area and center the window
        Local $iNewClientW = Round($g_iDesignClientW * $iNewDPI)
        Local $iNewClientH = Round($g_iDesignClientH * $iNewDPI)
        Local $iNewWinW = $iNewClientW + $g_iFrameW
        Local $iNewWinH = $iNewClientH + $g_iFrameH
        WinMove($hWnd, "", Default, Default, $iNewWinW, $iNewWinH)
        _CenterWindow($hWnd)
    EndIf

    ; Enumerate child controls from the design array and apply new positions/sizes
    Local $aCtrls = $g_aCtrlDesign
    For $i = 1 To $aCtrls[0][0]
        Local $hCtrl = $aCtrls[$i][0]
        Local $sClass = "" ; not used but kept for compatibility with original structure
        If Not IsHWnd($hCtrl) Then ContinueLoop

        ; Calculate new coordinates and dimensions directly from design sizes
        Local $iX = Round($aCtrls[$i][1] * $iNewDPI)
        Local $iY = Round($aCtrls[$i][2] * $iNewDPI)
        Local $iW = Round($aCtrls[$i][3] * $iNewDPI)
        Local $iH = Round($aCtrls[$i][4] * $iNewDPI)

        ; Move control using its handle
        _WinAPI_SetWindowPos($hCtrl, 0, $iX, $iY, $iW, $iH, $SWP_NOZORDER)

        ; Set the new scaled font
        _SendMessage($hCtrl, $WM_SETFONT, $g_hCtrlFont, True)
    Next

    ; Update previous DPI factor
    $g_iScale = $iNewDPI

    _WinAPI_LockWindowUpdate(0)

    ; Delete previous font
    If $hFont Then _WinAPI_DeleteObject($hFont)
EndFunc   ;==>_ApplyDPI


Func _CenterWindow($hWnd)
    Local $aWin = WinGetPos($hWnd)
    If Not IsArray($aWin) Then Return
    Local $iW = $aWin[2], $iH = $aWin[3]
    Local $aWork = _GetWorkArea()
    Local $iX = ($aWork[2] - $aWork[0] - $iW) / 2 + $aWork[0]
    Local $iY = ($aWork[3] - $aWork[1] - $iH) / 2 + $aWork[1]
    WinMove($hWnd, "", $iX, $iY)
EndFunc

Func _GetWorkArea()
    Local $tRect = DllStructCreate($tagRECT)
    DllCall("user32.dll", "bool", "SystemParametersInfoW", "uint", 0x0030, "uint", 0, "struct*", $tRect, "uint", 0)
    Local $aRect[4] = [DllStructGetData($tRect, "Left"), DllStructGetData($tRect, "Top"), _
                        DllStructGetData($tRect, "Right"), DllStructGetData($tRect, "Bottom")]
    Return $aRect
EndFunc

Func _Cleanup()
    _WinAPI_DeleteObject($g_hCtrlFont)
EndFunc

Func _WinAPI_GetDPIForWindow($hWnd) ; UEZ
    Local $aResult = DllCall('user32.dll', "uint", "GetDpiForWindow", "hwnd", $hWnd) ;requires Win10 v1607+ / no server support
    If Not IsArray($aResult) Or @error Then Return SetError(1, @extended, 0)
    If Not $aResult[0] Then Return SetError(2, @extended, 0)
    Return $aResult[0]
EndFunc   ;==>_WinAPI_GetDPIForWindow

 

Posted (edited)
24 minutes ago, fanxing said:

It is rather cumbersome to have to wrap and then compute each control individually. At the very least, I have not been able to think of a better approach—for instance, one that would allow the interface to become fully adaptive simply by adding a function or a parameter.

This is fantastic. Your modified example worked wonderfully. Excellent work! :)

I updated my initial example now because when I posted it last night, I had completely forgotten to scale the initial control sizes by the DPI factor which is why it got off to a bad start especially on 200% or higher.

EDIT: By the way, the more that I read your updated code example, the more brilliant that I realize it is. I've got a lot to learn and your updated example will absolutely help me learn more. So I am very thankful.

Edited by WildByDesign

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...