Jump to content

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


Go to solution Solved by Nine,

Recommended Posts

Posted
1 hour ago, jugador said:

_GUICtrlHeader_GetItemText is causing problems because of SendMessage.
To fix this, only read the ListView header if the handle ($hFrom) changes.

This is brilliant! :)

Thank you. It solved the crash that I did not think was possible to solve.

Also, your code makes it more efficient which is great.

Posted
3 hours ago, WildByDesign said:

It solved the crash that I did not think was possible to solve.

..the crash will occur again if you have more _GUICtrlHeader_GetItemText() calls. This code avoids calling it unnecessarily.
Is a clever approach given the circumstances but it does not fix the problem.
Am clarifying/describing the solution for you not to believe that the issue is now corrected.

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Posted
37 minutes ago, argumentum said:

..the crash will occur again if you have more _GUICtrlHeader_GetItemText() calls. This code avoids calling it unnecessarily.
Is a clever approach given the circumstances but it does not fix the problem

You are 100% right. SendMessage is a problem on x64 even with that recent fix from Nine.

You’re right because if I enabled it for a GUI with multiple ListViews, the $hFrom value would change when going back and forth from different ListViews. That would trigger more GetItemText calls and cause SendMessage to crash.

So I can’t really consider adding it (as option) to GUIDarkTheme UDF yet until all issues with that SendMessage are resolved on x64.

Posted

One possible approach is to register all list views into a single global map variable before you enable the subclass.  That way you would only read header titles once for each list view (outside the subclass proc).  Again it is only a patch until a solution is found to this bug.

ps. as much as I dislike global variables, they can be handy at times...

Posted
2 hours ago, Nine said:

One possible approach is to register all list views into a single global map variable before you enable the subclass.  That way you would only read header titles once for each list view (outside the subclass proc).

I think that this is the best idea, for sure.

I don’t know DllStruct at all unfortunately. But I am familiar with maps and arrays.

Do you know if maps would be faster in this situation?

Posted (edited)

I must admit, I spent an hour last night messing with arrays and another hour this morning messing with maps. I can't seem to figure out the best way to organize this in such a way that would be efficient for the DrawText line to process. Please keep in mind that this would potentially have to store header text for multiple ListViews. So I have to create the array or map from the perspective of holding header text for multiple ListViews.

18 hours ago, jugador said:

if want to avoid Global then store the ListView header string in a DllStruct and pass it as $pData during subclassing.
no problem if there multiple ListView

From the perspective of having multiple ListViews, this DllStruct passing through the subclass $pData sounds like it might be the best option. The only problem is that I know nothing about creating DllStruct or processing the DllStruct data on the other side.

$pData would need to have the Header handle and header text string.

Is it possible to have the DllStruct store both in $pData?

Edited by WildByDesign
Posted
3 hours ago, WildByDesign said:

... The only problem is that I know nothing about creating DllStruct ...

... time to learn. Is not that difficult if you try it.
 

Spoiler
 
dllsdtruct in autoit. how do i make an example of adding a parameter to an existing sctuct 🤔
 
In AutoIt, you cannot dynamically "append" a parameter to a DllStruct once it has been created because the memory for that structure is already allocated. [1, 2, 3]
To achieve this, you must define a new structure string that includes the original parameters plus your new one, and then create a new struct using DllStructCreate(). [2, 3, 4]

Example: Adding a Parameter to an Existing Struct

This example shows how to take the definition of an existing structure, add a new variable to it, and "re-create" it. [3]
; 1. Define and create the original struct
Local $sTagOriginal = "int var1;float var2"
Local $tOriginal = DllStructCreate($sTagOriginal)
DllStructSetData($tOriginal, "var1", 100)
DllStructSetData($tOriginal, "var2", 12.5)

; 2. Define a new tag by appending a parameter to the original string
Local $sTagNew = $sTagOriginal & ";char var3[128]" ; Adding a string parameter

; 3. Create the new struct
Local $tNew = DllStructCreate($sTagNew)

; 4. (Optional) Copy data from the old struct to the new one
DllStructSetData($tNew, "var1", DllStructGetData($tOriginal, "var1"))
DllStructSetData($tNew, "var2", DllStructGetData($tOriginal, "var2"))

; 5. Set the new parameter
DllStructSetData($tNew, "var3", "Added Parameter!")

; Display results
MsgBox(64, "Struct Example", _
    "Var1: " & DllStructGetData($tNew, "var1") & @CRLF & _
    "Var2: " & DllStructGetData($tNew, "var2") & @CRLF & _
    "Var3: " & DllStructGetData($tNew, "var3"))

Key Functions Used

 
If you are working with pointers, you can also use DllStructCreate($sTagNew, DllStructGetPtr($tOriginal)) to create a new struct definition that maps over the same memory address, though this only works safely if the new structure doesn't exceed the memory originally allocated. [3, 9, 10]
If you'd like, I can help you:
 
  • Convert a C++ struct into an AutoIt tag.
  • Work with nested structs (structs inside structs).
  • Use these structs in a DllCall. [3, 11]

..so, go at it ;) 

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Posted (edited)

in your case, lets say you have $tag="struct;int;int;bool;double;char[128];endstruct",
you just add your thing: $tag="struct;int;int;bool;double;char[128];char var3[128];endstruct"
Because you may think that the prior post with the AI example, is how it is, but is just an example.
Once you get the hang of it, is a no brainer :)

Edited by argumentum

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Posted

relooked at the code, but I don’t understand what these line is doing:

_WinAPI_SetWindowSubclass($hHeader, DllCallbackGetPtr($hSubClass), 1000, $hHeader)

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.

 

regarding the $pData query:

Local $tStruct1 = DllStructCreate("ptr;wchar[256]")
DllStructSetData($tStruct1, 1, $hHeader)
DllStructSetData($tStruct1, 2, "Items List|SubItems1|SubItems2")

Local $hSubClass = DllCallbackRegister(WM_NOTIFY, "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
_WinAPI_SetWindowSubclass($hListView, DllCallbackGetPtr($hSubClass), $hSubClass, DllStructGetPtr($tStruct1))

 

Case $CDDS_ITEMPREPAINT
    Local $tStruct1 = DllStructCreate("ptr;wchar[256]", $pData)
    Local $sString = DllStructGetData($tStruct1, 2)
    Local $aHeaderList = StringRegExp($sString, "[^|]+", 3)
    ;....
    _WinAPI_DrawText($tCustDraw.hDC, $aHeaderList[$tCustDraw.dwItemSpec], $tRect, BitOR($DT_LEFT, $DT_NOCLIP))


 

Posted
4 minutes ago, jugador said:

relooked at the code, but I don’t understand what these line is doing:

_WinAPI_SetWindowSubclass($hHeader, DllCallbackGetPtr($hSubClass), 1000, $hHeader)

This line sends to Header handle to the subclass process which compares the Header handle to the following:

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

It is the $bHover boolean there that @Nine used to get the hover color painting of the header items.

Posted
2 hours ago, argumentum said:

in your case, lets say you have $tag="struct;int;int;bool;double;char[128];endstruct",
you just add your thing: $tag="struct;int;int;bool;double;char[128];char var3[128];endstruct"
Because you may think that the prior post with the AI example, is how it is, but is just an example.
Once you get the hang of it, is a no brainer :)

Thank you so much. It looks like I've got some learning to do. :)

It looks like an incredibly powerful functionality that, if I can understand it, I can put it to good usage. I like how it seems flexible. I will spend some time tonight reading more about it. I appreciate the occasional kick in the butt to get started with something new. I quite often need it. :thumbsup:

Posted (edited)

I made a mistake initially and therefore edited my comment. After catching my own mistake, everything seems to be working perfectly. I will put it through some more time and more testing and report back on it later. :)

Edited by WildByDesign
Posted (edited)

It's been a few hours now and it refuses to crash. :)

Here is my current testing script with the fixes from @jugador

#AutoIt3Wrapper_UseX64=n

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

; From Nine
#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 ", 500, 300)
    GUISetBkColor(0x000000)

    Local $idListView = GUICtrlCreateListView("Items List|SubItems1|SubItems2", 10, 10, 480, 280, -1, BitOR($LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT, $LVS_EX_HEADERDRAGDROP))
    Local $hListView = GUICtrlGetHandle($idListView)
    ;Local $hHeader = GUICtrlSendMsg($idListView, $LVM_GETHEADER, 0, 0)
    Local $hHeader = _GUICtrlListView_GetHeader($idListview)
    _WinAPI_SetWindowTheme($hListView, "DarkMode_Explorer")
    _WinAPI_SetWindowTheme($hHeader, "DarkMode_ItemsView", "Header")
    GUICtrlSetBkColor($idListView, 0x000000)
    GUICtrlSetColor($idListView, 0xFFFFFF)

    ; get header info
    Local $sString
    Local $sCount = _GUICtrlHeader_GetItemCount($hHeader)

    For $i = 0 To $sCount - 1
        $sString &= _GUICtrlHeader_GetItemText($hHeader, $i) & '|'
    Next


    Local $tStruct1 = DllStructCreate("struct;ptr;wchar[256];endstruct")
    DllStructSetData($tStruct1, 1, $hHeader)
    DllStructSetData($tStruct1, 2, $sString)

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

    For $i = 1 To 10
        GUICtrlCreateListViewItem("item1|item2|item3", $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

    ; remove focus rectangle from listview items
    GUICtrlSendMsg($idListView, $WM_CHANGEUISTATE, 65537, 0)

    GUISetState()

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

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

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam, $iID, $pData)
    Local Static $bHover, $tPoint
    Local $hBrush
    Switch $iMsg
        Case $WM_NOTIFY
        Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
        Local $hFrom = $tCustDraw.hWndFrom
        Switch $tCustDraw.Code
            Case $NM_CUSTOMDRAW
                If _WinAPI_GetClassName($hFrom) = "sysheader32" Then
                    Local $tStruct1 = DllStructCreate("struct;ptr;wchar[256];endstruct", $pData)
                    Local $sString = DllStructGetData($tStruct1, 2)
                    Local $aHeaderList = StringRegExp($sString, "[^|]+", 3)
                    Local $hHeader = DllStructGetData($tStruct1, 1)

                    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, $hUser32Dll) ? 0x202020 : 0x303030)
                            Else
                                $hBrush = _WinAPI_CreateSolidBrush(0x121212)
                            EndIf
                            _WinAPI_InflateRect($tRect, -1, 0)
                            _WinAPI_FillRect($tCustDraw.hDC, $tRect, $hBrush)
                            _WinAPI_DeleteObject($hBrush)

                            _WinAPI_InflateRect($tRect, -5, -3)
                            Local $iFlags = BitOR($DT_LEFT, $DT_NOCLIP, $DT_VCENTER)
                            _WinAPI_DrawText($tCustDraw.hDC, $aHeaderList[$tCustDraw.dwItemSpec], $tRect, $iFlags)

                            Return BitOR($CDRF_SKIPDEFAULT, $CDRF_NEWFONT)
                    EndSwitch
            EndIf
            Case $HDN_BEGINTRACKW
                $bHover = False
                _WinAPI_RemoveWindowSubclass($pData, DllCallbackGetPtr($iID), 1000)
            Case $HDN_ENDTRACKW
                _WinAPI_SetWindowSubclass($pData, DllCallbackGetPtr($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   ;==>_WinAPI_DefSubclassProc

 

Edited by WildByDesign
Posted
If a collection of datatypes is defined as in a "struct{}" in C declaration, the "STRUCT; ...; ENDSTRUCT;" must be used.
 This needs to be done to respect alignment inside the entire structure creation. No need if all datatypes are in the defined structure as an implicit structure alignment is done.
; Assign a Local constant variable the definition of a structure (read carefully the DllStructCreate remarks).
 Local Const $tagSTRUCT1 = "struct;int var1;byte var2;uint var3;char var4[128];endstruct"

..this is from the help file.
Silly as it may seem, I think that adding "struct;" and ";endstruct" everywhere is a good mania to have.
Is it needed ?,  meh.
Does it hurt ?, nope. :) 

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting  image.gif.922e3a93535f431de08b31ee669cc446.gif
autoit_scripter_blue_userbar.png

Posted
34 minutes ago, argumentum said:

..this is from the help file.
Silly as it may seem, I think that adding "struct;" and ";endstruct" everywhere is a good mania to have.

Thanks. I just updated the code in the post above with this change and nothing exploded, so we are good to go. 😄

GUIDarkTheme UDF 2.0 is so very close to being ready. It's going to be a big step up in theme design. But there is still lots to do after version 2.

Posted

@Nine I just wanted to follow up on this briefly. After the subclassing bug was fixed regarding the _WinAPI_DefSubclassProc function and the bug regarding the _GUICtrlHeader_GetItemText function (root issue there being the SendMsg), I was able to remove the following from the header subclass:

Case $HDN_BEGINTRACKW
    $bHover = False
    _WinAPI_RemoveWindowSubclass($pData, DllCallbackGetPtr($iID), 1000)
Case $HDN_ENDTRACKW
    _WinAPI_SetWindowSubclass($pData, DllCallbackGetPtr($iID), 1000, $pData)

If I remember correctly, your purpose for adding that was to avoid a crash. I remember removing that during testing initially and it would crash anytime you try to resize the headers. So I assume crash avoidance was their purpose.

Anyway, I just wanted to follow up and mention that they are no longer needed, as long as the fixes are in place.

Although to be specific, the whole _GUICtrlHeader_GetItemText and SendMsg thing is still problematic and I had to work around it. 

Posted
4 hours ago, WildByDesign said:

that they are no longer needed, as long as the fixes are in place

You are right.  I'm still stunned that a simple error check cause the whole GUI to crash.  But I have seen some strange things before in ListView.  FWIW, I tested with my approach of using a global map, and it also works correctly without handling those messages.

Posted
10 minutes ago, Nine said:

FWIW, I tested with my approach of using a global map, and it also works correctly without handling those messages.

If you have this available, would you be willing to share the code?

I ended up going with a global array but only because my global map attempts failed. I could not figure out how to properly do the map.

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