Jump to content

Recommended Posts

Posted

I've seen many ListView header subclassing examples in the forum that use CUSTOMDRAW to change colors with SetTextColor and SetBkColor.

But I'm wondering, is it technically possible to color the background of LV header items with _WinAPI_CreateSolidBrush and _WinAPI_FillRect while setting the background mix mode with _WinAPI_SetBkMode to transparent?

This would, of course, require the text color to be changed with _WinAPI_SetTextColor and drawn with _WinAPI_DrawText.

I'm just not even sure if this is possible with CUSTOMDRAW because I simply don't have much experience with it.

My biggest problem and misunderstanding is that I don't know how to get the rectangle sizes to start with to even test whether or not the FillRect will work.

I will share an example that @UEZ shared as SampleControls.au3 in Dark Mode but I have chopped it down to only what is required for ListView to keep the script as small as possible. The ListView header subclassing is done in the _SubclassProc function at line 147 in this example.

As always, thank you so much for your time and help. :)

; Coded by UEZ build 2025-12-18 beta
; not DPI aware!

#include <APIConstants.au3>
#include <ListViewConstants.au3>
#include <WinAPIConstants.au3>
#include <WinAPIGdi.au3>
#include <WinAPIShellEx.au3>
#include <WinAPISys.au3>
#include <WinAPITheme.au3>

Enum $APPMODE_FORCEDARK

; Dark Mode Colors (RGB)
Global Const $COLOR_BG_DARK = 0x202020
Global Const $COLOR_TEXT_LIGHT = 0xFFFFFF
Global Const $COLOR_EDIT_BG = 0x1E1E1E

; Global variables for subclassing (MUST be declared before _Example()!)
Global $g_hGUI = 0, $g_ListView
Global $g_aControls[50][3] = [[0, 0, 0]] ; [ControlID, hWnd, OldWndProc]
Global $g_iControlCount = 0
Global $g_pSubclassProc = 0

; Structure for NM_CUSTOMDRAW notification
Global Const $tagNMCUSTOMDRAW = _
            $tagNMHDR & ";" & _                                ; Contains NM_CUSTOMDRAW / NMHDR header among other things
            "dword dwDrawStage;" & _                           ; Current drawing stage (CDDS_*)
            "handle hdc;" & _                                  ; Device Context Handle
            "long left;long top;long right;long bottom;" & _   ; Drawing rectangle
            "dword_ptr dwItemSpec;" & _                        ; Item index or other info (depending on the control)
            "uint uItemState;" & _                             ; State Flags (CDIS_SELECTED, CDIS_FOCUS etc.)
            "lparam lItemlParam"                               ; lParam set by the item (e.g., via LVITEM.lParam)

_Example()

Func _Example()

    #Region GUI
    $g_hGUI = GUICreate("Sample GUI with Dark Mode", 400, 400)
    GUISetBkColor($COLOR_BG_DARK, $g_hGUI)
    #EndRegion GUI

    #Region LIST VIEW
    Local $idListView = GUICtrlCreateListView("Sample|ListView|", 10, 10, 380, 380, -1, BitOR($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT, $WS_EX_CLIENTEDGE))
    _AddControlForSubclass($idListView)
    GUICtrlSetBkColor($idListView, $COLOR_EDIT_BG)
    GUICtrlSetColor($idListView, $COLOR_TEXT_LIGHT)
    GUICtrlSetTip(-1, '#Region LIST VIEW')
    GUICtrlCreateListViewItem("A|One", $idListView)
    GUICtrlCreateListViewItem("B|Two", $idListView)
    GUICtrlCreateListViewItem("C|Three", $idListView)
    $g_ListView = GUICtrlGetHandle($idListView)
    #EndRegion LIST VIEW


    ; Apply Dark Mode
    _ApplyDarkModeToAllControls()

    GUISetState(@SW_SHOW)

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

    GUIDelete()
EndFunc   ;==>_Example

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 _AddControlForSubclass($iCtrlID)
    Local $hCtrl = GUICtrlGetHandle($iCtrlID)
    If $hCtrl Then
        $g_aControls[$g_iControlCount][0] = $iCtrlID
        $g_aControls[$g_iControlCount][1] = $hCtrl
        $g_aControls[$g_iControlCount][2] = 0 ; Placeholder for OldWndProc
        $g_iControlCount += 1
    EndIf
EndFunc   ;==>_AddControlForSubclass

Func _ApplyDarkModeToAllControls()
    ; DWM Dark Mode for the main window
    _WinAPI_SetPreferredAppMode($APPMODE_FORCEDARK)

    ; Create subclass callback
    If Not $g_pSubclassProc Then $g_pSubclassProc = DllCallbackRegister("_SubclassProc", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")

    ; Subclass all controls
    Local $hCtrl, $sClass, $hEdit, $hComboLBox, $hHeader, $hUpDown
    For $i = 0 To $g_iControlCount - 1
        $hCtrl = $g_aControls[$i][1]
        If $hCtrl Then
            $sClass = _WinAPI_GetClassName($hCtrl)
            ; Use SetWindowSubclass
            _WinAPI_SetWindowSubclass($hCtrl, DllCallbackGetPtr($g_pSubclassProc), $i, 0)

            ; Special themes for different control types
            Switch StringLower($sClass)
                Case "syslistview32"
                    _WinAPI_SetWindowTheme($hCtrl, "DarkMode_Explorer", 0)
                    ; Also make the ListView header dark
                    $hHeader = _SendMessage($hCtrl, $LVM_GETHEADER, 0, 0)
                    If $hHeader Then
                        _WinAPI_SetWindowTheme($hHeader, "DarkMode_ItemsView", 0)
                    EndIf
                Case Else
                    _WinAPI_SetWindowTheme($hCtrl, "DarkMode_Explorer", 0)
            EndSwitch

            _WinAPI_AllowDarkModeForWindow($hCtrl, True)
        EndIf
    Next

    ; Update theme system
    _WinAPI_RefreshImmersiveColorPolicyState()
    _WinAPI_FlushMenuThemes()
    _WinAPI_DwmSetWindowAttribute($g_hGUI, $DWMWA_USE_IMMERSIVE_DARK_MODE, True)

    ; Redraw GUI
    _WinAPI_RedrawWindow($g_hGUI, 0, 0, $RDW_UPDATENOW)
EndFunc   ;==>_ApplyDarkModeToAllControls

Func _CleanupSubclassing()
    ; Remove all subclasses
    If $g_pSubclassProc Then
        Local $hCtrl
        For $i = 0 To $g_iControlCount - 1
            $hCtrl = $g_aControls[$i][1]
            If $hCtrl Then
                _WinAPI_RemoveWindowSubclass($hCtrl, DllCallbackGetPtr($g_pSubclassProc), $i)
            EndIf
        Next
        DllCallbackFree($g_pSubclassProc)
        $g_pSubclassProc = 0
    EndIf
EndFunc   ;==>_CleanupSubclassing

Func _SubclassProc($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    Switch $iMsg
        Case $WM_NOTIFY
            Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
            Local $hFrom = $tNMHDR.hWndFrom
            Local $iCode = $tNMHDR.Code

            ; --- Adjust ListView Header text color ---
            If $iCode = $NM_CUSTOMDRAW Then
                Local $tNMCUSTOMDRAW = DllStructCreate($tagNMCUSTOMDRAW, $lParam)
                Local $dwDrawStage = $tNMCUSTOMDRAW.dwDrawStage
                Local $hDC = $tNMCUSTOMDRAW.hdc

                Switch $dwDrawStage
                    Case $CDDS_PREPAINT
                        Return $CDRF_NOTIFYITEMDRAW
                    Case $CDDS_ITEMPREPAINT
                        _WinAPI_SetTextColor($hDC, _ColorToCOLORREF($COLOR_TEXT_LIGHT)) ; White text
                        _WinAPI_SetBkColor($hDC, _ColorToCOLORREF($COLOR_BG_DARK))     ; Dark background
                        Return BitOR($CDRF_NEWFONT, $CDRF_NOTIFYPOSTPAINT)
                EndSwitch
            EndIf

        Case $WM_PAINT
            ; Custom Paint for better Dark Mode rendering
            Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
    EndSwitch

    ; Forward standard message to DefSubclassProc
    Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>_SubclassProc

Func _WinAPI_FindWindowEx($hParent, $hAfter, $sClass, $sTitle = "")
    Local $ret = DllCall("user32.dll", "hwnd", "FindWindowExW", "hwnd", $hParent, "hwnd", $hAfter, "wstr", $sClass, "wstr", $sTitle)
    If @error Or Not IsArray($ret) Then Return 0
    Return $ret[0]
EndFunc   ;==>_WinAPI_FindWindowEx

Func _WinAPI_AllowDarkModeForWindow($hWND, $bAllow = True)
    Local $aResult = DllCall("UxTheme.dll", "bool", 133, "hwnd", $hWND, "bool", $bAllow)
    If @error Then Return SetError(1, 0, False)
    Return ($aResult[0] <> 0)
EndFunc   ;==>_WinAPI_AllowDarkModeForWindow

Func _WinAPI_FlushMenuThemes()
    Local $aResult = DllCall("UxTheme.dll", "none", 136)
    If @error Then Return SetError(1, 0, False)
    Return True
EndFunc   ;==>_WinAPI_FlushMenuThemes

Func _WinAPI_RefreshImmersiveColorPolicyState()
    Local $aResult = DllCall("UxTheme.dll", "none", 104)
    If @error Then Return SetError(1, 0, False)
    Return True
EndFunc   ;==>_WinAPI_RefreshImmersiveColorPolicyState

Func _WinAPI_SetPreferredAppMode($iPreferredAppMode) ;Windows 10 Build 18362+
    Local $aResult = DllCall("UxTheme.dll", "long", 135, "long", $iPreferredAppMode)
    If @error Then Return SetError(1, 0, False)
    Return $aResult[0]
EndFunc   ;==>_WinAPI_SetPreferredAppMode
#EndRegion DarkMode API

 

Posted

By the way, the reason why I would like to trying using FillRect/SetBkMode is because I am trying to apply Windows 11 materials to all controls in another app (Files Au3), with the exception of the header seen in the screenshot below. Files Au3 uses a detached header. But I figured that it might be easier to share an example of a ListView header since that is much more common. Anyway, I just wanted to give a bit of reason for why I am trying to do this.

 

filesau3.png

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