Jump to content

How can you CUSTOMDRAW ListView header by State? (CDIS_HOT, etc.)


Go to solution Solved by Nine,

Recommended Posts

Posted (edited)

Ok, I am quite busy at the moment, so I don't have time to clean up and optimize. 

#AutoIt3Wrapper_UseX64=y

; From Nine
#include <GuiConstants.au3>
#include <GuiHeader.au3>
#include <WinAPI.au3>
#include <Misc.au3>
#include <WinAPITheme.au3>
#include <GuiListView.au3>

DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "HWND", "DPI_AWARENESS_CONTEXT" - 2)

Global $mHeader[]

Example()

Func Example()
  Local $hGUI = GUICreate("Header ", 500, 300)
  GUISetBkColor(0x000000)

  Local $idListView = GUICtrlCreateListView("Items List|SubItems1|SubItems2", 10, 10, 480, 280, -1, BitOR($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT))
  Local $hListView = GUICtrlGetHandle($idListView)
  Local $hHeader = GUICtrlSendMsg($idListView, $LVM_GETHEADER, 0, 0)
  _WinAPI_SetWindowTheme($hListView, "DarkMode_Explorer")              ; PROBLEM: cause of random GUI crashes
  _WinAPI_SetWindowTheme($hHeader, "DarkMode_ItemsView", "Header")      ; No problem with dark mode header theme
  GUICtrlSetBkColor($idListView, 0x121212)
  GUICtrlSetColor($idListView, 0xFFFFFF)

  Local $iCount = _GUICtrlListView_GetColumnCount($hListView), $aHeader[$iCount]
  For $i = 0 To $iCount - 1
    $aHeader[$i] = _GUICtrlListView_GetColumn($hListView, $i)[5]
  Next
  $mHeader[$idListView] = $aHeader

  Local $hSubClass = DllCallbackRegister(WM_NOTIFY, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
  _WinAPI_SetWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), _WinAPI_MakeLong($hSubClass, $idListView), $hHeader)
  _WinAPI_SetWindowSubclass($hHeader, DllCallbackGetPtr($hSubClass), 1000, $hHeader)

  For $i = 1 To 20
    GUICtrlCreateListViewItem("item" & $i & "|item" & $i & "|item" & $i, $idListView)
  Next

  _GUICtrlListView_SetColumnWidth($idListView, 2, $LVSCW_AUTOSIZE_USEHEADER)

  _WinAPI_DwmSetWindowAttributeEx($hGUI, 20, 1)

  ; Apply Acrylic material on newer Windows 11 builds
  If @OSBuild >= 22621 Then
    _WinAPI_DwmSetWindowAttributeEx($hGUI, 38, 3)
    _WinAPI_DwmExtendFrameIntoClientArea($hGUI, _WinAPI_CreateMargins(-1, -1, -1, -1))
  EndIf

  GUISetState()

  Do
  Until GUIGetMsg() = $GUI_EVENT_CLOSE

  _WinAPI_RemoveWindowSubclass($hHeader, DllCallbackGetPtr($hSubClass), 1000)
  _WinAPI_RemoveWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), _WinAPI_MakeLong($hSubClass, $idListView))
  DllCallbackFree($hSubClass)
EndFunc   ;==>Example

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
  Local Static $bHover, $tPoint
  Local $hBrush
  Switch $iMsg
    Case $WM_NOTIFY
      Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
      Switch $tNMHDR.Code
        Case $NM_CUSTOMDRAW
          If _WinAPI_GetClassName($tNMHDR.hWndFrom) = "sysheader32" Then
            Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
            Switch $tCustDraw.dwDrawStage
              Case $CDDS_PREPAINT
                Return $CDRF_NOTIFYITEMDRAW
              Case $CDDS_ITEMPREPAINT
                Local $tRect = DllStructCreate($tagRECT, DllStructGetPtr($tCustDraw, "Left"))
                _WinAPI_SetBkMode($tCustDraw.hDC, 1)
                _WinAPI_SetTextColor($tCustDraw.hDC, 0xFFFFFF)

                If $bHover And _WinAPI_PtInRect($tRect, $tPoint) Then
                  $hBrush = _WinAPI_CreateSolidBrush(_IsPressed($VK_LBUTTON) ? 0x404040 : 0x303030)
                Else
                  $hBrush = _WinAPI_CreateSolidBrush(0x202020)
                EndIf
                _WinAPI_InflateRect($tRect, -1, 0)
                _WinAPI_FillRect($tCustDraw.hDC, $tRect, $hBrush)
                _WinAPI_DeleteObject($hBrush)

                _WinAPI_InflateRect($tRect, -5, -2)
                _WinAPI_DrawText($tCustDraw.hDC, ($mHeader[_WinAPI_HiWord($iID)])[$tCustDraw.dwItemSpec], $tRect, BitOR($DT_LEFT, $DT_NOCLIP))

                Return $CDRF_SKIPDEFAULT
            EndSwitch
          EndIf
        ;Case $HDN_BEGINTRACKW
        ;  $bHover = False
        ;  _WinAPI_RemoveWindowSubclass($pData, DllCallbackGetPtr(_WinAPI_LoWord($iID)), 1000)
        ;Case $HDN_ENDTRACKW
        ;  _WinAPI_SetWindowSubclass($pData, DllCallbackGetPtr(_WinAPI_LoWord($iID)), 1000, $pData)
      EndSwitch

    Case $WM_MOUSEMOVE
      If $pData = $hWnd Then
        $bHover = True
        $tPoint = _WinAPI_GetMousePos(True, $hWnd)
        _WinAPI_InvalidateRect($hWnd, 0, False)
        _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE)
      EndIf
    Case $WM_MOUSELEAVE
      If $pData = $hWnd Then
        $bHover = False
        _WinAPI_InvalidateRect($hWnd, 0, False)
      EndIf

  EndSwitch
  Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>WM_NOTIFY

Func _WinAPI_DwmSetWindowAttributeEx($hWnd, $iAttribute, $iData)
  Switch $iAttribute
    Case 2, 3, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, $DWMWA_USE_IMMERSIVE_DARK_MODE, 33, 34, 35, 36, 37, 38, 39, 40

    Case Else
      Return SetError(1, 0, 0)
  EndSwitch

  Local $aCall = DllCall('dwmapi.dll', 'long', 'DwmSetWindowAttribute', 'hwnd', $hWnd, 'dword', $iAttribute, _
      'dword*', $iData, 'dword', 4)
  If @error Then Return SetError(@error + 10, @extended, 0)
  If $aCall[0] Then Return SetError(10, $aCall[0], 0)

  Return 1
EndFunc   ;==>_WinAPI_DwmSetWindowAttributeEx

Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
  Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, 'lparam', $lParam)[0]
EndFunc

You can have with this approach as many LV in a single GUI as you want, you just need to register each one in the global map...

Edited by Nine
Posted
7 hours ago, Nine said:

You can have with this approach as many LV in a single GUI as you want, you just need to register each one in the global map...

This is brilliant! Thank you. :)

I just wanted to confirm a few things. Since we were able to get rid of cases for $HDN_BEGINTRACKW and $HDN_ENDTRACKW after the fixes:

Does that mean that we can switch anything that is HiWord/LoWord related?

;_WinAPI_SetWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), _WinAPI_MakeLong($hSubClass, $idListView), $hHeader)
  _WinAPI_SetWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), $idListView, $hHeader)

 

;_WinAPI_RemoveWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), _WinAPI_MakeLong($hSubClass, $idListView))
  _WinAPI_RemoveWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), $idListView)

 

;_WinAPI_DrawText($tCustDraw.hDC, ($mHeader[_WinAPI_HiWord($iID)])[$tCustDraw.dwItemSpec], $tRect, BitOR($DT_LEFT, $DT_NOCLIP))
    _WinAPI_DrawText($tCustDraw.hDC, ($mHeader[$iID])[$tCustDraw.dwItemSpec], $tRect, BitOR($DT_LEFT, $DT_NOCLIP))

Are those changes (above) safe changes to make?

 

Posted (edited)
On 4/28/2026 at 10:43 PM, jugador said:

Keep _GUICtrlHeader_GetItemText($tCustDraw.hWndFrom, $tCustDraw.dwItemSpec) as it is, omit $hHeader Subclass lines, and use the DefSubclassProc fix. 
I think this will solve the crash.

infinite recursion loop caused the script in this post to crash. 

Here is how to solve it.

#include <GuiConstants.au3>
#include <GuiHeader.au3>
#include <WinAPI.au3>
#include <Misc.au3>
#include <WinAPITheme.au3>
#include <GuiListView.au3>

AutoItSetOption("MustDeclareVars", 1)

Global $hUser32Dll = DllOpen("user32.dll")

Example()

Func Example()
    Local $hGUI = GUICreate("Header Subclass", 500, 300)
    GUISetBkColor(0x000000)

    Local $idListView = GUICtrlCreateListView("Items List|SubItems1|SubItems2", 10, 10, 480, 280, $LVS_REPORT, BitOR($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT, $LVS_EX_HEADERDRAGDROP))
    Local $hListView = GUICtrlGetHandle($idListView)
    Local $hHeader = _GUICtrlListView_GetHeader($hListView)

    _WinAPI_SetWindowTheme($hListView, "DarkMode_Explorer")
    _WinAPI_SetWindowTheme($hHeader, "DarkMode_ItemsView", "Header")
    GUICtrlSetBkColor($idListView, 0x121212)
    GUICtrlSetColor($idListView, 0xFFFFFF)

    GUICtrlCreateListViewItem("item1|item1|item1", $idListview)
    GUICtrlCreateListViewItem("item2|item2|item2", $idListview)
    GUICtrlCreateListViewItem("item3|item3|item3", $idListview)

     _GUICtrlListView_SetColumnWidth($idListView, 2, $LVSCW_AUTOSIZE_USEHEADER)

    Local $hSubClass = DllCallbackRegister(_Header_Subclass, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    _WinAPI_SetWindowSubclass($hHeader, DllCallbackGetPtr($hSubClass), 1000, $hHeader)

    GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
    GUISetState()

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    _WinAPI_RemoveWindowSubclass($hHeader, DllCallbackGetPtr($hSubClass), 1000)
    DllCallbackFree($hSubClass)
    DllClose($hUser32Dll)
EndFunc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
    Local $hWndFrom = HWnd($tNMHDR.hWndFrom)
    Local $iCode = $tNMHDR.Code

    If _WinAPI_GetClassName($hWndFrom) = "sysheader32" Then
        Switch $iCode
            Case $NM_CUSTOMDRAW
                Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)

                Switch $tCustDraw.dwDrawStage
                    Case $CDDS_PREPAINT
                        Return $CDRF_NOTIFYITEMDRAW

                    Case $CDDS_ITEMPREPAINT
                        Local $hDC = $tCustDraw.hDC
                        Local $tRect = DllStructCreate($tagRECT, DllStructGetPtr($tCustDraw, "Left"))
                        Local $tPoint = _WinAPI_GetMousePos(True, $hWndFrom)

                        Local $iColor = 0x121212
                        If _WinAPI_PtInRect($tRect, $tPoint) Then
                            $iColor = _IsPressed(0x01, $hUser32Dll) ? 0x252525 : 0x383838
                        EndIf

                        Local $hBrush = _WinAPI_CreateSolidBrush($iColor)
                        _WinAPI_FillRect($hDC, $tRect, $hBrush)
                        _WinAPI_DeleteObject($hBrush)

                        _WinAPI_SetBkMode($hDC, 1)
                        _WinAPI_SetTextColor($hDC, 0xFFFFFF)

                        _WinAPI_InflateRect($tRect, -5, -2)
                        _WinAPI_DrawText($tCustDraw.hDC, _GUICtrlHeader_GetItemText($tCustDraw.hWndFrom, $tCustDraw.dwItemSpec), $tRect, BitOR($DT_LEFT, $DT_NOCLIP))
                        Return $CDRF_SKIPDEFAULT
                EndSwitch

        EndSwitch
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc

Func _Header_Subclass($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    #forceref $iID, $pData
    Switch $iMsg
        Case $WM_MOUSEMOVE
             _WinAPI_TrackMouseEvent($hWnd, $TME_LEAVE)
            _WinAPI_InvalidateRect($hWnd)

        Case $WM_MOUSELEAVE
            _WinAPI_InvalidateRect($hWnd)
    EndSwitch

    Return __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc

Func __WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
    Return DllCall('comctl32.dll', 'lresult', 'DefSubclassProc', 'hwnd', $hWnd, 'uint', $iMsg, 'wparam', $wParam, _
            'lparam', $lParam)[0]
EndFunc   ;==>_WinAPI_DefSubclassProc

 

Edited by jugador
Posted
3 hours ago, jugador said:

infinite recursion loop cause of crash:

I don't completely understand what the root of the infinite recursion loop is.

But from this example, the $CDDS_ITEMPREPAINT case is never entered and no coloring is done on the header and therefore is never reaching the _WinAPI_DrawText function that contains the _GUICtrlHeader_GetItemText which was causing crashes. I can only see the initial $CDDS_PREPAINT case being used here.

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