Jump to content

Recommended Posts

Posted

In a Dark Theme UDF that I have been working on recently, one of the remaining controls left to receive any kind of dark mode treatment is SysDateTimePick32. Subclassing methods in other programming languages are scarce and it seems to be not easy to subclass properly. So I am trying to think outside the box here and think of other methods.

Example screenshot of current GUIDarkTheme UDF used on SampleControls.au3:

Spoiler

dark-GUI.png

I have thought about some things such as setting the SysDateTimePick32 control as a layer and setting a transparent color which can work, but leaves the text very grainy.

I was thinking about _WinAPI_InvertRgn most recently but not sure how exactly to use it. There aren't any previous examples in the forum either. I have stripped down a copy of SampleControls.au3 and got rid of most of the controls to help isolate SysDateTimePick32 if anyone wants to give it a try.

If _WinAPI_InvertRgn doesn't give any desired results, possibly we can try other ideas here in this thread.

; AutoIt GUI Example
; Created: 17/01/2005 - CyberSlug
; Modifed: 05/12/2011 - guinness
; Modifed: 09/06/2014 - mLipok
; Modifed: 15/10/2018 - mLipok

#Region INCLUDE
#include <GuiConstantsEx.au3>
#EndRegion INCLUDE

; set working directory to the original SampleControls example which contains the necessary resources
FileChangeDir(StringLeft(@AutoItExe, StringInStr(@AutoItExe, "\", 0, -1) -1) & "\Examples\GUI")

#Region INITIALIZATION and EXIT
_Example()
Exit
; Finished!
#EndRegion INITIALIZATION and EXIT

Func _Example()
    #Region GUI
    Local $hGUI = GUICreate("Sample GUI", 400, 440)
    GUISetBkColor(0x202020)
    #EndRegion GUI

    #Region DATE
    GUICtrlCreateDate("", 5, 280, 200, 20)
    #EndRegion DATE

    #Region BUTTON
    Local $idButton = GUICtrlCreateButton("Sample Button", 10, 330, 100, 30)
    GUICtrlSetTip(-1, '#Region BUTTON')
    #EndRegion BUTTON

    #Region GUI MESSAGE LOOP

    ControlFocus($hGUI, "", $idButton)

    GUISetState(@SW_SHOW)
    
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

        EndSwitch
    WEnd

    GUIDelete()
    #EndRegion GUI MESSAGE LOOP

EndFunc   ;==>_Example

 

Posted

Rgn is usually _WinAPI_CreateRectRgn() (or CreateRoundRectRgn), but somehow SysDateTimePick32 doesnt want to play along, also behaves weird like it's drawn from bottom up, as in X,Y pos is not the top left corner but the lower left corner.

Rgn works fine with SetWindowRgn but not with InvertRgn...

#Region DATE
    $hRgn = _WinAPI_CreateRectRgn(5, 300, 210, 330)
            _WinAPI_SetWindowRgn($hGui, $hRgn)
;~          _WinAPI_InvertRgn ($hGUI, $hRgn) ; <-- doesnt want to play along for some reason

    GUICtrlCreateDate("", 5, 280, 200, 20)
    #EndRegion DATE

 

Some guy's script + some other guy's script = my script!

Posted

Thanks @Werty. I still haven't experimented or learned much about creating regions, so even though _WinAPI_InvertRgn may not end up successful in this, at least I end up learning more about Rgn. I appreciate your help.

From my understanding, _WinAPI_InvertRgn is asking for a handle to the DC, not the GUI handle. But what I don't know is whether it is supposed to be the device context for the GUI or the device context for the date control.

There is also _WinAPI_InvertRect which also wants a DC but goes by RECT instead of Rgn. I tried testing it briefly but since I don't understand it very well at all I've probably done it all wrong.

Here is my current testing mess:

; AutoIt GUI Example
; Created: 17/01/2005 - CyberSlug
; Modifed: 05/12/2011 - guinness
; Modifed: 09/06/2014 - mLipok
; Modifed: 15/10/2018 - mLipok

#Region INCLUDE
#include <GuiConstantsEx.au3>
#include <WinAPIGdi.au3>
#EndRegion INCLUDE

; set working directory to the original SampleControls example which contains the necessary resources
FileChangeDir(StringLeft(@AutoItExe, StringInStr(@AutoItExe, "\", 0, -1) -1) & "\Examples\GUI")

#Region INITIALIZATION and EXIT
_Example()
Exit
; Finished!
#EndRegion INITIALIZATION and EXIT

Func _Example()
    #Region GUI
    Local $hGUI = GUICreate("Sample GUI", 400, 440)
    GUISetBkColor(0x202020)
    #EndRegion GUI

    #Region DATE
    Local $idDate = GUICtrlCreateDate("", 5, 280, 200, 20)
    #EndRegion DATE

    #Region BUTTON
    Local $idButton = GUICtrlCreateButton("Sample Button", 10, 330, 100, 30)
    GUICtrlSetTip(-1, '#Region BUTTON')
    #EndRegion BUTTON

    #Region GUI MESSAGE LOOP

    ControlFocus($hGUI, "", $idButton)

    GUISetState(@SW_SHOW)

    Local $aCtrl_pos = ControlGetPos($hGUI, "", $idDate)
    Local $hDC = _WinAPI_GetDC(GUICtrlGetHandle($idDate))
    ;Local $tRECT = _WinAPI_CreateRect(0, 0, 0, 0)
    ;$tRECT.Left = $aCtrl_pos[0]
    ;$tRECT.Top = $aCtrl_pos[1]
    ;$tRECT.Right = $aCtrl_pos[0]
    ;$tRECT.Bottom = $aCtrl_pos[1]
    ;_WinAPI_InvertRect($hDC, $tRECT)
    ;$hRgn = _WinAPI_CreateRectRgn($aCtrl_pos[0], $aCtrl_pos[1], $aCtrl_pos[0] + $aCtrl_pos[2], $aCtrl_pos[1] + $aCtrl_pos[3])
    $hRgn = _WinAPI_CreateRectRgn(5, 300, 210, 330)
    _WinAPI_SetWindowRgn($hGUI, $hRgn)
    _WinAPI_InvertRgn($hDC, $hRgn) ; <-- doesnt want to play along for some reason
    ;_WinAPI_DeleteObject($hRgn)
    
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop

        EndSwitch
    WEnd

    GUIDelete()
    #EndRegion GUI MESSAGE LOOP

EndFunc   ;==>_Example

 

Posted

Not a bad idea Wild.  Here my take on it :

; From Nine
#include <WindowsConstants.au3>
#include <GuiConstants.au3>
#include <GuiDateTimePicker.au3>
#include <WinAPI.au3>

Opt("MustDeclareVars", True)

Example()

Func Example()
  Local $hGUI = GUICreate("Sample GUI", 400, 440)
  GUISetBkColor(0x202020)

  Local $idDate = GUICtrlCreateDate("", 15, 15, 200, 20)

  GUISetState()

  InvertDateColor($hGUI, $idDate)

  While True
    Switch GUIGetMsg()
      Case $GUI_EVENT_CLOSE
        ExitLoop
    EndSwitch
  WEnd
EndFunc   ;==>Example

Func InvertDateColor($hWnd, $idCtrl)
  Local $aPos = ControlGetPos($hWnd, "", $idCtrl)
  Local $tPoint1 = DllStructCreate($tagPOINT), $tPoint2 = DllStructCreate($tagPOINT)
  $tPoint1.X = $aPos[0]
  $tPoint1.Y = $aPos[1]
  _WinAPI_ClientToScreen($hWnd, $tPoint1)

  $tPoint2.X = $aPos[0] + $aPos[2]
  $tPoint2.Y = $aPos[1] + $aPos[3]
  _WinAPI_ClientToScreen($hWnd, $tPoint2)

  $aPos = WinGetPos($hWnd)
  Local $tRect = DllStructCreate($tagRECT)
  $tRect.left = $tPoint1.X - $aPos[0]
  $tRect.top = $tPoint1.Y - $aPos[1]
  $tRect.right = $tPoint2.X - $aPos[0]
  $tRect.bottom = $tPoint2.Y - $aPos[1]

  Local $hDC = _WinAPI_GetWindowDC($hWnd)
  _WinAPI_InvertRect($hDC, $tRect)
EndFunc   ;==>InvertDateColor

We will need to intercept all events that could change the color.   Have to think of the best way now...

Posted
39 minutes ago, Nine said:

Not a bad idea Wild.  Here my take on it :

Thanks. I often have decent ideas or at least the determination to never give up on an idea. Thank you for helping to bring this idea to life. I really had no idea whether it would work or not.

Here is what it looks like when I give the focus (prior to GUISetState) to a button:

calendar.png

I think it looks better than expected. That is with high DPI as well.

41 minutes ago, Nine said:

We will need to intercept all events that could change the color.   Have to think of the best way now...

We would have to intercept anytime the cursor hovers over it and likely click events as well. If you are able to expand on this part, I would really appreciate it. There is a lot of potential with this, I think.

Posted
57 minutes ago, WildByDesign said:

If you are able to expand on this part

I tested different subclassing methods and they were not very conclusive.  Best approach I found so far is this one.  Not perfect but maybe good enough.  We could test 2 regions though, the text part, and the dropdown part.  I'll let you play with it for now...

; From Nine
#include <WindowsConstants.au3>
#include <GuiConstants.au3>
#include <GuiDateTimePicker.au3>
#include <WinAPI.au3>

Opt("MustDeclareVars", True)

Example()

Func Example()
  Local $hGUI = GUICreate("Sample GUI", 400, 440)
  GUISetBkColor(0x202020)

  Local $idDate = GUICtrlCreateDate("", 15, 15, 200, 20)
  GUISetState()

  InvertDateColor($hGUI, $idDate)
  _WinAPI_SetTimer($hGUI, $idDate, 10, 0)
  GUIRegisterMsg($WM_TIMER, WM_TIMER)

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

  _WinAPI_KillTimer($hGUI, $idDate)
EndFunc   ;==>Example

Func InvertDateColor($hWnd, $idCtrl)
  Local Static $nColor
  Local $aPos = ControlGetPos($hWnd, "", $idCtrl)
  If @error Then Return
  Local $tPoint1 = DllStructCreate($tagPOINT), $tPoint2 = DllStructCreate($tagPOINT)
  $tPoint1.X = $aPos[0]
  $tPoint1.Y = $aPos[1]
  _WinAPI_ClientToScreen($hWnd, $tPoint1)

  $tPoint2.X = $aPos[0] + $aPos[2]
  $tPoint2.Y = $aPos[1] + $aPos[3]
  _WinAPI_ClientToScreen($hWnd, $tPoint2)

  $aPos = WinGetPos($hWnd)
  Local $tRect = DllStructCreate($tagRECT)
  $tRect.left = $tPoint1.X - $aPos[0]
  $tRect.top = $tPoint1.Y - $aPos[1]
  $tRect.right = $tPoint2.X - $aPos[0]
  $tRect.bottom = $tPoint2.Y - $aPos[1]

  Local $hDC = _WinAPI_GetWindowDC($hWnd)
  If $nColor = _WinAPI_GetPixel($hDC, $tRect.left + 2, $tRect.top + 2) Then Return

  _WinAPI_InvertRect($hDC, $tRect)

  $nColor = _WinAPI_GetPixel($hDC, $tRect.left + 2, $tRect.top + 2)
EndFunc   ;==>InvertDateColor

Func WM_TIMER($hWnd, $iMsg, $wParam, $lParam)
  InvertDateColor($hWnd, Int($wParam))
EndFunc   ;==>WM_TIMER

 

  • Solution
Posted (edited)
;Coded by UEZ build 2026-02-28 beta
#include <WindowsConstants.au3>
#include <GuiConstants.au3>
#include <GuiDateTimePicker.au3>
#include <GuiMonthCal.au3>
#include <WinAPI.au3>
#include <WinAPISysWin.au3>
#include <WinAPITheme.au3>

Opt("MustDeclareVars", True)
Global $g_hDateProc_CB, $g_pDateProc_CB, $g_hDateOldProc, $g_hDate
Global Const $PRF_CLIENT = 0x0004, $COLOR_BORDER = 0x404040, $COLOR_BG_DARK = 0x202020, $COLOR_TEXT_LIGHT = 0xFFFFFF, $COLOR_BORDER_LIGHT = 0xD8D8D8
Global $g_bHover = False

Example()

Func Example()
    Local $hGUI = GUICreate("Sample GUI", 400, 240)
    GUISetBkColor($COLOR_BG_DARK)

    Local $idDate = GUICtrlCreateDate("", 15, 15, 200, 20)
    $g_hDate = GUICtrlGetHandle($idDate)
    $g_hDateProc_CB = DllCallbackRegister('_DateProc', 'ptr', 'hwnd;uint;wparam;lparam')
    $g_pDateProc_CB = DllCallbackGetPtr($g_hDateProc_CB)
    $g_hDateOldProc = _WinAPI_SetWindowLong($g_hDate, $GWL_WNDPROC, $g_pDateProc_CB)
    GUIRegisterMsg($WM_NOTIFY, "_WM_NOTIFY")
    GUISetState()

    While True
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _WinAPI_SetWindowLong($g_hDate, $GWL_WNDPROC, $g_hDateOldProc)
                DllCallbackFree($g_hDateProc_CB)
                GUIRegisterMsg($WM_NOTIFY, "")
                GUIDelete()
                ExitLoop
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func _DateProc($hWnd, $iMsg, $wParam, $lParam)
    Switch $iMsg

        Case $WM_PAINT
            Local $tPaint = DllStructCreate($tagPAINTSTRUCT)
            Local $hDC = _WinAPI_BeginPaint($hWnd, $tPaint)

            Local $tClient = _WinAPI_GetClientRect($hWnd)
            Local $iW = $tClient.Right
            Local $iH = $tClient.Bottom

            ; --- Memory DC for flicker-free rendering ---
            Local $hMemDC = _WinAPI_CreateCompatibleDC($hDC)
            Local $hBitmap = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH)
            Local $hOldBmp = _WinAPI_SelectObject($hMemDC, $hBitmap)

            ; 1. Let Windows draw the light-mode control into memory DC
            _WinAPI_CallWindowProc($g_hDateOldProc, $hWnd, $WM_PRINTCLIENT, $hMemDC, $PRF_CLIENT)

            ; 2. Invert all pixels (background becomes black, text white, selection orange)
            Local $tRect = DllStructCreate($tagRECT)
            $tRect.right = $iW
            $tRect.bottom = $iH
            _WinAPI_InvertRect($hMemDC, $tRect)

            ; --- 3. PIXEL HACK: destroy orange highlight & set background color ---
            Local $iSize = $iW * $iH
            Local $tPixels = DllStructCreate("dword c[" & $iSize & "]")
            ; Load pixel array directly from bitmap memory
            Local $iBytes = DllCall("gdi32.dll", "long", "GetBitmapBits", "handle", $hBitmap, "long", $iSize * 4, "ptr", DllStructGetPtr($tPixels))[0]

            If $iBytes = $iSize * 4 Then
                Local $iPixel, $r, $g, $b, $iGray
                For $i = 1 To $iSize
                    $iPixel = $tPixels.c(($i))

                    ; Split into color channels
                    $b = BitAND($iPixel, 0xFF)
                    $g = BitAND(BitShift($iPixel, 8), 0xFF)
                    $r = BitAND(BitShift($iPixel, 16), 0xFF)

                    ; Convert to grayscale (orange becomes mid-gray)
                    $iGray = Int(($r + $g + $b) / 3)

                    ; Very dark pixel = inverted white background
                    If $iGray < 15 Then
                        $iPixel = $COLOR_BG_DARK ; Replace with exact GUI background color
                    Else
                        ; Grayscale value for text (white) and selection (gray)
                        ; (negative BitShift shifts left in AutoIt)
                        $iPixel = BitOR(BitShift($iGray, -16), BitShift($iGray, -8), $iGray)
                    EndIf

                    $tPixels.c(($i)) = $iPixel
                Next
                ; Write cleaned pixels back into the bitmap
                DllCall("gdi32.dll", "long", "SetBitmapBits", "handle", $hBitmap, "long", $iSize * 4, "ptr", DllStructGetPtr($tPixels))
            EndIf
            ; --- END PIXEL HACK ---

            ; --- Border color (hover effect) ---
            Local $iBorderColor = $COLOR_BORDER
            If _WinAPI_GetFocus() = $hWnd Then $iBorderColor = $COLOR_BORDER
            Local $tCursorPos = DllStructCreate($tagPOINT)
            DllCall("user32.dll", "bool", "GetCursorPos", "struct*", $tCursorPos)
            DllCall("user32.dll", "bool", "ScreenToClient", "hwnd", $hWnd, "struct*", $tCursorPos)
            If $tCursorPos.X >= 0 And $tCursorPos.X <= $iW And $tCursorPos.Y >= 0 And $tCursorPos.Y <= $iH Then
                $iBorderColor = $COLOR_BORDER_LIGHT
            EndIf

            ; --- Draw border ---
            Local $hPen = _WinAPI_CreatePen(0, 1, _ColorToCOLORREF($iBorderColor))
            Local $hNullBr = _WinAPI_GetStockObject(5)
            Local $hOldPen = _WinAPI_SelectObject($hMemDC, $hPen)
            Local $hOldBr = _WinAPI_SelectObject($hMemDC, $hNullBr)
            DllCall("gdi32.dll", "bool", "Rectangle", "handle", $hMemDC, "int", 0, "int", 0, "int", $iW, "int", $iH)
            _WinAPI_SelectObject($hMemDC, $hOldPen)
            _WinAPI_SelectObject($hMemDC, $hOldBr)
            _WinAPI_DeleteObject($hPen)

            ; --- Copy finished result to screen in one step (no flicker) ---
            _WinAPI_BitBlt($hDC, 0, 0, $iW, $iH, $hMemDC, 0, 0, $SRCCOPY)

            ; --- Cleanup ---
            _WinAPI_SelectObject($hMemDC, $hOldBmp)
            _WinAPI_DeleteObject($hBitmap)
            _WinAPI_DeleteDC($hMemDC)
            _WinAPI_EndPaint($hWnd, $tPaint)
            Return 0

        Case $WM_ERASEBKGND
            Return 1

        Case $WM_SETFOCUS, $WM_KILLFOCUS, $WM_LBUTTONDOWN, $WM_LBUTTONUP
            Local $iRet = _WinAPI_CallWindowProc($g_hDateOldProc, $hWnd, $iMsg, $wParam, $lParam)
            _WinAPI_InvalidateRect($hWnd, 0, False)
            Return $iRet

        Case $WM_MOUSEMOVE
            Local $iRet = _WinAPI_CallWindowProc($g_hDateOldProc, $hWnd, $iMsg, $wParam, $lParam)
            If Not $g_bHover Then
                $g_bHover = True
                _WinAPI_InvalidateRect($hWnd, 0, False)
            EndIf
            Return $iRet

        Case $WM_MOUSELEAVE
            $g_bHover = False
            _WinAPI_InvalidateRect($hWnd, 0, False)

    EndSwitch

    Return _WinAPI_CallWindowProc($g_hDateOldProc, $hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>_DateProc

Func _ColorToCOLORREF($iColor) ;RGB to BGR
    Local $iR = BitAND(BitShift($iColor, 16), 0xFF)
    Local $iG = BitAND(BitShift($iColor, 8), 0xFF)
    Local $iB = BitAND($iColor, 0xFF)
    Return BitOR(BitShift($iB, -16), BitShift($iG, -8), $iR)
EndFunc   ;==>_ColorToCOLORREF

Func _WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam
    Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $tInfo, $tBuffer, $tBuffer2, $iCtrl
    $tNMHDR   = DllStructCreate($tagNMHDR, $lParam)
    $hWndFrom = HWnd($tNMHDR.hWndFrom)
    $iIDFrom  = $tNMHDR.IDFrom
    $iCode    = $tNMHDR.Code

    Switch $hWndFrom
        Case $g_hDate ; thanks to argumentum for the code :-)
            Switch $iCode
                Case $NM_SETFOCUS
                    ; Disable visual theme when DateTime control receives focus
                    _WinAPI_SetThemeAppProperties(0)

                Case $DTN_DROPDOWN
                    ; Apply dark colors when the calendar dropdown opens
                    _WinAPI_SetWindowTheme($iCtrl, "", "")
                    Local $iCtrl = _GUICtrlDTP_GetMonthCal($hWndFrom)
                    _GUICtrlMonthCal_SetColor($iCtrl, $MCSC_TEXT,         $COLOR_TEXT_LIGHT)
                    _GUICtrlMonthCal_SetColor($iCtrl, $MCSC_TITLEBK,      $COLOR_BG_DARK)
                    _GUICtrlMonthCal_SetColor($iCtrl, $MCSC_TITLETEXT,    $COLOR_TEXT_LIGHT)
                    _GUICtrlMonthCal_SetColor($iCtrl, $MCSC_MONTHBK,      $COLOR_BG_DARK)
                    _GUICtrlMonthCal_SetColor($iCtrl, $MCSC_TRAILINGTEXT, $COLOR_TEXT_LIGHT)

                Case $DTN_CLOSEUP
                    ; Calendar dropdown closed - no action needed

            EndSwitch
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_NOTIFY

 

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Posted

@UEZ That is truly impressive. I was looking into ways to get rid of the orange highlight and you had figured it out pretty quickly. Adding high DPI scaling to this, it looks absolutely production ready. And you retain all of the functionality of the control.

I was looking into hacks that would have taken away focus from the control and we would have lost the ability to use the keyboard to navigate the control. But you nailed it perfectly. Now I understand your message of "Never say never ". Excellent work!

Posted

I have a feeling that this _WinAPI_InvertRect technique might actually produce better results for statusbar in comparison to the actual dark theme. For example, the following lines work successfully to enable native dark mode theme on a statusbar control:

; msctls_statusbar32
_WinAPI_SetWindowTheme($hWnd, "DarkMode_DarkTheme", "Status")   ; Win11 24H2/25H2
_WinAPI_SetWindowTheme($hWnd, "DarkMode", "ExplorerStatusBar")

They work quite well but lack dark theme for the size grip ($SBS_SIZEBOX or $SBS_SIZEGRIP).

But a straight up color invert of the light mode statusbar actually looks even better compared to the actual dark-themed variant. I will have to look into this when I get some more time.

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