#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 )
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:2 by , 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 , 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 , on Apr 27, 2026 at 11:09:47 AM
| Description: | modified (diff) |
|---|
comment:5 by , 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 , 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 , on Oct 23, 2026 at 8:46:25 AM
| Resolution: | → Wont Fix |
|---|---|
| Status: | new → closed |

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