Modify ↓

#4091 closed Bug (Wont Fix)

Header painting dysfunctional when subclassed in ListView

Reported by: Nine Owned by:
Milestone: Component: AutoIt
Version: 3.3.18.0 Severity: None
Keywords: Cc:

Description (last modified by mLipok)

The following line is not executed properly when run in x64 while it works perfectly in x86.

If $tItem.Code <> $NM_CUSTOMDRAW Then Return $GUI_RUNDEFMSG

Here the full example. Toggle the useX64 to see difference.
This code was prepared by mlipok.

#AutoIt3Wrapper_UseX64=Y

; From Nine
#include <WindowsConstants.au3>
#include <ColorConstants.au3>
#include <GUIConstants.au3>
#include <GuiListView.au3>
#include <WinAPI.au3>

Opt("MustDeclareVars", True)

Example()

Func Example()
    GUICreate("Example")
    Local $idListview = GUICtrlCreateListView("", 10, 10, 350, 200, -1, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_DOUBLEBUFFER))
    Local $idButton = GUICtrlCreateButton("Test", 10, 220, 70, 20)
    GUICtrlSendMsg($idListview, $LVM_GETHEADER, 0, 0)

    GUISetState(@SW_SHOW)
    GUIRegisterMsg($WM_NOTIFY, WM_NOTIFY)

    _GUICtrlListView_AddColumn($idListview, "Column 1", 100)
    _GUICtrlListView_AddItem($idListview, "Row 1: Col 1", 0)

    While True
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
            Case $idButton
                ConsoleWrite("Test button clicked" & @CRLF)
        EndSwitch
    WEnd
EndFunc   ;==>Example

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam
    Local Static $iCounter = 0

    $iCounter += 1
    Local $tItem = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
    ConsoleWrite("+ COUNTER = " & $iCounter & " >>> " & _WinAPI_GetClassName($tItem.hWndFrom) & @CRLF)

    ; I thought that for testing AutoIt x64 we do not need anything more but it is not true
    ; when the next line is commented, the COUNTER ends on 5 in x32  ..... but ends on 4 in x64
;~  Return $GUI_RUNDEFMSG

    #QUESTION = why COUNTER ends with 4 in x64 instead of doing the 5 processing as in x32 ?
    #NOTE =  I see that in x32 there is additionall step in the middle of processing ; + COUNTER = 3 >>> SysHeader32

    If _WinAPI_GetClassName($tItem.hWndFrom) <> "SysHeader32" Then Return $GUI_RUNDEFMSG ; we are focusing only on "SysHeader32"

    If $tItem.Code <> $NM_CUSTOMDRAW Then Return $GUI_RUNDEFMSG

    Local $tRect = DllStructCreate($tagRECT, DllStructGetPtr($tItem, "left"))
    Local $hBrush, $hPen

    If _WinAPI_GetClassName($tItem.hWndFrom) = "SysHeader32" Then
    ConsoleWrite("! 1st CheckPoint = " & @ScriptLineNumber & @CRLF)
        If $tItem.dwDrawStage = $CDDS_PREPAINT Then Return $CDRF_NOTIFYITEMDRAW
    ConsoleWrite("! 2nd CheckPoint = " & @ScriptLineNumber & @CRLF)

        If Not $tItem.dwItemSpec Then $tRect.left += 5
        $tRect.bottom -= 1
        $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0xA0A0A0)
        _WinAPI_SelectObject($tItem.hDC, $hPen)
        $hBrush = _WinAPI_CreateSolidBrush(0xFFCCDD)
        _WinAPI_SelectObject($tItem.hDC, $hBrush)
        _WinAPI_Rectangle($tItem.hDC, $tRect)
        _WinAPI_DeleteObject($hPen)
        _WinAPI_DeleteObject($hBrush)

        $tRect.Left += 5
        $tRect.Top += 3
        _WinAPI_SetTextColor($tItem.hDC, _WinAPI_SwitchColor($COLOR_BLACK))
        _WinAPI_SetBkMode($tItem.hDC, $TRANSPARENT)
        _WinAPI_DrawText($tItem.hDC, _GUICtrlHeader_GetItemText($tItem.hWndFrom, $tItem.dwItemSpec), $tRect, $DT_LEFT)

        Return $CDRF_SKIPDEFAULT
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

Attachments (0)

Change History (7)

comment:1 by mLipok, on Apr 26, 2026 at 12:08:17 PM

My note
I think that WM_NOTIFY() function registered by GUIRegisterMsg() in x64 is not allways called.

comment:2 by MattyD, on Apr 26, 2026 at 4:51:13 PM

Guessing this is probably a windows thing as returning _WinAPI_DefWindowProcW instead of $GUI_RUNDEFMSG doesn't change the behavior.

The below works in both x64 & x86 when we handle the listview's WM_NOTIFY messages rather than the GUI's.

#AutoIt3Wrapper_UseX64=Y

#include <WindowsConstants.au3>
#include <GUIConstants.au3>
#include <GuiListView.au3>
#include <WinAPI.au3>

Global Const $tagNMCUSTOMDRAWINFO = "struct;" & $tagNMHDR & ";dword DrawStage;handle hdc;" & _
                $tagRECT & ";dword_ptr ItemSpec;uint ItemState;lparam lItemParam;endstruct"

Global $hSubclassProc, $pSubclassProc

Example()

Func Example()
        GUICreate("Example")

        Local $idListview = GUICtrlCreateListView("", 10, 10, 350, 200, -1, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_DOUBLEBUFFER))
        $hSubclassProc = DllCallbackRegister("_SubclassProc", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
        $pSubclassProc = DllCallbackGetPtr($hSubclassProc)
        _WinAPI_SetWindowSubclass(GUICtrlGetHandle($idListview), $pSubclassProc, 0)

        GUISetState()

        _GUICtrlListView_AddColumn($idListview, "Column 1", 100)
        _GUICtrlListView_AddItem($idListview, "Row 1: Col 1", 0)

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


        GUIDelete()
        DllCallbackFree($hSubclassProc)
EndFunc   ;==>Example

Func _SubclassProc($hWnd, $iMsg, $wParam, $lParam, $iSubclassID, $pData)
        #forceref $hWnd, $iMsg, $wParam, $lParam, $iSubclassID, $pData
        Local Static $hBrush, $hPen

        Switch $iMsg
                Case $WM_DESTROY
                        _WinAPI_DeleteObject($hPen)
                        _WinAPI_DeleteObject($hBrush)
                        _WinAPI_RemoveWindowSubclass($hWnd, $pSubclassProc, $iSubclassID)
                        $hPen = 0
                        $hBrush = 0

                Case $WM_NOTIFY

                        Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)

                        If _WinAPI_GetClassName($tNMHDR.hWndFrom) <> "SysHeader32" Then Return $GUI_RUNDEFMSG

                        If $tNMHDR.Code = $NM_CUSTOMDRAW Then
                                Local $tNMCD = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)

                                Switch $tNMCD.DrawStage
                                        Case $CDDS_PREPAINT
                                                ;Returning $CDRF_NOTIFYITEMDRAW here should trigger the $CDDS_ITEMPREPAINT draw stage.
                                                ; When this code is in the GUI's WM_NOTIFY msg handler rather than the control's, 
                                                ; we don't hit the CDDS_ITEMPREPAINT stage. This is only in x64 though, the trigger 
                                                ; works fine for x86.
                                                Return $CDRF_NOTIFYITEMDRAW

                                        Case $CDDS_ITEMPREPAINT
                                                If Not $hBrush Then $hBrush = _WinAPI_CreateSolidBrush(0xFFCCDD)
                                                If Not $hPen Then $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0xA0A0A0)
                                                _WinAPI_SelectObject($tNMCD.hDC, $hPen)
                                                _WinAPI_SelectObject($tNMCD.hDC, $hBrush)

                                                Local $tRect = DllStructCreate($tagRECT, DllStructGetPtr($tNMCD, "Left"))
                                                If Not $tNMCD.dwItemSpec Then $tRect.left += 5
                                                $tRect.bottom -= 1

                                                _WinAPI_Rectangle($tNMCD.hDC, $tRect)
                                                _WinAPI_SetTextColor($tNMCD.hDC, 0)
                                                _WinAPI_SetBkMode($tNMCD.hDC, $TRANSPARENT)
                                                $tRect.Left += 5
                                                $tRect.Top += 3
                                                _WinAPI_DrawText($tNMCD.hDC, _GUICtrlHeader_GetItemText($tNMCD.hWndFrom, $tNMCD.dwItemSpec), $tRect, $DT_LEFT)

                                                Return $CDRF_SKIPDEFAULT

                                EndSwitch
                        EndIf
        EndSwitch

        Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>_SubclassProc

comment:3 by Nine, on Apr 26, 2026 at 7:41:45 PM

@mattyd - I know about this workaround. In the other track I made, I was also using the listview proc instead of the GUI. But I still think there is a bug here that we should address.

comment:4 by mLipok, on Apr 27, 2026 at 11:09:47 AM

Description: modified (diff)

comment:5 by MattyD, on Oct 21, 2026 at 10:15:19 AM

Hi guys,
I've done a bit more digging on this one, and FWIW this is my understanding of what's happening.

The LV header's parent window is the list view. So for custom drawing - the listview's proc is where the WM_NOTIFY/NMCUSTOMDRAWINFO initially hits.

If the msg is unhandled at this level (or DefSubclassProc is called) then it is fired synchronously up to the parent. This is why we can see the header's customdraw messages via GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY") and respond.

The breakdown occurs when returning a value back from the parent window to the control. In x86, we can return CDRF_NOTIFYITEMDRAW from WM_NOTIFY on the parent level - and this value is returned intact by the listview proc. In x64 however, the return value seems to be converted to a bool somewhere along the way, so its numeric value is lost.

#AutoIt3Wrapper_UseX64=Y

#include <WindowsConstants.au3>
#include <ColorConstants.au3>
#include <GUIConstants.au3>
#include <GuiListView.au3>
#include <WinAPI.au3>

Global Const $tagNMCUSTOMDRAWINFO = "struct;" & $tagNMHDR & ";dword DrawStage;handle hdc;" & _
                $tagRECT & ";dword_ptr ItemSpec;uint ItemState;lparam lItemParam;endstruct"

Global $hSubclassProc

Example()

Func Example()
        Local $hGUI = GUICreate("Example", 370, 220)
        GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

        Local $hListview = _GUICtrlListView_Create($hGUI, "", 10, 10, 350, 200, $LVS_REPORT)
        $hSubclassProc = DllCallbackRegister("_SubclassProc", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
        _WinAPI_SetWindowSubclass($hListview, DllCallbackGetPtr($hSubclassProc), 0)

        _GUICtrlListView_AddColumn($hListview, "Column 1", 100)
        _GUICtrlListView_AddItem($hListview, "Row 1: Col 1", 0)
        GUISetState()

        Do
        Until GUIGetMsg() = $GUI_EVENT_CLOSE

        GUIDelete()
        DllCallbackFree($hSubclassProc)

EndFunc   ;==>Example

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

        Local Static $hBrush, $hPen
        Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)

        If _WinAPI_GetClassName($tNMHDR.hWndFrom) <> "SysHeader32" Then Return $GUI_RUNDEFMSG

        If $tNMHDR.Code = $NM_CUSTOMDRAW Then
                Local $tNMCD = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)

                Switch $tNMCD.DrawStage
                        Case $CDDS_PREPAINT
                                Local $iRet = $CDRF_NOTIFYITEMDRAW
                                ConsoleWrite("Wnd Proc Ret: " & $iRet & @CRLF)
                                ;$CDRF_NOTIFYITEMDRAW is passed to listview proc when in x86, but for x64 it comes through as 1.
                                Return $iRet

                        Case $CDDS_ITEMPREPAINT
                                If Not $hBrush Then $hBrush = _WinAPI_CreateSolidBrush(0xFFCCDD)
                                If Not $hPen Then $hPen = _WinAPI_CreatePen($PS_SOLID, 1, 0xA0A0A0)
                                _WinAPI_SelectObject($tNMCD.hDC, $hPen)
                                _WinAPI_SelectObject($tNMCD.hDC, $hBrush)

                                Local $tRect = DllStructCreate($tagRECT, DllStructGetPtr($tNMCD, "Left"))
                                If Not $tNMCD.dwItemSpec Then $tRect.left += 5
                                $tRect.bottom -= 1

                                _WinAPI_Rectangle($tNMCD.hDC, $tRect)
                                _WinAPI_SetTextColor($tNMCD.hDC, 0)
                                _WinAPI_SetBkMode($tNMCD.hDC, $TRANSPARENT)
                                $tRect.Left += 5
                                $tRect.Top += 3
                                _WinAPI_DrawText($tNMCD.hDC, _GUICtrlHeader_GetItemText($tNMCD.hWndFrom, $tNMCD.dwItemSpec), $tRect, $DT_LEFT)

                                ;$CDRF_SKIPDEFAULT is passed to listview proc when in x86, but for x64 it comes through as 1.
                                Return $CDRF_SKIPDEFAULT

                EndSwitch
        EndIf

        Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

Func _SubclassProc($hWnd, $iMsg, $wParam, $lParam, $iSubclassID, $pData)
        #forceref $hWnd, $iMsg, $wParam, $lParam, $iSubclassID, $pData

        Local $iRet

        Switch $iMsg
                Case $WM_DESTROY
                        _WinAPI_RemoveWindowSubclass($hWnd, DllCallbackGetPtr($hSubclassProc), $iSubclassID)

                Case $WM_NOTIFY
                        Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)

                        If $tNMHDR.Code = $NM_CUSTOMDRAW Then
                                Local $tNMCD = DllStructCreate($tagNMCUSTOMDRAWINFO, $lParam)

                                Switch $tNMCD.DrawStage
                                        Case $CDDS_PREPAINT

                                                $iRet = _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
                                                ConsoleWrite(" LV Proc Ret: " & $iRet & @CRLF)
                                                Return $iRet
                                                ;$iRet needs to be $CDRF_NOTIFYITEMDRAW to custom draw the item.

;~                                              _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
;~                                              Return $CDRF_NOTIFYITEMDRAW

                                        Case $CDDS_ITEMPREPAINT
                                                ;Same problem here, the ret value needs to be $CDRF_SKIPDEFAULT for our drawing to work.
                                                Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)

;~                                              _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
;~                                              Return $CDRF_SKIPDEFAULT

                                EndSwitch
                        EndIf
        EndSwitch

        Return _WinAPI_DefSubclassProc($hWnd, $iMsg, $wParam, $lParam)
EndFunc   ;==>_SubclassProc

comment:6 by Jpm, on Oct 23, 2026 at 8:45:49 AM

I finally understand that GUIRegister cannot be use with subclassoroc as it is working befor subclass in some case MattyD show the right way to work

comment:7 by Jpm, on Oct 23, 2026 at 8:46:25 AM

Resolution: → Wont Fix
Status: new → closed

Modify Ticket

Action
as closed The ticket will remain with no owner.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.