Jump to content

Need help with subclassing UpDown (spinner) control


Go to solution Solved by Nine,

Recommended Posts

Posted (edited)

This is something that I need for the GUIDarkTheme UDF project, but would be beneficial in general for anyone using dark mode in their GUI.

For Windows 11 users, there is no problem because you can use _WinAPI_SetWindowTheme($h_UpDown, "DarkMode_Explorer") on the UpDown control handle.

Problem: This does not work for Windows 10 users because DarkMode_Explorer pulls the resources from DarkMode_Explorer::Spin in the msstyles theme file and those specific resources do not exist for Windows 10 users.

We would have to do subclassing to achieve dark mode on UpDown controls for Windows 10 users and this is where I need some help. If you have some time and have to skills to do this, I would really appreciate it. Please and thank you. :)

There are some good examples for subclassing UpDown controls in C++ in the win32-darkmodelib library:

Link: win32-darkmodelib/src/DmlibSubclassControl.cpp at main · ozone10/win32-darkmodelib

Link: win32-darkmodelib/src/Darkmodelib.cpp at main · ozone10/win32-darkmodelib

I've made an example to get things started:

DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsStylesConstants.au3>
#include <WinAPITheme.au3>
#include <WinAPISysWin.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPIGdiDC.au3>
#include <APIGdiConstants.au3>

Example()

Func Example()
    GUICreate("Dark Mode UpDown", 400, 300)
    GUISetBkColor(0x323232)
    GUISetFont(14)

    ; Apply dark mode to
    GUIRegisterMsg($WM_CTLCOLOREDIT, "_WM_CTLCOLOR")

    Local $idInput = GUICtrlCreateInput("2", 100, 100, 50, 20, -1, $WS_EX_STATICEDGE)
    Local $h_Input = GUICtrlGetHandle($idInput)
    Local $idUpDown = GUICtrlCreateUpdown($idInput)
    Local $h_UpDown = GUICtrlGetHandle($idUpDown)

    ; Resize input control
    GUICtrlSetPos($idInput, 150, 100, 100, 40)

    If @OSBuild >= 22000 Then
        ; Apply dark mode to UpDown (spinner) control on Windows 11
        _WinAPI_SetWindowTheme($h_UpDown, "DarkMode_Explorer") ; pulls resources from DarkMode_Explorer::Spin in theme file
                                                               ; Problem: Windows 10 does not have DarkMode_Explorer::Spin
    EndIf

    If @OSBuild > 10240 And @OSBuild <= 19045 Then
        ; Apply dark mode to UpDown (spinner) control on Windows 10
        ; TODO: This would have to be achieved with subclassing
        ; C++ subclassing examples:
        ;       https://github.com/ozone10/win32-darkmodelib/blob/main/src/DmlibSubclassControl.cpp#L1020
        ;       https://github.com/ozone10/win32-darkmodelib/blob/main/src/Darkmodelib.cpp#L1168
    EndIf

    GUISetState(@SW_SHOW)

    Local $idMsg
    ; Loop until the user exits.
    While 1
        $idMsg = GUIGetMsg()

        Switch $idMsg
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func _WM_CTLCOLOR($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg
    Local $hDC = $wParam
    Local $hCtrl = $lParam
    _WinAPI_SetTextColor($hDC, 0xFFFFFF)
    Local $hBrush = _WinAPI_CreateSolidBrush(0x202020)
    _WinAPI_SetBkColor($hDC, 0x202020)
    _WinAPI_SetBkMode($hDC, $TRANSPARENT)
    Return $hBrush
EndFunc   ;==>_WM_CTLCOLOR

 

Edited by WildByDesign
Posted

Here something to get you started :

; From Nine
#include <GUIConstants.au3>
#include <WinAPI.au3>
#include <WindowsNotifsConstants.au3>

Global Const $UDM_GETBUDDY = 1130

Example()

Func Example()
  GUICreate("UpDown")

  Local $idInput = GUICtrlCreateInput("0", 10, 10, 50, 20)
  Local $idUpDown = GUICtrlCreateUpdown($idInput)
  Local $hUpDown = GUICtrlGetHandle($idUpDown)

  Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
  _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
  GUISetState()

  Do
  Until GUIGetMsg() = $GUI_EVENT_CLOSE

  _WinAPI_RemoveWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
  DllCallbackFree($hDll)
EndFunc   ;==>Example

Func UpDownSub($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
  Switch $iMsg
    Case $WM_PAINT
      Local $tPaint, $hDC = _WinAPI_BeginPaint($hWnd, $tPaint)
      Local $tRect = _WinAPI_GetClientRect($hWnd)

      Local $hBrush = _WinAPI_CreateSolidBrush(0x808080)
      _WinAPI_SetBkMode($hDC, $TRANSPARENT)
      _WinAPI_SelectObject($hDC, $hBrush)
      _WinAPI_Rectangle($hDC, $tRect)

      _WinAPI_SetTextColor($hDC, 0xFFFFFF)
      Local $hFont = _WinAPI_CreateFont(8, 6)
      _WinAPI_SelectObject($hDC, $hFont)
      $tRect.top += 2
      _WinAPI_DrawText($hDC, "▲" & @LF & "▼", $tRect, $DT_CENTER)

      _WinAPI_DeleteObject($hBrush)
      _WinAPI_DeleteObject($hFont)
      _WinAPI_EndPaint($hWnd, $tPaint)
    Case $WM_LBUTTONDOWN
      Local $hBuddy = HWnd(_SendMessage($hWnd, $UDM_GETBUDDY))
      Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
      ControlSetText("", "", $hBuddy, ControlGetText("", "", $hBuddy) + ($iPos ? -1 : +1))
      _SendMessage($hBuddy, $EM_SETSEL, 0, -1)
      Return
    Case $WM_LBUTTONUP, $WM_MOUSEMOVE
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
  EndSwitch
  Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>UpDownSub

That should work with any OS...

Posted (edited)
9 hours ago, Nine said:

Here something to get you started :

Thank you so much. I've been playing around with this example off and on throughout the day trying various things. I appreciate that you took time to help me out with this. Your example works great, as always.

I tried a few times through the day to extend this in various ways but failed spectacularly on all attempts. I tried, for example:

  • Split the rectangle into two separate rectangles based on the height of the control (since height could vary)
  • Adding indication of hovering the cursor over the rectangle(s) to show different color

Usually I am pretty good at figuring things out, but UpDown controls are peculiar.

I like your idea of using fonts to draw the arrows. I was hoping to split the rectangle into two rectangles (buttons) and draw the arrow in each, horizontally and vertically centered, since from the perspective of the UDF these UpDown controls could vary in height.

I was really hoping that I could make some progress on my own before reaching out for more help. Sorry about that.

Edited by WildByDesign
Posted

Here a tad more elaborated :

; From Nine
#include <GUIConstants.au3>
#include <WinAPI.au3>
#include <WindowsNotifsConstants.au3>

Global Const $UDM_GETBUDDY = 1130

Example()

Func Example()
  GUICreate("UpDown")

  Local $idInput = GUICtrlCreateInput("0", 10, 10, 60, 25)
  Local $idUpDown = GUICtrlCreateUpdown($idInput)
  Local $hUpDown = GUICtrlGetHandle($idUpDown)

  Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
  _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
  GUISetState()

  Do
  Until GUIGetMsg() = $GUI_EVENT_CLOSE

  _WinAPI_RemoveWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
  DllCallbackFree($hDll)
EndFunc   ;==>Example

Func UpDownSub($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
  Local Static $bHover
  Switch $iMsg
    Case $WM_PAINT
      Local $tPaint, $hDC = _WinAPI_BeginPaint($hWnd, $tPaint)
      Local $tRect = _WinAPI_GetClientRect($hWnd)

      Local $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0)
      Local $hBrush = _WinAPI_CreateSolidBrush(0x808080)
      _WinAPI_SetBkMode($hDC, $TRANSPARENT)
      _WinAPI_SelectObject($hDC, $hBrush)
      _WinAPI_Rectangle($hDC, $tRect)
      _WinAPI_DrawLine($hDC, 0, Int($tRect.bottom / 2), $tRect.right, Int($tRect.bottom / 2))
      _WinAPI_DeleteObject($hPen)
      _WinAPI_DeleteObject($hBrush)

      If $bHover Then
        Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
        $hBrush = _WinAPI_CreateSolidBrush(0xA0A0A0)
        $tRectTmp = _WinAPI_GetClientRect($hWnd)
        If $iPos Then
          $tRectTmp.top = Int($tRect.bottom / 2)
        Else
          $tRectTmp.bottom = Int($tRect.bottom / 2)
        EndIf
        _WinAPI_SelectObject($hDC, $hBrush)
        _WinAPI_Rectangle($hDC, $tRectTmp)
        _WinAPI_DeleteObject($hBrush)
      EndIf

      _WinAPI_SetTextColor($hDC, 0xFFFFFF)
      Local $hFont = _WinAPI_CreateFont(Int($tRect.bottom / 3), Int($tRect.right / 3))
      _WinAPI_SelectObject($hDC, $hFont)
      $tRect.top += 2
      _WinAPI_DrawText($hDC, "▲", $tRect, $DT_CENTER)
      $tRect.top += Int($tRect.bottom / 2)
      _WinAPI_DrawText($hDC, "▼", $tRect, $DT_CENTER)

      _WinAPI_DeleteObject($hFont)
      _WinAPI_EndPaint($hWnd, $tPaint)
    Case $WM_LBUTTONDOWN
      Local $hBuddy = HWnd(_SendMessage($hWnd, $UDM_GETBUDDY))
      Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
      ControlSetText("", "", $hBuddy, ControlGetText("", "", $hBuddy) + ($iPos ? -1 : +1))
      _SendMessage($hBuddy, $EM_SETSEL, 0, -1)
      Return
    Case $WM_LBUTTONUP
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
    Case $WM_MOUSEMOVE
      $bHover = True
      _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE)
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
    Case $WM_MOUSELEAVE
      $bHover = False
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
  EndSwitch
  Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>UpDownSub

 

Posted (edited)
1 hour ago, Nine said:

Here a tad more elaborated :

This is fantastic. Works very well and definitely something that I can work with and hopefully expand some more. Thank you. :)

Here is what I have done so far:

  • Made everything more dark mode
  • Moved the UpDown control to the right by 2 pixels to prevent clipping by Edit control (Windows issue with UpDown controls by default)

Question: Do you know if anything can be done about the flickering that sometimes (not always) happens when hovering over the UpDown buttons?

My current testing example:

DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsStylesConstants.au3>
#include <WinAPITheme.au3>
#include <WinAPISysWin.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPIGdiDC.au3>
#include <APIGdiConstants.au3>
#include <GUIConstants.au3>

#include <WinAPI.au3>
#include <Array.au3>

Global Const $UDM_GETBUDDY = 1130

Example()

Func Example()
    Local $hGUI = GUICreate("Dark Mode UpDown", 400, 300)
    GUISetBkColor(0x323232)
    GUISetFont(14)

    ; Apply dark mode to Edit box
    GUIRegisterMsg($WM_CTLCOLOREDIT, "_WM_CTLCOLOR")

    Local $idInput = GUICtrlCreateInput("2", 100, 100, 50, 20, -1, $WS_EX_STATICEDGE)
    Local $h_Input = GUICtrlGetHandle($idInput)
    Local $idUpDown = GUICtrlCreateUpdown($idInput)
    Local $hUpDown = GUICtrlGetHandle($idUpDown)

    ; Resize input control
    GUICtrlSetPos($idInput, 150, 100, 100, 40)

    ; Move UpDown control by 2 pixels to prevent clipping
    Local $aPos = ControlGetPos($hGUI, "", "[CLASS:msctls_updown32]")
    GUICtrlSetPos($idUpDown, $aPos[0] + 2, $aPos[1])

    If @OSBuild >= 22000 Then
        ; Apply dark mode to UpDown (spinner) control on Windows 11
        ;_WinAPI_SetWindowTheme($hUpDown, "DarkMode_Explorer") ; pulls resources from DarkMode_Explorer::Spin in theme file
                                                               ; Problem: Windows 10 does not have DarkMode_Explorer::Spin
    EndIf

    If @OSBuild > 10240 And @OSBuild <= 19045 Then
        ; Apply dark mode to UpDown (spinner) control on Windows 10
        ; TODO: This would have to be achieved with subclassing
        ; C++ subclassing examples:
        ;       https://github.com/ozone10/win32-darkmodelib/blob/main/src/DmlibSubclassControl.cpp#L1020
        ;       https://github.com/ozone10/win32-darkmodelib/blob/main/src/Darkmodelib.cpp#L1168
        Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
        _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
    EndIf

    Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)

    GUISetState(@SW_SHOW)

    Local $idMsg
    ; Loop until the user exits.
    While 1
        $idMsg = GUIGetMsg()

        Switch $idMsg
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd

    _WinAPI_RemoveWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
    DllCallbackFree($hDll)
EndFunc   ;==>Example

Func _WM_CTLCOLOR($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg
    Local $hDC = $wParam
    Local $hCtrl = $lParam
    _WinAPI_SetTextColor($hDC, 0xFFFFFF)
    Local $hBrush = _WinAPI_CreateSolidBrush(0x202020)
    _WinAPI_SetBkColor($hDC, 0x202020)
    _WinAPI_SetBkMode($hDC, $TRANSPARENT)
    Return $hBrush
EndFunc   ;==>_WM_CTLCOLOR

Func UpDownSub($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    Local Static $bHover
    Switch $iMsg
        Case $WM_PAINT
        Local $tPaint, $hDC = _WinAPI_BeginPaint($hWnd, $tPaint)
        Local $tRect = _WinAPI_GetClientRect($hWnd)

        Local $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0)
        Local $hBrush = _WinAPI_CreateSolidBrush(0x808080)
        _WinAPI_SetBkMode($hDC, $TRANSPARENT)
        _WinAPI_SelectObject($hDC, $hBrush)
        _WinAPI_Rectangle($hDC, $tRect)
        _WinAPI_DrawLine($hDC, 0, Int($tRect.bottom / 2), $tRect.right, Int($tRect.bottom / 2))
        _WinAPI_DeleteObject($hPen)
        _WinAPI_DeleteObject($hBrush)

        If $bHover Then
            Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
            $hBrush = _WinAPI_CreateSolidBrush(0xA0A0A0)
            $tRectTmp = _WinAPI_GetClientRect($hWnd)
            If $iPos Then
            $tRectTmp.top = Int($tRect.bottom / 2)
            Else
            $tRectTmp.bottom = Int($tRect.bottom / 2)
            EndIf
            _WinAPI_SelectObject($hDC, $hBrush)
            _WinAPI_Rectangle($hDC, $tRectTmp)
            _WinAPI_DeleteObject($hBrush)
        EndIf

        _WinAPI_SetTextColor($hDC, 0xFFFFFF)
        Local $hFont = _WinAPI_CreateFont(Int($tRect.bottom / 3), Int($tRect.right / 3))
        _WinAPI_SelectObject($hDC, $hFont)
        $tRect.top += 2
        _WinAPI_DrawText($hDC, "▲", $tRect, $DT_CENTER)
        $tRect.top += Int($tRect.bottom / 2)
        _WinAPI_DrawText($hDC, "▼", $tRect, $DT_CENTER)

        _WinAPI_DeleteObject($hFont)
        _WinAPI_EndPaint($hWnd, $tPaint)
        Case $WM_LBUTTONDOWN
        Local $hBuddy = HWnd(_SendMessage($hWnd, $UDM_GETBUDDY))
        Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
        ControlSetText("", "", $hBuddy, ControlGetText("", "", $hBuddy) + ($iPos ? -1 : +1))
        _SendMessage($hBuddy, $EM_SETSEL, 0, -1)
        Return
        Case $WM_LBUTTONUP
        _WinAPI_InvalidateRect($hWnd, 0, False)
        Return
        Case $WM_MOUSEMOVE
        $bHover = True
        _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE)
        _WinAPI_InvalidateRect($hWnd, 0, False)
        Return
        Case $WM_MOUSELEAVE
        $bHover = False
        _WinAPI_InvalidateRect($hWnd, 0, False)
        Return
    EndSwitch
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>UpDownSub

 

Edited by WildByDesign
Posted
7 hours ago, Nine said:

See _WinAPI_CreateCompatibleDC.  You have used that API previously...

Yes, it is included in a few of my projects. But unfortunately, it is copy and pasted and I don't truly understand it.

So I spent a few hours today trying to learn and understand it. I attempted to follow 4 or 5 different examples since they were all slightly different. All of my attempts failed to show anything on the buttons at all.

I really wanted to at least be able to contribute something to this. After failing with the double-buffering stuff, I tried to trigger a button color change on click so that I could at least come back with something. I was able to get the color change working on click. But from all of my attempts, the color would stay until I would move the cursor a bit. I even tried messing with timers and much more. No luck.

You've got something really special started with this. It works very well and looks even better than what Microsoft provides with their own UpDown control. However, my skills and lack of understanding of subclassing is not able to provide anything to build onto what you have started. I apologize. I put in a lot of time and tried my best.

  • Solution
Posted (edited)

I included your control move of the up-down so it looks better when interacting with the input box.

; From Nine
#include <GUIConstants.au3>
#include <WinAPI.au3>
#include <WindowsNotifsConstants.au3>

Global Const $UDM_GETBUDDY = 1130

Example()

Func Example()
  GUICreate("UpDown")

  Local $idInput = GUICtrlCreateInput("0", 10, 10, 60, 25)
  GUICtrlSetFont(-1, 12)
  Local $idUpDown = GUICtrlCreateUpdown($idInput)
  Local $hUpDown = GUICtrlGetHandle(-1)
  GUICtrlSetPos(-1, ControlGetPos("", "", $hUpDown)[0] + 2)

  Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
  _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
  GUISetState()

  Do
  Until GUIGetMsg() = $GUI_EVENT_CLOSE

  _WinAPI_RemoveWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
  DllCallbackFree($hDll)
EndFunc   ;==>Example

Func UpDownSub($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
  Local Static $bHover
  Switch $iMsg
    Case $WM_PAINT
      Local $tPaint, $hDC = _WinAPI_BeginPaint($hWnd, $tPaint)
      Local $tRect = _WinAPI_GetClientRect($hWnd)
      Local $hMemDC  = _WinAPI_CreateCompatibleDC($hDC)
      Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $tRect.right, $tRect.bottom)
      Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap)

      Local $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0)
      Local $hBrush = _WinAPI_CreateSolidBrush(0x808080)
      _WinAPI_SetBkMode($hMemDC, $TRANSPARENT)
      _WinAPI_SelectObject($hMemDC, $hBrush)
      _WinAPI_Rectangle($hMemDC, $tRect)
      _WinAPI_DrawLine($hMemDC, 0, Int($tRect.bottom / 2), $tRect.right, Int($tRect.bottom / 2))
      _WinAPI_DeleteObject($hPen)
      _WinAPI_DeleteObject($hBrush)

      If $bHover Then
        Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
        $hBrush = _WinAPI_CreateSolidBrush(0xA0A0A0)
        $tRectTmp = _WinAPI_GetClientRect($hWnd)
        If $iPos Then
          $tRectTmp.top = Int($tRect.bottom / 2)
        Else
          $tRectTmp.bottom = Int($tRect.bottom / 2)
        EndIf
        _WinAPI_SelectObject($hMemDC, $hBrush)
        _WinAPI_Rectangle($hMemDC, $tRectTmp)
        _WinAPI_DeleteObject($hBrush)
      EndIf

      _WinAPI_SetTextColor($hMemDC, 0xFFFFFF)
      Local $hFont = _WinAPI_CreateFont(Int($tRect.bottom / 3), Int($tRect.right / 3))
      _WinAPI_SelectObject($hMemDC, $hFont)
      $tRect.top += 2
      _WinAPI_DrawText($hMemDC, "▲", $tRect, $DT_CENTER)
      $tRect.top += Int($tRect.bottom / 2)
      _WinAPI_DrawText($hMemDC, "▼", $tRect, $DT_CENTER)

      _WinAPI_BitBlt($hDC, 0, 0, $tRect.right, $tRect.bottom, $hMemDC, 0, 0, $SRCCOPY)

      _WinAPI_SelectObject($hMemDC, $hOldBmp)
      _WinAPI_DeleteObject($hBitmap)
      _WinAPI_DeleteDC($hMemDC)
      _WinAPI_DeleteObject($hFont)
      _WinAPI_EndPaint($hWnd, $tPaint)
    Case $WM_LBUTTONDOWN
      Local $hBuddy = HWnd(_SendMessage($hWnd, $UDM_GETBUDDY))
      Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
      ControlSetText("", "", $hBuddy, ControlGetText("", "", $hBuddy) + ($iPos ? -1 : +1))
      _SendMessage($hBuddy, $EM_SETSEL, 0, -1)
      Return
    Case $WM_LBUTTONUP
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
    Case $WM_MOUSEMOVE
      $bHover = True
      _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE)
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
    Case $WM_MOUSELEAVE
      $bHover = False
      _WinAPI_InvalidateRect($hWnd, 0, False)
      Return
  EndSwitch
  Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>UpDownSub

 

Edited by Nine
Posted
11 minutes ago, Nine said:

I included your control move of the up-down so it looks better when interacting with the input box.

Thank you for the update, I appreciate it. The double-buffering makes a significant difference.

I can immediately see where I went wrong with it. I had everything correct, except that I had my _WinAPI_BitBlt() function in the wrong place.

Posted

I'm finally starting to understand. I've got the "on click" button colour change now. All of the 3-4 methods that I did likely would have worked, but I was missing one thing:

Case $WM_LBUTTONDOWN
; ...
    _WinAPI_InvalidateRect($hWnd, 0, False)
    Return

I was reading up on how to trigger WM_PAINT. Then it was like "Uh Huh!" :)

In the GUIDarkTheme UDF, I will have to check any UpDown controls for UDS_HORZ style and do a horizontal variant of the buttons at some point. But one step at a time.

Question: The two rectangle buttons show a black line around them. Is it possible to change that outline colour? For example, maybe a plain white button outline colour. I am not quite sure what would look best in the end, but I am curious about that.

Anyway, here is my current dark mode interpretation of @Nine's script with the addition of the "on click" button colour change:

DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", @AutoItX64 ? "int64" : "int", -2)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsStylesConstants.au3>
#include <WinAPITheme.au3>
#include <WinAPISysWin.au3>
#include <WindowsNotifsConstants.au3>
#include <WinAPIGdiDC.au3>
#include <APIGdiConstants.au3>
#include <GUIConstants.au3>

#include <WinAPI.au3>
#include <Array.au3>
#include <Misc.au3>

Global Const $UDM_GETBUDDY = 1130
Global $hGUI
Global $hUser32Dll = DllOpen("user32.dll")

Example()

Func Example()
    $hGUI = GUICreate("Dark Mode UpDown", 400, 300)
    GUISetBkColor(0x323232)
    GUISetFont(14)

    ; Apply dark mode to Edit box
    GUIRegisterMsg($WM_CTLCOLOREDIT, "_WM_CTLCOLOR")

    Local $idInput = GUICtrlCreateInput("2", 100, 100, 50, 20, -1, $WS_EX_STATICEDGE)
    Local $h_Input = GUICtrlGetHandle($idInput)
    Local $idUpDown = GUICtrlCreateUpdown($idInput)
    Local $hUpDown = GUICtrlGetHandle($idUpDown)

    ; Resize input control
    GUICtrlSetPos($idInput, 150, 100, 100, 40)

    ; Move UpDown control by 2 pixels to prevent clipping
    GUICtrlSetPos($idUpDown, ControlGetPos("", "", $hUpDown)[0] + 2)

    If @OSBuild >= 22000 Then
        ; Apply dark mode to UpDown (spinner) control on Windows 11
        ;_WinAPI_SetWindowTheme($hUpDown, "DarkMode_Explorer") ; pulls resources from DarkMode_Explorer::Spin in theme file
                                                               ; Problem: Windows 10 does not have DarkMode_Explorer::Spin
    EndIf

    If @OSBuild > 10240 And @OSBuild <= 19045 Then
        ; Apply dark mode to UpDown (spinner) control on Windows 10
        ; TODO: This would have to be achieved with subclassing
        ; C++ subclassing examples:
        ;       https://github.com/ozone10/win32-darkmodelib/blob/main/src/DmlibSubclassControl.cpp#L1020
        ;       https://github.com/ozone10/win32-darkmodelib/blob/main/src/Darkmodelib.cpp#L1168
        Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
        _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
    EndIf

    Local $hDll = DllCallbackRegister(UpDownSub, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    _WinAPI_SetWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)

    GUISetState(@SW_SHOW)

    Local $idMsg
    ; Loop until the user exits.
    While 1
        $idMsg = GUIGetMsg()

        Switch $idMsg
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd

    _WinAPI_RemoveWindowSubclass($hUpDown, DllCallbackGetPtr($hDll), $idUpDown)
    DllCallbackFree($hDll)
    DllClose($hUser32Dll)
EndFunc   ;==>Example

Func _WM_CTLCOLOR($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg
    Local $hDC = $wParam
    Local $hCtrl = $lParam
    _WinAPI_SetTextColor($hDC, 0xFFFFFF)
    Local $hBrush = _WinAPI_CreateSolidBrush(0x202020)
    _WinAPI_SetBkColor($hDC, 0x202020)
    _WinAPI_SetBkMode($hDC, $TRANSPARENT)
    Return $hBrush
EndFunc   ;==>_WM_CTLCOLOR

Func UpDownSub($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    Local Static $bHover
    Switch $iMsg
        Case $WM_PAINT
            Local $tPaint, $hDC = _WinAPI_BeginPaint($hWnd, $tPaint)
            Local $tRect = _WinAPI_GetClientRect($hWnd)
            Local $hMemDC  = _WinAPI_CreateCompatibleDC($hDC)
            Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $tRect.right, $tRect.bottom)
            Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap)

            Local $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0)
            Local $hBrush = _WinAPI_CreateSolidBrush(0x242424)
            _WinAPI_SetBkMode($hMemDC, $TRANSPARENT)
            _WinAPI_SelectObject($hMemDC, $hBrush)
            _WinAPI_Rectangle($hMemDC, $tRect)
            _WinAPI_DrawLine($hMemDC, 0, Int($tRect.bottom / 2), $tRect.right, Int($tRect.bottom / 2))
            _WinAPI_DeleteObject($hPen)
            _WinAPI_DeleteObject($hBrush)

            If $bHover Then
                Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
                $hBrush = _WinAPI_CreateSolidBrush(_IsPressed($VK_LBUTTON, $hUser32Dll) ? 0x404040 : 0x808080)
                $tRectTmp = _WinAPI_GetClientRect($hWnd)
                If $iPos Then
                    $tRectTmp.top = Int($tRect.bottom / 2)
                Else
                    $tRectTmp.bottom = Int($tRect.bottom / 2)
                EndIf
                _WinAPI_SelectObject($hMemDC, $hBrush)
                _WinAPI_Rectangle($hMemDC, $tRectTmp)
                _WinAPI_DeleteObject($hBrush)
            EndIf

            _WinAPI_SetTextColor($hMemDC, 0xFFFFFF)
            Local $hFont = _WinAPI_CreateFont(Int($tRect.bottom / 3), Int($tRect.right / 3))
            _WinAPI_SelectObject($hMemDC, $hFont)
            $tRect.top += 2
            _WinAPI_DrawText($hMemDC, "▲", $tRect, $DT_CENTER)
            $tRect.top += Int($tRect.bottom / 2)
            _WinAPI_DrawText($hMemDC, "▼", $tRect, $DT_CENTER)

            _WinAPI_BitBlt($hDC, 0, 0, $tRect.right, $tRect.bottom, $hMemDC, 0, 0, $SRCCOPY)

            _WinAPI_SelectObject($hMemDC, $hOldBmp)
            _WinAPI_DeleteObject($hBitmap)
            _WinAPI_DeleteDC($hMemDC)
            _WinAPI_DeleteObject($hFont)
            _WinAPI_EndPaint($hWnd, $tPaint)
        Case $WM_LBUTTONDOWN
            Local $hBuddy = HWnd(_SendMessage($hWnd, $UDM_GETBUDDY))
            Local $iPos = Round(_WinAPI_GetMousePos(True, $hWnd).y / _WinAPI_GetClientRect($hWnd).bottom, 0)
            ControlSetText("", "", $hBuddy, ControlGetText("", "", $hBuddy) + ($iPos ? -1 : +1))
            _SendMessage($hBuddy, $EM_SETSEL, 0, -1)
            _WinAPI_InvalidateRect($hWnd, 0, False)
            Return
        Case $WM_LBUTTONUP
            _WinAPI_InvalidateRect($hWnd, 0, False)
            Return
        Case $WM_MOUSEMOVE
            $bHover = True
            _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE)
            _WinAPI_InvalidateRect($hWnd, 0, False)
            Return
        Case $WM_MOUSELEAVE
            $bHover = False
            _WinAPI_InvalidateRect($hWnd, 0, False)
            Return
    EndSwitch
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>UpDownSub

 

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