colored listview headers Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
Bugz000
Posts: 93
Joined: 30 Sep 2013, 10:01

colored listview headers

24 Feb 2021, 13:47

i am trying to get colored listview headers the "proper" way, by intercepting the draw message and sending customdraw to plug in my alterations and move forward from there

i have some resources:
Spoiler


as you can see - someone else has done 90% of the work in the ways of getting text color to work - so i think it may be an address issue re; structs

Code: Select all

HDC := NumGet(L + 0, OHDC, "Ptr")
i think perhaps the HDC of the TEXT is different to the background rectangle
so the HDC must be different but i can't work out where to find this information
any assistance would help a great deal, thankyou
Image
||-------[-HP-ML350E-G8-]-------||-[-32-core-xeon-]-||--[-48gb-ECC-]--||
||----[-Dell-Poweredge-r610-]---||-[-16-core-xeon-]-||--[-16gb-ECC-]--||
||-[-Lenovo-ThinkPad-x201-tab-]-||---[-4-core-i7-]--||-[-8gb-nonECC-]-||
||---------------------------[-shack--img-]---------------------------||
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers  Topic is solved

26 Feb 2021, 06:52

Hi, after some testing it seems that CDDS_ITEMPREPAINT notifications are not received by the monitor function. I don't know why, maybe because the header control is'nt an AHK GUI control?
User avatar
Bugz000
Posts: 93
Joined: 30 Sep 2013, 10:01

Re: colored listview headers

01 Mar 2021, 11:27

just me wrote:
26 Feb 2021, 06:52
Hi, after some testing it seems that CDDS_ITEMPREPAINT notifications are not received by the monitor function. I don't know why, maybe because the header control is'nt an AHK GUI control?
hey you're the one who did the original code!
it does appear to be either impossible, or incredibly difficult to achieve - i however resolved that a listview is not the optimal control to display the info i wanted, and a Treeview was much more suitable, so i guess i dodged the issue entirely that way - though i have seen many who wish for this functionality too, and i'm sure to need it in future if you wish to continue and get it working (somehow) - but for now, i shall mark as solved :)

thankyou <3
Image
||-------[-HP-ML350E-G8-]-------||-[-32-core-xeon-]-||--[-48gb-ECC-]--||
||----[-Dell-Poweredge-r610-]---||-[-16-core-xeon-]-||--[-16gb-ECC-]--||
||-[-Lenovo-ThinkPad-x201-tab-]-||---[-4-core-i7-]--||-[-8gb-nonECC-]-||
||---------------------------[-shack--img-]---------------------------||
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers

02 Mar 2021, 11:39

Hi,

found some AutoIt forum thread describing the same problem. Subclassing the ListView as shown in the AutoIt forum seems to be working on 64 and 32-bit for AHK too:

Code: Select all

#NoEnv
SetBatchLines, -1
WM_NOTIFY := 0x004E
HDS_FLAT  := 0x0200
; Create a GUI with a ListView
Gui, Margin, 20, 20
Gui, Add, ListView, w600 r20 hwndHLV Grid C0000FF NoSort, Message         |State           |Item            |TickCount
LV_ModifyCol(0, "AutoHdr")
; Get the HWND of the ListView's Header control
SendMessage, LVM_GETHEADER := 0x101F, 0, 0, , ahk_id %HLV%
HHEADER := ErrorLevel + 0
; ----------------------------------------------------------------------------------------------------------------------
; DllCall("UxTheme.dll\SetWindowTheme", "Ptr", HHEADER, "Ptr", 0, "Str", "")     ; Win XP
; Control, Style, +0x0200, , ahk_id %HHEADER%                                    ; Win XP (HDS_FLAT = 0x0200)
; ----------------------------------------------------------------------------------------------------------------------
; Create an object containing the color for each Header control
HeaderColor := {}
HeaderColor[HHEADER] := {Color: 0xFF0000} ; Note: It's BGR instead of RGB!
SubClassControl(HLV, "HeaderCustomDraw")
Gui, Show, , Color LV Header
; Register message handler for WM_NOTIFY (-> NM_CUSTOMDRAW)
; OnMessage(WM_NOTIFY, "On_NM_CUSTOMDRAW")
; Redraw the Header to get the notfications for all Header items
WinSet, Redraw, , ahk_id %HHEADER%
Return
GuiClose:
GuiEscape:
ExitApp
; ======================================================================================================================
HeaderCustomDraw(H, M, W, L, IdSubclass, RefData) {
   Static NM_CUSTOMDRAW          := -12
   Static CDRF_DODEFAULT         := 0x00000000
   Static CDRF_NEWFONT           := 0x00000002
   Static CDRF_NOTIFYITEMDRAW    := 0x00000020
   Static CDRF_NOTIFYSUBITEMDRAW := 0x00000020
   Static CDDS_PREPAINT          := 0x00000001
   Static CDDS_ITEMPREPAINT      := 0x00010001
   Static CDDS_SUBITEM           := 0x00020000
   Static OHWND      := 0
   Static OMsg       := (2 * A_PtrSize)
   Static ODrawStage := OMsg + 4 + (A_PtrSize - 4)
   Static OHDC       := ODrawStage + 4 + (A_PtrSize - 4)
   Static OItemSpec  := OHDC + 16 + A_PtrSize
   Global HeaderColor
   Critical 1000
   Switch M {
      Case 0x004E:   ; WM_NOTIFY  --------------------------------------------------------------------------------------
         ; Get sending control's HWND
         HWND := NumGet(L + 0, OHWND, "UPtr")
         ; If HeaderColor contains appropriate key ...
         If (HeaderColor.HasKey(HWND)) {
            ; If the message is NM_CUSTOMDRAW ...
            If (NumGet(L + 0, OMsg, "Int") = NM_CUSTOMDRAW) {
               ; ... do the job!
               DrawStage := NumGet(L + 0, ODrawStage, "UInt")
               ; -------------------------------------------------------------------------------------------------------------
               Item := NumGet(L + 0, OItemSpec, "Ptr")                                       ; for testing
               LV_Modify(LV_Add("", NM_CUSTOMDRAW, DrawStage, Item, A_TickCount), "Vis")     ; for testing
               ; -------------------------------------------------------------------------------------------------------------
               If (DrawStage = CDDS_ITEMPREPAINT) {
                  HDC := NumGet(L + 0, OHDC, "Ptr")
                  DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", HeaderColor[HWND].Color)
                  Return CDRF_NEWFONT
               }
               If (DrawStage = CDDS_PREPAINT) {
                  Return CDRF_NOTIFYITEMDRAW
               }
               Return CDRF_DODEFAULT
            }
         }
      Case 0x0002:   ; WM_DESTROY --------------------------------------------------------------------------------------
         SubclassControl(H, "") ; remove the subclass procedure
   }

   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", H, "UInt", M, "Ptr", W, "Ptr", L, "Ptr")
}
; ==================================================================================================================================
; SubclassControl    Installs, updates, or removes the subclass callback for the specified control.
; Parameters:        HCTL     -  Handle to the control.
;                    FuncName -  Name of the callback function as string.
;                                If you pass an empty string, the subclass callback will be removed.
;                    Data     -  Optional integer value passed as dwRefData to the callback function.
; Return value:      Non-zero if the subclass callback was successfully installed, updated, or removed; otherwise, False.
; Remarks:           The callback function must have exactly six parameters, see
;                    SUBCLASSPROC -> msdn.microsoft.com/en-us/library/bb776774(v=vs.85).aspx
; MSDN:              Subclassing Controls -> msdn.microsoft.com/en-us/library/bb773183(v=vs.85).aspx
; ==================================================================================================================================
SubclassControl(HCTL, FuncName, Data := 0) {
   Static ControlCB := []
   If Controls.HasKey(HCTL) {
      DllCall("RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[HCTL], "Ptr", HCTL)
      DllCall("GlobalFree", "Ptr", Controls[HCTL], "Ptr")
      Controls.Delete(HCTL)
      If (FuncName = "")
         Return True
   }
   If !DllCall("IsWindow", "Ptr", HCTL, "UInt")
   || !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
   || !(CB := RegisterCallback(FuncName, , 6))
      Return False
   If !DllCall("SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)
      Return (DllCall("GlobalFree", "Ptr", CB, "Ptr") & 0)
   Return (ControlCB[HCTL] := CB)
}
; ==================================================================================================================================
/*
SubclassProc(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData) {
   ...
   ...
   ...
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "Ptr")
}
*/
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Re: colored listview headers

02 Mar 2021, 19:00

Nice!

Your SubclassControl function names the array "ControlCB" but then seems to access it with the name "Controls". Is this just a typo? Or if not, where is Controls defined?
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Re: colored listview headers

02 Mar 2021, 20:15

I tried adding a call to

Code: Select all

DllCall("Gdi32.dll\SetBkColor", "Ptr", hdc, "UInt", bgColor)
but it had no effect. I think the issue is that the background color is drawn by the header itself, not drawn as part of the header items.

Looking at the first link referenced in the first post, I think I see what they are doing:

They are actually using FillRect to fill the background color and then calling TextOut to write the text, and then they are returning CDRF_SKIPDEFAULT which tells the OS not to draw the item at all.

I confirmed that returning CDRF_SKIPDEFAULT instead of CDRF_NEWFONT does in fact make the header items output completely blank, so I think this might be the best way to replace the background color.

However, I am noticing that the extra strip of header if your items don't extend the full width of the LV is always white for me even when I am styling the header items with CDRF_SKIPDEFAULT. I guess that means the headers should always extend to 100% or more.

(Side note: Returning CDRF_NEWFONT as in the example above doesn't seem to matter--the text color takes effect in the example code regardless.)
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Re: colored listview headers

02 Mar 2021, 23:35

I got this working! Thanks to the previous code examples, and the references you linked to, I figured out how this is typically done I think.

Here is a simple example that makes the header black with white text. It's based on the previous example, but it draws its own background and text instead of just modifying the text color.

Code: Select all

#NoEnv
SetBatchLines, -1

; Create a GUI with a ListView
Gui, Margin, 20, 20
Gui, Add, ListView, w600 r20 hwndHLV Grid C0000FF NoSort, Message         |State           |Item            |TickCount
LV_ModifyCol(0, "AutoHdr")

; Get the HWND of the ListView's Header control
SendMessage, LVM_GETHEADER := 0x101F, 0, 0, , ahk_id %HLV%
HHEADER := ErrorLevel + 0

; Create an object containing the color for each Header control
HeaderColor := {}
HeaderColor[HHEADER] := {Color: 0xFF0000} ; Note: It's BGR instead of RGB!
SubClassControl(HLV, "HeaderCustomDraw")
Gui, Show, , Color LV Header

; Redraw the Header to get the notfications for all Header items
WinSet, Redraw, , ahk_id %HHEADER%
Return

GuiClose:
GuiEscape:
ExitApp

HeaderCustomDraw(H, M, W, L, IdSubclass, RefData) {
    Static WM_NOTIFY              := 0x004E
    Static WM_DESTROY             := 0x0002
    Static NM_CUSTOMDRAW          := -12
    static DT_LEFT                := 0
    Static CDRF_DODEFAULT         := 0x00000000
    Static CDRF_SKIPDEFAULT       := 0x00000004
    Static CDRF_NOTIFYITEMDRAW    := 0x00000020
    Static CDDS_PREPAINT          := 0x00000001
    Static CDDS_ITEMPREPAINT      := 0x00010001
    Static CDDS_SUBITEM           := 0x00020000
    Static OHWND      := 0
    Static OMsg       := (2 * A_PtrSize)
    Static ODrawStage := OMsg + 4 + (A_PtrSize - 4)
    Static OHDC       := ODrawStage + 4 + (A_PtrSize - 4)
    static ORect      := OHDC + 4 + (A_PtrSize - 4)
    Static OItemSpec  := OHDC + 16 + A_PtrSize
    Global HHEADER

    ; Get these from anywhere
    TextColor := 0xFFFFFF
    BackgroundColor := 0x000000

    Critical 1000

    if (M == WM_NOTIFY) {
        ; Get sending control's HWND
        HWND := NumGet(L + 0, OHWND, "UPtr")

        ; If HeaderColor contains appropriate key ...
        If (HHEADER == HWND) {
            Message := NumGet(L + 0, OMsg, "Int")

            If (Message == NM_CUSTOMDRAW) {
                DrawStage := NumGet(L + 0, ODrawStage, "UInt")

                ; -------------------------------------------------------------------------------------------------------------
                Item := NumGet(L + 0, OItemSpec, "Ptr")                                       ; for testing
                LV_Modify(LV_Add("", NM_CUSTOMDRAW, DrawStage, Item, A_TickCount), "Vis")     ; for testing
                ; -------------------------------------------------------------------------------------------------------------
                
                If (DrawStage = CDDS_ITEMPREPAINT) {
                    ; Get the device context
                    HDC := NumGet(L + 0, OHDC, "Ptr")

                    ; Get the Rect bounds for the header item
                    VarSetCapacity(Rect, 16)
                    NumPut(NumGet(L + 0, ORect, "Int"), Rect, 0, "Int")
                    NumPut(NumGet(L + 0, ORect + 4, "Int"), Rect, 4, "Int")
                    NumPut(NumGet(L + 0, ORect + 8, "Int"), Rect, 8, "Int")
                    NumPut(NumGet(L + 0, ORect + 12, "Int"), Rect, 12, "Int")

                    ; Draw a solid rectangle for the background
                    DllCall("Gdi32.dll\SetBkMode", "Ptr", HDC, "UInt", 0)
                    Brush := DllCall("CreateSolidBrush", "UInt", BackgroundColor, "Ptr")
                    DllCall("FillRect", "Ptr", HDC, "Ptr", &Rect, "Ptr", Brush)
                        
                    ; Draw the text
                    item := NumGet(L + 0, OItemSpec, "Ptr")+1
                    LV_GetText(Content, 0, item)
                    DllCall("Gdi32.dll\SetTextColor", "Ptr", HDC, "UInt", TextColor)
                    DllCall("DrawText", "Ptr", HDC, "Str", Content, "Int", StrLen(Content), "Ptr", &Rect, "UInt", DT_LEFT)

                    Return CDRF_SKIPDEFAULT
                }
                
                If (DrawStage = CDDS_PREPAINT) {
                    Return CDRF_NOTIFYITEMDRAW
                }
                
                Return CDRF_DODEFAULT
            }
        }
    } else if (M == WM_DESTROY) {
        SubclassControl(H, "") ; remove the subclass procedure
    }

    ; All messages not completely handled by the function must be passed to the DefSubclassProc:
    Return DllCall("DefSubclassProc", "Ptr", H, "UInt", M, "Ptr", W, "Ptr", L, "Ptr")
}

; ==================================================================================================================================
; SubclassControl    Installs, updates, or removes the subclass callback for the specified control.
; Parameters:        HCTL     -  Handle to the control.
;                    FuncName -  Name of the callback function as string.
;                                If you pass an empty string, the subclass callback will be removed.
;                    Data     -  Optional integer value passed as dwRefData to the callback function.
; Return value:      Non-zero if the subclass callback was successfully installed, updated, or removed; otherwise, False.
; Remarks:           The callback function must have exactly six parameters, see
;                    SUBCLASSPROC -> msdn.microsoft.com/en-us/library/bb776774(v=vs.85).aspx
; MSDN:              Subclassing Controls -> msdn.microsoft.com/en-us/library/bb773183(v=vs.85).aspx
; ==================================================================================================================================
SubclassControl(HCTL, FuncName, Data := 0) {
    Static ControlCB := []
    If Controls.HasKey(HCTL) {
        DllCall("RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[HCTL], "Ptr", HCTL)
        DllCall("GlobalFree", "Ptr", Controls[HCTL], "Ptr")
        Controls.Delete(HCTL)
        If (FuncName = "")
            Return True
    }
    
    If !DllCall("IsWindow", "Ptr", HCTL, "UInt")
        || !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
        || !(CB := RegisterCallback(FuncName, , 6))
        Return False
   
    If !DllCall("SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)
        Return (DllCall("GlobalFree", "Ptr", CB, "Ptr") & 0)
    
    Return (ControlCB[HCTL] := CB)
}
I think the way I'm re-creating the rect object is probably unnecessary, but I'm not very familiar with how to reference a rect within a rect and was getting errors until I created a new one.

Different options can be passed into DrawText to tell it how to position the text, I just left-aligned it for now.

You can use GDI functions to draw anything you want in the header--shapes, images, icons, text with any font you choose.
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers

03 Mar 2021, 12:10

bmcclure wrote:Your SubclassControl function names the array "ControlCB" but then seems to access it with the name "Controls". Is this just a typo? Or if not, where is Controls defined?
It's an error. I've fixed it in the following version.
bmcclure wrote:I think the way I'm re-creating the rect object is probably unnecessary, but I'm not very familiar with how to reference a rect within a rect and was getting errors until I created a new one.
The function expects a pointer to / the address of a rectangle structure. This address is the value of lParam + the offset to the rectangle: L + ORect.

I spent some time with your code. This is what I got:

Code: Select all

#NoEnv
SetBatchLines, -1
; Create a GUI with a ListView
Gui, Margin, 20, 20
Gui, Font, s12 Italic
Gui, Add, ListView, w600 r20 hwndHLV Grid NoSort, Message|State|Item|HickCount ; C0000FF
LV_Add("", "Message", "State", "Item", "HickCount")
; LV_ModifyCol(0, "AutoHdr")
LV_ModifyCol(1, "Left 150")
LV_ModifyCol(2, "Center 150")
LV_ModifyCol(3, "Right 150")
; Get the HWND of the ListView's Header control
HHDR := DllCall("SendMessage", "Ptr", HLV, "UInt", 0x101F, "Ptr", 0, "Ptr", 0, "UPtr") ; LVM_GETHEADER
; ----------------------------------------------------------------------------------------------------------------------
; DllCall("UxTheme.dll\SetWindowTheme", "Ptr", HHEADER, "Ptr", 0, "Str", "")     ; Win XP
; Control, Style, +0x0200, , ahk_id %HHEADER%                                    ; Win XP (HDS_FLAT = 0x0200)
; ----------------------------------------------------------------------------------------------------------------------
; Create an object containing the color for each Header control
HeaderColors := {}
HeaderColors[HHDR] := {Txt: 0xFFFFFF, Bkg: 0x0000FF} ; Note: It's BGR instead of RGB!
SubClassControl(HLV, "HeaderCustomDraw")
Gui, Show, , Color LV Header
; Redraw the Header to get the notfications for all Header items
WinSet, Redraw, , ahk_id %HHEADER%
Return
GuiClose:
GuiEscape:
ExitApp

HeaderCustomDraw(H, M, W, L, IdSubclass, RefData) {
   Static HDM_GETITEM            := (A_IsUnicode ? 0x120B : 0x1203) ; ? HDM_GETITEMW : HDM_GETITEMA
   Static NM_CUSTOMDRAW          := -12
   Static CDRF_DODEFAULT         := 0x00000000
   Static CDRF_SKIPDEFAULT       := 0x00000004
   Static CDRF_NOTIFYITEMDRAW    := 0x00000020
   Static CDDS_PREPAINT          := 0x00000001
   Static CDDS_ITEMPREPAINT      := 0x00010001
   Static CDDS_SUBITEM           := 0x00020000
   Static DC_Brush   := DllCall("GetStockObject", "UInt", 18, "UPtr") ; DC_BRUSH = 18
   Static OHWND      := 0
   Static OMsg       := (2 * A_PtrSize)
   Static ODrawStage := OMsg + A_PtrSize
   Static OHDC       := ODrawStage + A_PtrSize
   static ORect      := OHDC + A_PtrSize
   Static OItemSpec  := OHDC + 16 + A_PtrSize
   Static LM := 4    ; left margin of the first column (determined experimentally)
   Static TM := 6    ; left and right text margins (determined experimentally)
   Global HeaderColors
   ;
   Critical ; 1000 ; ?
   ;
   If (M = 0x4E) { ; WM_NOTIFY
      ; Get sending control's HWND
      HWND := NumGet(L + OHWND, "UPtr")
      ; If HeaderColors contains an appropriate key ...
      If HeaderColors.HasKey(HWND) {
         HC := HeaderColors[HWND]
         Code := NumGet(L + OMsg, "Int")
         If (Code = NM_CUSTOMDRAW) {
            DrawStage := NumGet(L + ODrawStage, "UInt")
            ; -------------------------------------------------------------------------------------------------------------
            ; Item := NumGet(L + OItemSpec, "Ptr")                                          ; for testing
            ; LV_Modify(LV_Add("", NM_CUSTOMDRAW, DrawStage, Item, A_TickCount), "Vis")     ; for testing
            ; -------------------------------------------------------------------------------------------------------------
            If (DrawStage = CDDS_ITEMPREPAINT) {
               ; Get the item's text, format and column order
               Item := NumGet(L + OItemSpec, "Ptr")
               VarSetCapacity(HDITEM, 24 + (6 * A_PtrSize), 0)
               VarSetCapacity(ItemTxt, 520, 0)
               NumPut(0x86, HDITEM, "UInt") ; HDI_TEXT (0x02) | HDI_FORMAT (0x04) | HDI_ORDER (0x80)
               NumPut(&ItemTxt, HDITEM, 8, "Ptr")
               NumPut(260, HDITEM, 8 + (2 * A_PtrSize), "Int")
               DllCall("SendMessage", "Ptr", HWND, "UInt", HDM_GETITEM, "Ptr", Item, "Ptr", &HDITEM)
               VarSetCapacity(ItemTxt, -1)
               Fmt := NumGet(HDITEM, 12 + (2 * A_PtrSize), "UInt") & 3
               Order := NumGet(HDITEM, 20 + (3 * A_PtrSize), "Int")
               ; Get the device context
               HDC := NumGet(L + OHDC, "Ptr")
               ; Draw a solid rectangle for the background
               (Item = 0) && (Order = 0) ? NumPut(NumGet(L + ORect, "Int") + LM, L + ORect, "Int") : ""
               DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", HC.Bkg)
               DllCall("FillRect", "Ptr", HDC, "Ptr", L + ORect, "Ptr", DC_Brush)
               (Item = 0) && (Order = 0) ? NumPut(NumGet(L + ORect, "Int") - LM, L + ORect, "Int") : ""
               ; Draw the text
               DllCall("SetBkMode", "Ptr", HDC, "UInt", 0)
               DllCall("SetTextColor", "Ptr", HDC, "UInt", HC.Txt)
               DllCall("InflateRect", "Ptr", L + ORect, "Int", -TM, "Int", 0)
               ; DT_EXTERNALLEADING (0x0200) | DT_SINGLELINE (0x20) | DT_VCENTER (0x04)
               ; HDF_LEFT (0x00)   -> DT_LEFT (0x00
               ; HDF_CENTER (0x02) -> DT_CENTER (0x01)
               ; HDF_RIGHT (0x01)  -> DT_RIGHT (0x02)
               DT_ALIGN := 0x0224 + ((Fmt & 1) ? 2 : (Fmt & 2) ? 1 : 0) ;
               DllCall("DrawText", "Ptr", HDC, "Ptr", &ItemTxt, "Int", -1, "Ptr", L + ORect, "UInt", DT_ALIGN)
               Return CDRF_SKIPDEFAULT
            }
            Return (DrawStage = CDDS_PREPAINT) ? CDRF_NOTIFYITEMDRAW : CDRF_DODEFAULT
         }
      }
   }
   Else If (M = 0x02) { ; WM_DESTROY
      SubclassControl(H, "") ; remove the subclass procedure
   }
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", H, "UInt", M, "Ptr", W, "Ptr", L, "Ptr")
}
; ==================================================================================================================================
; SubclassControl    Installs, updates, or removes the subclass callback for the specified control.
; Parameters:        HCTL     -  Handle to the control.
;                    FuncName -  Name of the callback function as string.
;                                If you pass an empty string, the subclass callback will be removed.
;                    Data     -  Optional integer value passed as dwRefData to the callback function.
; Return value:      Non-zero if the subclass callback was successfully installed, updated, or removed; otherwise, False.
; Remarks:           The callback function must have exactly six parameters, see
;                    SUBCLASSPROC -> msdn.microsoft.com/en-us/library/bb776774(v=vs.85).aspx
; MSDN:              Subclassing Controls -> msdn.microsoft.com/en-us/library/bb773183(v=vs.85).aspx
; ==================================================================================================================================
SubclassControl(HCTL, FuncName, Data := 0) {
   Static ControlCB := []
   If ControlCB.HasKey(HCTL) {
      DllCall("RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[HCTL], "Ptr", HCTL)
      DllCall("GlobalFree", "Ptr", ControlCB[HCTL], "Ptr")
      ControlCB.Delete(HCTL)
      If (FuncName = "")
         Return True
   }
   If !DllCall("IsWindow", "Ptr", HCTL, "UInt")
   || !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
   || !(CB := RegisterCallback(FuncName, , 6))
      Return False
   If !DllCall("SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)
      Return (DllCall("GlobalFree", "Ptr", CB, "Ptr") & 0)
   Return (ControlCB[HCTL] := CB)
}
; ==================================================================================================================================
/*
SubclassProc(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData) {
   ...
   ...
   ...
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "Ptr")
}
*/



/*
typedef struct _HD_ITEMA {
  UINT    mask;
  int     cxy;
  LPSTR   pszText;
  HBITMAP hbm;
  int     cchTextMax;
  int     fmt;
  LPARAM  lParam;
  int     iImage;
  int     iOrder;
  UINT    type;
  void    *pvFilter;
  UINT    state;
} HDITEMA, *LPHDITEMA;

#define DT_LEFT                     0x00000000
#define DT_CENTER                   0x00000001
#define DT_RIGHT                    0x00000002
#define DT_VCENTER                  0x00000004
*/
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Re: colored listview headers

03 Mar 2021, 14:39

Thanks for updating the code! That is very useful, and I'll pull a bunch of those changes into my application.

I'm thrilled to have this working so well, especially with your changes. I only have two issues now:
  1. The empty space to the right of the last column header is always white. I am auto-sizing my columns to fill the space, but a user can still drag the last column smaller which shows the white space. Can I hook into the background painting process some other way in the subclass since it isn't a part of the custom draw callbacks that are sent? In the example code from the first link in the first post (I think), I saw they were using a method in their overridden class that paints the background during the erase phase, which I guess is called just before the background needs to be painted.
  2. The ItemSelect event (and presumably others?) no longer fires on the listview using the typical AHK method. I think I probably need to manually hook into the notifications sent by the LV since it's happening in a subclass now, but I haven't dug into this too deeply yet
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers

04 Mar 2021, 06:29

Hi,

1. I'm testing on Win 10. Here, the (coloured) header has the same width as the coloured area of a selected list view row. On Win Vista+ you can prevent the resizing of columns by the user using somthing like

Code: Select all

; Get the HWND of the ListView's Header control
HHDR := DllCall("SendMessage", "Ptr", HLV, "UInt", 0x101F, "Ptr", 0, "Ptr", 0, "UPtr") ; LVM_GETHEADER
Control, Style, +0x0800, , ahk_id %HHDR% ; HDS_NOSIZING = 0x0800 - Win Vista+
2. After a first test the g-label events seem to fire here.
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Re: colored listview headers

04 Mar 2021, 20:46

1. Yeah, the colored header items start out at the same width as the header itself. I'm trying to avoid preventing header resizing if at all possible, so that's why I'm thinking it would be beneficial to repaint the background of the header itself, vs only the header items.

Additionally, when resizing the window I often see flashes of white along the right edge of the header in my application. This is more noticeable on a dark application where the white color really sticks out.

2. For me the ItemSelect event never fires when I use this custom window subclassing method in my script. However, my script is using AHK v2 so it's possible that the GUI event handling is different enough that the issue is v2-alpha specific

AHK never hits my ItemSelect callback when subclassing the window, where it hits it properly when I comment out the subclassing call. The other events all seem to work fine for me so far, it is only ItemSelect.

I thought maybe the issue was that the Critical callback, which runs whenever the ListView is interacted with, was preventing the window message for ItemSelect from being consumed by AHK since that probably fires at about the same time, but removing Critical doesn't fix it (and does start causing freezing when clicking the ListView, so I added it back).

I could probably detect the window message from within the same callback function that is painting the header items, but I'd rather not add more work to a critical thread if I can help it, and I'd prefer to just use the existing AHK event if I can find out how. But AHK v2 issues are probably outside the scope of this thread, so if ItemSelect is working on AHK v1 when subclassing the window, then at least that gives me some clues.
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers

07 Mar 2021, 05:12

Well, this might come closer. It might be some kind of basic framework for list view header custom drawing, especially on 64-bit:

Code: Select all

#NoEnv
SetBatchLines, -1
; MsgBox, % Format("{:06X}", DllCall("GetSysColor", "Int", 15, "UInt"))
; Create a GUI with a ListView
Gui, +hwndHGUI
Gui, Margin, 20, 20
Gui, Font, s12 Italic
Gui, Add, ListView, w600 r20 hwndHLV Grid NoSort gSubLV AltSubmit Background808080
   , Message|State|Item|HickCount ; C0000FF
; Get the HWND of the ListView's Header control
HHDR := DllCall("SendMessage", "Ptr", HLV, "UInt", 0x101F, "Ptr", 0, "Ptr", 0, "UPtr") ; LVM_GETHEADER
; Create an object containing the color for each Header control
HeaderColors := {}
HeaderColors[HHDR] := {Txt: 0xFFFFFF, Bkg: 0xFE9050, Grid: 1} ; Note: It's BGR instead of RGB!
SubClassControl(HLV, "LV_HeaderCustomDraw")
LV_Add("", "Message", "State", "Item", "HickCount")
; LV_ModifyCol(0, "AutoHdr")
LV_ModifyCol(1, "Left 150")
LV_ModifyCol(2, "Center 150")
LV_ModifyCol(3, "Right 150")
LV_ModifyCol(4, "100")
; ----------------------------------------------------------------------------------------------------------------------
; DllCall("UxTheme.dll\SetWindowTheme", "Ptr", HHEADER, "Ptr", 0, "Str", "")     ; Win XP
; Control, Style, +0x0200, , ahk_id %HHEADER%                                    ; Win XP (HDS_FLAT = 0x0200)
; ----------------------------------------------------------------------------------------------------------------------
Gui, Show, , Colored LV Header
; Redraw the Header to get the notfications for all Header items
WinSet, Redraw, , ahk_id %HHEADER%
Return

GuiClose:
GuiEscape:
ExitApp

SubLV:
; ToolTip, %A_GuiEvent% - %ErrorLevel%
Return

LV_HeaderCustomDraw(H, M, W, L, IdSubclass, RefData) {
   Static DC_Brush      := DllCall("GetStockObject", "UInt", 18, "UPtr") ; DC_BRUSH = 18
   Static DC_Pen        := DllCall("GetStockObject", "UInt", 19, "UPtr") ; DC_PEN = 19
   Static DefGridClr    := DllCall("GetSysColor", "Int", 15, "UInt") ; COLOR_3DFACE
   Static HDM_GETITEM   := (A_IsUnicode ? 0x120B : 0x1203) ; ? HDM_GETITEMW : HDM_GETITEMA
   Static OHWND         := 0
   Static OCode         := (2 * A_PtrSize)
   Static ODrawStage    := OCode + A_PtrSize
   Static OHDC          := ODrawStage + A_PtrSize
   static ORect         := OHDC + A_PtrSize
   Static OItemSpec     := ORect + 16
   Static OItemState    := OItemSpec + A_PtrSize
   Static LM := 4       ; left margin of the first column (determined experimentally)
   Static TM := 6       ; left and right text margins (determined experimentally)
   Global HeaderColors
   ;
   Critical 1000 ; ?
   ;
   If (M = 0x004E) && (NumGet(L + OCode, "Int") = -12) { ; WM_NOTIFY -> NM_CUSTOMDRAW
      ; Get sending control's HWND
      HHD := NumGet(L + OHWND, "UPtr")
      ; If HeaderColors contains an appropriate key ...
      If HeaderColors.HasKey(HHD) {
         HC := HeaderColors[HHD]
         DrawStage := NumGet(L + ODrawStage, "UInt")
         ; -------------------------------------------------------------------------------------------------------------
         If (DrawStage = 0x00010001) { ; CDDS_ITEMPREPAINT
            ; Get the item's text, format and column order
            Item := NumGet(L + OItemSpec, "Ptr")
            , VarSetCapacity(HDITEM, 24 + (6 * A_PtrSize), 0)
            , VarSetCapacity(ItemTxt, 520, 0)
            , NumPut(0x86, HDITEM, "UInt") ; HDI_TEXT (0x02) | HDI_FORMAT (0x04) | HDI_ORDER (0x80)
            , NumPut(&ItemTxt, HDITEM, 8, "Ptr")
            , NumPut(260, HDITEM, 8 + (2 * A_PtrSize), "Int")
            , DllCall("SendMessage", "Ptr", HHD, "UInt", HDM_GETITEM, "Ptr", Item, "Ptr", &HDITEM)
            , VarSetCapacity(ItemTxt, -1)
            , Fmt := NumGet(HDITEM, 12 + (2 * A_PtrSize), "UInt") & 3
            , Order := NumGet(HDITEM, 20 + (3 * A_PtrSize), "Int")
            ; Get the device context
            , HDC := NumGet(L + OHDC, "Ptr")
            ; Draw a solid rectangle for the background
            , VarSetCapacity(RC, 16, 0)
            , DllCall("CopyRect", "Ptr", &RC, "Ptr", L + ORect)
            , NumPut(NumGet(RC, "Int") + (!(Item | Order) ? LM : 0), RC, "Int")
            , NumPut(NumGet(RC, 8, "Int") + 1, RC, 8, "Int")
            , DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", HC.Bkg)
            , DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)
            ; Draw the text
            DllCall("SetBkMode", "Ptr", HDC, "UInt", 0)
            , DllCall("SetTextColor", "Ptr", HDC, "UInt", HC.Txt)
            , DllCall("InflateRect", "Ptr", L + ORect, "Int", -TM, "Int", 0)
            ; DT_EXTERNALLEADING (0x0200) | DT_SINGLELINE (0x20) | DT_VCENTER (0x04)
            ; HDF_LEFT (0) -> DT_LEFT (0), HDF_CENTER (2) -> DT_CENTER (1), HDF_RIGHT (1) -> DT_RIGHT (2)
            , DT_ALIGN := 0x0224 + ((Fmt & 1) ? 2 : (Fmt & 2) ? 1 : 0)
            , DllCall("DrawText", "Ptr", HDC, "Ptr", &ItemTxt, "Int", -1, "Ptr", L + ORect, "UInt", DT_ALIGN)
            ; Draw a 'grid' line at the left edge of the item if required
            If (HC.Grid) && (Order) {
               DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
               , DllCall("SetDCPenColor", "Ptr", HDC, "UInt", DefGridClr)
               , NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
               , DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
            }
            Return 4 ; CDRF_SKIPDEFAULT
         }
         ; -------------------------------------------------------------------------------------------------------------
         If (DrawStage = 1) { ; CDDS_PREPAINT
            Return 0x30 ; CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT
         }
         ; -------------------------------------------------------------------------------------------------------------
         If (DrawStage = 2) { ; CDDS_POSTPAINT
            VarSetCapacity(RC, 16, 0)
            , DllCall("GetClientRect", "Ptr", HHD, "Ptr", &RC, "UInt")
            , Cnt := DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1200, "Ptr", 0, "Ptr", 0, "Int") ; HDM_GETITEMCOUNT
            , VarSetCapacity(RCI, 16, 0)
            , DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1207, "Ptr", Cnt - 1, "Ptr", &RCI) ; HDM_GETITEMRECT
            , R1 := NumGet(RC, 8, "Int")
            , R2 := NumGet(RCI, 8, "Int")
            If (R2 < R1) {
               HDC := NumGet(L + OHDC, "UPtr")
               , NumPut(R2, RC, 0, "Int")
               , DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", HC.Bkg)
               , DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)
               If (HC.Grid) {
                  DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
                  , DllCall("SetDCPenColor", "Ptr", HDC, "UInt", DefGridClr)
                  , NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
                  , DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
               }
            }
            Return 4 ; CDRF_SKIPDEFAULT
         }
         ; All other drawing stages ------------------------------------------------------------------------------------
         Return 0 ; CDRF_DODEFAULT
      }
   }
   Else If (M = 0x0002) { ; WM_DESTROY
      SubclassControl(H, "") ; remove the subclass procedure
   }
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", H, "UInt", M, "Ptr", W, "Ptr", L, "Ptr")
}
; ======================================================================================================================
; SubclassControl    Installs, updates, or removes the subclass callback for the specified control.
; Parameters:        HCTL     -  Handle to the control.
;                    FuncName -  Name of the callback function as string.
;                                If you pass an empty string, the subclass callback will be removed.
;                    Data     -  Optional integer value passed as dwRefData to the callback function.
; Return value:      Non-zero if the subclass callback was successfully installed, updated, or removed;
;                    otherwise, False.
; Remarks:           The callback function must have exactly six parameters, see
;                    SUBCLASSPROC -> msdn.microsoft.com/en-us/library/bb776774(v=vs.85).aspx
; MSDN:              Subclassing Controls -> msdn.microsoft.com/en-us/library/bb773183(v=vs.85).aspx
; ======================================================================================================================
SubclassControl(HCTL, FuncName, Data := 0) {
   Static ControlCB := []
   If ControlCB.HasKey(HCTL) {
      DllCall("RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[HCTL], "Ptr", HCTL)
      DllCall("GlobalFree", "Ptr", ControlCB[HCTL], "Ptr")
      ControlCB.Delete(HCTL)
      If (FuncName = "")
         Return True
   }
   If !DllCall("IsWindow", "Ptr", HCTL, "UInt")
   || !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
   || !(CB := RegisterCallback(FuncName, , 6))
      Return False
   If !DllCall("SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)
      Return (DllCall("GlobalFree", "Ptr", CB, "Ptr") & 0)
   Return (ControlCB[HCTL] := CB)
}
; ======================================================================================================================
/*
SubclassProc(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData) {
   ...
   ...
   ...
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "Ptr")
}
*/



/*
typedef struct _HD_ITEMA {
  UINT    mask;
  int     cxy;
  LPSTR   pszText;
  HBITMAP hbm;
  int     cchTextMax;
  int     fmt;
  LPARAM  lParam;
  int     iImage;
  int     iOrder;
  UINT    type;
  void    *pvFilter;
  UINT    state;
} HDITEMA, *LPHDITEMA;

#define DT_LEFT                     0x00000000
#define DT_CENTER                   0x00000001
#define DT_RIGHT                    0x00000002
#define DT_VCENTER                  0x00000004
*/
User avatar
Bugz000
Posts: 93
Joined: 30 Sep 2013, 10:01

Re: colored listview headers

10 Mar 2021, 00:51

i come back and you guys have all been smashing out these code snippets... amazing work guys! <3 thankyou i will certainly be using these in the future :D these will go on to help many folk, colored headers are a must, along with colored cells/rows/columns (which all already exist)
Image
||-------[-HP-ML350E-G8-]-------||-[-32-core-xeon-]-||--[-48gb-ECC-]--||
||----[-Dell-Poweredge-r610-]---||-[-16-core-xeon-]-||--[-16gb-ECC-]--||
||-[-Lenovo-ThinkPad-x201-tab-]-||---[-4-core-i7-]--||-[-8gb-nonECC-]-||
||---------------------------[-shack--img-]---------------------------||
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Re: colored listview headers

10 Mar 2021, 22:43

Thanks just me, the updated code looks awesome!
User avatar
KruschenZ
Posts: 45
Joined: 20 Jan 2021, 07:05
Location: Germany (Rheinhessen)
Contact:

Re: colored listview headers

07 Dec 2021, 03:33

just me wrote:
07 Mar 2021, 05:12
Well, this might come closer. It might be some kind of basic framework for list view header custom drawing, especially on 64-bit:

Code: Select all

#NoEnv
SetBatchLines, -1
; MsgBox, % Format("{:06X}", DllCall("GetSysColor", "Int", 15, "UInt"))
; Create a GUI with a ListView
Gui, +hwndHGUI
Gui, Margin, 20, 20
Gui, Font, s12 Italic
Gui, Add, ListView, w600 r20 hwndHLV Grid NoSort gSubLV AltSubmit Background808080
   , Message|State|Item|HickCount ; C0000FF
; Get the HWND of the ListView's Header control
HHDR := DllCall("SendMessage", "Ptr", HLV, "UInt", 0x101F, "Ptr", 0, "Ptr", 0, "UPtr") ; LVM_GETHEADER
; Create an object containing the color for each Header control
HeaderColors := {}
HeaderColors[HHDR] := {Txt: 0xFFFFFF, Bkg: 0xFE9050, Grid: 1} ; Note: It's BGR instead of RGB!
SubClassControl(HLV, "LV_HeaderCustomDraw")
LV_Add("", "Message", "State", "Item", "HickCount")
; LV_ModifyCol(0, "AutoHdr")
LV_ModifyCol(1, "Left 150")
LV_ModifyCol(2, "Center 150")
LV_ModifyCol(3, "Right 150")
LV_ModifyCol(4, "100")
; ----------------------------------------------------------------------------------------------------------------------
; DllCall("UxTheme.dll\SetWindowTheme", "Ptr", HHEADER, "Ptr", 0, "Str", "")     ; Win XP
; Control, Style, +0x0200, , ahk_id %HHEADER%                                    ; Win XP (HDS_FLAT = 0x0200)
; ----------------------------------------------------------------------------------------------------------------------
Gui, Show, , Colored LV Header
; Redraw the Header to get the notfications for all Header items
WinSet, Redraw, , ahk_id %HHEADER%
Return

GuiClose:
GuiEscape:
ExitApp

SubLV:
; ToolTip, %A_GuiEvent% - %ErrorLevel%
Return

LV_HeaderCustomDraw(H, M, W, L, IdSubclass, RefData) {
   Static DC_Brush      := DllCall("GetStockObject", "UInt", 18, "UPtr") ; DC_BRUSH = 18
   Static DC_Pen        := DllCall("GetStockObject", "UInt", 19, "UPtr") ; DC_PEN = 19
   Static DefGridClr    := DllCall("GetSysColor", "Int", 15, "UInt") ; COLOR_3DFACE
   Static HDM_GETITEM   := (A_IsUnicode ? 0x120B : 0x1203) ; ? HDM_GETITEMW : HDM_GETITEMA
   Static OHWND         := 0
   Static OCode         := (2 * A_PtrSize)
   Static ODrawStage    := OCode + A_PtrSize
   Static OHDC          := ODrawStage + A_PtrSize
   static ORect         := OHDC + A_PtrSize
   Static OItemSpec     := ORect + 16
   Static OItemState    := OItemSpec + A_PtrSize
   Static LM := 4       ; left margin of the first column (determined experimentally)
   Static TM := 6       ; left and right text margins (determined experimentally)
   Global HeaderColors
   ;
   Critical 1000 ; ?
   ;
   If (M = 0x004E) && (NumGet(L + OCode, "Int") = -12) { ; WM_NOTIFY -> NM_CUSTOMDRAW
      ; Get sending control's HWND
      HHD := NumGet(L + OHWND, "UPtr")
      ; If HeaderColors contains an appropriate key ...
      If HeaderColors.HasKey(HHD) {
         HC := HeaderColors[HHD]
         DrawStage := NumGet(L + ODrawStage, "UInt")
         ; -------------------------------------------------------------------------------------------------------------
         If (DrawStage = 0x00010001) { ; CDDS_ITEMPREPAINT
            ; Get the item's text, format and column order
            Item := NumGet(L + OItemSpec, "Ptr")
            , VarSetCapacity(HDITEM, 24 + (6 * A_PtrSize), 0)
            , VarSetCapacity(ItemTxt, 520, 0)
            , NumPut(0x86, HDITEM, "UInt") ; HDI_TEXT (0x02) | HDI_FORMAT (0x04) | HDI_ORDER (0x80)
            , NumPut(&ItemTxt, HDITEM, 8, "Ptr")
            , NumPut(260, HDITEM, 8 + (2 * A_PtrSize), "Int")
            , DllCall("SendMessage", "Ptr", HHD, "UInt", HDM_GETITEM, "Ptr", Item, "Ptr", &HDITEM)
            , VarSetCapacity(ItemTxt, -1)
            , Fmt := NumGet(HDITEM, 12 + (2 * A_PtrSize), "UInt") & 3
            , Order := NumGet(HDITEM, 20 + (3 * A_PtrSize), "Int")
            ; Get the device context
            , HDC := NumGet(L + OHDC, "Ptr")
            ; Draw a solid rectangle for the background
            , VarSetCapacity(RC, 16, 0)
            , DllCall("CopyRect", "Ptr", &RC, "Ptr", L + ORect)
            , NumPut(NumGet(RC, "Int") + (!(Item | Order) ? LM : 0), RC, "Int")
            , NumPut(NumGet(RC, 8, "Int") + 1, RC, 8, "Int")
            , DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", HC.Bkg)
            , DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)
            ; Draw the text
            DllCall("SetBkMode", "Ptr", HDC, "UInt", 0)
            , DllCall("SetTextColor", "Ptr", HDC, "UInt", HC.Txt)
            , DllCall("InflateRect", "Ptr", L + ORect, "Int", -TM, "Int", 0)
            ; DT_EXTERNALLEADING (0x0200) | DT_SINGLELINE (0x20) | DT_VCENTER (0x04)
            ; HDF_LEFT (0) -> DT_LEFT (0), HDF_CENTER (2) -> DT_CENTER (1), HDF_RIGHT (1) -> DT_RIGHT (2)
            , DT_ALIGN := 0x0224 + ((Fmt & 1) ? 2 : (Fmt & 2) ? 1 : 0)
            , DllCall("DrawText", "Ptr", HDC, "Ptr", &ItemTxt, "Int", -1, "Ptr", L + ORect, "UInt", DT_ALIGN)
            ; Draw a 'grid' line at the left edge of the item if required
            If (HC.Grid) && (Order) {
               DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
               , DllCall("SetDCPenColor", "Ptr", HDC, "UInt", DefGridClr)
               , NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
               , DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
            }
            Return 4 ; CDRF_SKIPDEFAULT
         }
         ; -------------------------------------------------------------------------------------------------------------
         If (DrawStage = 1) { ; CDDS_PREPAINT
            Return 0x30 ; CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT
         }
         ; -------------------------------------------------------------------------------------------------------------
         If (DrawStage = 2) { ; CDDS_POSTPAINT
            VarSetCapacity(RC, 16, 0)
            , DllCall("GetClientRect", "Ptr", HHD, "Ptr", &RC, "UInt")
            , Cnt := DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1200, "Ptr", 0, "Ptr", 0, "Int") ; HDM_GETITEMCOUNT
            , VarSetCapacity(RCI, 16, 0)
            , DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1207, "Ptr", Cnt - 1, "Ptr", &RCI) ; HDM_GETITEMRECT
            , R1 := NumGet(RC, 8, "Int")
            , R2 := NumGet(RCI, 8, "Int")
            If (R2 < R1) {
               HDC := NumGet(L + OHDC, "UPtr")
               , NumPut(R2, RC, 0, "Int")
               , DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", HC.Bkg)
               , DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)
               If (HC.Grid) {
                  DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
                  , DllCall("SetDCPenColor", "Ptr", HDC, "UInt", DefGridClr)
                  , NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
                  , DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
               }
            }
            Return 4 ; CDRF_SKIPDEFAULT
         }
         ; All other drawing stages ------------------------------------------------------------------------------------
         Return 0 ; CDRF_DODEFAULT
      }
   }
   Else If (M = 0x0002) { ; WM_DESTROY
      SubclassControl(H, "") ; remove the subclass procedure
   }
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", H, "UInt", M, "Ptr", W, "Ptr", L, "Ptr")
}
; ======================================================================================================================
; SubclassControl    Installs, updates, or removes the subclass callback for the specified control.
; Parameters:        HCTL     -  Handle to the control.
;                    FuncName -  Name of the callback function as string.
;                                If you pass an empty string, the subclass callback will be removed.
;                    Data     -  Optional integer value passed as dwRefData to the callback function.
; Return value:      Non-zero if the subclass callback was successfully installed, updated, or removed;
;                    otherwise, False.
; Remarks:           The callback function must have exactly six parameters, see
;                    SUBCLASSPROC -> msdn.microsoft.com/en-us/library/bb776774(v=vs.85).aspx
; MSDN:              Subclassing Controls -> msdn.microsoft.com/en-us/library/bb773183(v=vs.85).aspx
; ======================================================================================================================
SubclassControl(HCTL, FuncName, Data := 0) {
   Static ControlCB := []
   If ControlCB.HasKey(HCTL) {
      DllCall("RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[HCTL], "Ptr", HCTL)
      DllCall("GlobalFree", "Ptr", ControlCB[HCTL], "Ptr")
      ControlCB.Delete(HCTL)
      If (FuncName = "")
         Return True
   }
   If !DllCall("IsWindow", "Ptr", HCTL, "UInt")
   || !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
   || !(CB := RegisterCallback(FuncName, , 6))
      Return False
   If !DllCall("SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)
      Return (DllCall("GlobalFree", "Ptr", CB, "Ptr") & 0)
   Return (ControlCB[HCTL] := CB)
}
; ======================================================================================================================
/*
SubclassProc(hWnd, uMsg, wParam, lParam, uIdSubclass, dwRefData) {
   ...
   ...
   ...
   ; All messages not completely handled by the function must be passed to the DefSubclassProc:
   Return DllCall("DefSubclassProc", "Ptr", hWnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam, "Ptr")
}
*/



/*
typedef struct _HD_ITEMA {
  UINT    mask;
  int     cxy;
  LPSTR   pszText;
  HBITMAP hbm;
  int     cchTextMax;
  int     fmt;
  LPARAM  lParam;
  int     iImage;
  int     iOrder;
  UINT    type;
  void    *pvFilter;
  UINT    state;
} HDITEMA, *LPHDITEMA;

#define DT_LEFT                     0x00000000
#define DT_CENTER                   0x00000001
#define DT_RIGHT                    0x00000002
#define DT_VCENTER                  0x00000004
*/

So first of all, this is very very helpful! :dance: :clap:
Thank you @just-me

I still have 2 questions: :?:

1) in the "DrawStage = 0x00010001" for the grid, there is yes a polyline drawn on the left; I found out by accident :crazy: , that also one is drawn above. :D But I can't get it to draw one below as well.

Code: Select all

; Draw: A 'Grid' Line

If (Grid) && (Order)
{
DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
, DllCall("SetDCPenColor", "Ptr", HDC, "UInt", GUI_Color_FontNormal)


; Left
, NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
, DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)

; Top
, NumPut(NumGet(RC, 10, "Int"), RC, 8, "Int")
, DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
}

2) Unfortunately I had to comment out "DrawStage = 2", because otherwise in interaction with the style "+LV0x4000" LVS_EX_LABELTIP only the box is displayed, but no text within it. Is there a chance here to display the text as well?

Code: Select all

If (DrawStage = 2)
{
; CDDS_POSTPAINT

VarSetCapacity(RC, 16, 0)
, DllCall("GetClientRect", "Ptr", HHD, "Ptr", &RC, "UInt")
, Cnt := DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1200, "Ptr", 0, "Ptr", 0, "Int") // HDM_GETITEMCOUNT
, VarSetCapacity(RCI, 16, 0)
, DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1207, "Ptr", Cnt - 1, "Ptr", &RCI) // HDM_GETITEMRECT
, R1 := NumGet(RC, 8, "Int")
, R2 := NumGet(RCI, 8, "Int")

If (R2 < R1)
{

; --> Conflict: with LABELTIP LV0x4000 > shows only the background without text

HDC := NumGet(L + OHDC, "UPtr")
, NumPut(R2, RC, 0, "Int")
DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", GUI_Color_BG)
DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)


If (Grid)
{
DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
, DllCall("SetDCPenColor", "Ptr", HDC, "UInt", GUI_Color_FontNormal)
, NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
, DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
}

}

Return 4 // CDRF_SKIPDEFAULT
}

Thank you very much, for any answer :) ;)

Many regards
KruschenZ
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers

07 Dec 2021, 05:13

Hi @KruschenZ:

A RECT structure contains four signed integer values defining the coordinates of two points:
X1 (Left) at offset 0, Y1 (Top) at offset 4.
X2 (Right) at offset 8, Y2 (Bottom) at offset 12.
None of the values starts at offset 10. So

Code: Select all

, NumPut(NumGet(RC, 10, "Int"), RC, 8, "Int")
doesn't make sense.

1) Which edges do you want to draw?

2) I cannot reproduce the issue here. Could you provide an example with a ListView showing the issue?
User avatar
KruschenZ
Posts: 45
Joined: 20 Jan 2021, 07:05
Location: Germany (Rheinhessen)
Contact:

Re: colored listview headers

07 Dec 2021, 05:47

just me wrote:
07 Dec 2021, 05:13
Hi @KruschenZ:

A RECT structure contains four signed integer values defining the coordinates of two points:
X1 (Left) at offset 0, Y1 (Top) at offset 4.
X2 (Right) at offset 8, Y2 (Bottom) at offset 12.
None of the values starts at offset 10. So

Code: Select all

, NumPut(NumGet(RC, 10, "Int"), RC, 8, "Int")
doesn't make sense.
That's why I called it an accident. ;)
just me wrote:
07 Dec 2021, 05:13
Hi @KruschenZ:
1) Which edges do you want to draw?
Top (the right one...) and Bottom, please :-)
Maybe I understand then the right values/syntax...

just me wrote:
07 Dec 2021, 05:13
Hi @KruschenZ:
2) I cannot reproduce the issue here. Could you provide an example with a ListView showing the issue?
> not jet, but I will try to reproduce this in a simple example code; maybe it collides with "[Class] LV_Colors - 1.1.04.01 (2016-05-03)" "viewtopic.php?f=6&t=1081", or with something else but I don't know yet.


Thank you again :thumbup:

Many regards
KruschenZ
just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: colored listview headers

07 Dec 2021, 08:00

If you want to draw more than one edge you can use something like the following:

Code: Select all

      L := NumGet(RC,  0, "Int") ; Left
      T := NumGet(RC,  4, "Int") ; Top
      R := NumGet(RC,  8, "Int") ; Right
      B := NumGet(RC, 12, "Int") ; Bottom
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, L, T, L, B), "Int", 2) ; left edge
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, L, T, R, T), "Int", 2) ; top edge
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, R, T, R, B), "Int", 2) ; right edge
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, L, B - 1, R, B - 1), "Int", 2) ; bottom edge (subtract 1 to prevent overdrawing by the first list-view item)

...
SetRect(ByRef RC, L := 0, T := 0, R := 0, B := 0) {
   VarSetCapacity(RC, 16, 0)
   , NumPut(L, RC,  0, "Int")
   , NumPut(T, RC,  4, "Int")
   , NumPut(R, RC,  8, "Int")
   , NumPut(B, RC, 12, "Int")
   Return &RC
}
eugenesv
Posts: 174
Joined: 21 Dec 2015, 10:11

Re: colored listview headers

07 Dec 2021, 09:02

I've noticed the on-mouse-hover effects disappeared, I guess due to CDRF_SKIPDEFAULT (they work on CDRF_NEWFONT, but then obviously that only applies the font formatting, not the rest)?
Is it possible to pass another code that would restore them or since now this subclass completely overtakes the drawing of the header it also has to catch on-hover messages and handle them separately by defining its own on-hover style?
Thanks
User avatar
KruschenZ
Posts: 45
Joined: 20 Jan 2021, 07:05
Location: Germany (Rheinhessen)
Contact:

Re: colored listview headers

07 Dec 2021, 09:08

just me wrote:
07 Dec 2021, 08:00
If you want to draw more than one edge you can use something like the following:

Code: Select all

      L := NumGet(RC,  0, "Int") ; Left
      T := NumGet(RC,  4, "Int") ; Top
      R := NumGet(RC,  8, "Int") ; Right
      B := NumGet(RC, 12, "Int") ; Bottom
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, L, T, L, B), "Int", 2) ; left edge
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, L, T, R, T), "Int", 2) ; top edge
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, R, T, R, B), "Int", 2) ; right edge
      DllCall("Polyline", "Ptr", HDC, "Ptr", SetRect(RCL, L, B - 1, R, B - 1), "Int", 2) ; bottom edge (subtract 1 to prevent overdrawing by the first list-view item)

...
SetRect(ByRef RC, L := 0, T := 0, R := 0, B := 0) {
   VarSetCapacity(RC, 16, 0)
   , NumPut(L, RC,  0, "Int")
   , NumPut(T, RC,  4, "Int")
   , NumPut(R, RC,  8, "Int")
   , NumPut(B, RC, 12, "Int")
   Return &RC
}
Hey :-)

okay, now I think I understand...

> SetRect(RCL, 1x, 1y, 2x, 2y)
> 1. Start-Point: 1x, 1y
> 2. End-Point: 2x, 2y
>> Draw the line between this two points.

very helpfull! :clap:

Thank you so much for example (solution) and explain :thumbup:

And for the other "problem" I write later / maybe tomorrow.

Edit:
Here the test code for reproduce

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.



	GLOBAL GUI_Color_FontControl := "009CA6", GUI_Color_BGControl := "0A0A0A"

	Gui, Add, Text,,`n Test ListView
	Gui, Add, ListView, r5 w400 vLV1 hwndhListView +AltSubmit +LV0x4000 r5, Title 1|Title 2|Title 3|Title 4|Title 5|Title 6
	gosub IconList


	;Set: Color for ListView Header
		Func_GUI_Control_Subclass(hListView, "Func_ListView_Header_CustomDraw")


	LV_Add("Icon1", "blabla", "1", "11", "abc abc abc abc abc abc abc abc abc abc")
	LV_Add("Icon2", "test", "2", "22",,"long text here, or there? .... blubb")
	LV_Add("Icon3", "nngg", "3", "33")
	LV_Add(, "343434", "4", "44")


	Gui, Show
	Return


GuiClose:
	ExitApp


IconList:
	LV_ImageList := IL_Create(, 1)
	, LV_SetImageList(LV_ImageList)

	Loop, 10
	{
		IL_Add(LV_ImageList, "Shell32.DLL", A_Index)
	}
	
	Return
	


; ----------------------------------------------------------------------------------------------------


Func_ListView_Header_CustomDraw(H, M, W, L, IdSubclass, RefData)
{
	;https:;www.autohotkey.com/boards/viewtopic.php?style=17&t=87318
	;by just me 07.03.2021
	
	Global GUI_Color_FontControl, GUI_Color_BGControl
	
	Static DC_Brush := DllCall("GetStockObject", "UInt", 18, "UPtr") ; DC_BRUSH = 18
	, DC_Pen := DllCall("GetStockObject", "UInt", 19, "UPtr") ; DC_PEN = 19
	, HDM_GETITEM := (A_IsUnicode ? 0x120B : 0x1203) ; ? HDM_GETITEMW : HDM_GETITEMA
	, OHWND := 0
	, OCode := (2 * A_PtrSize)
	, ODrawStage := OCode + A_PtrSize
	, OHDC := ODrawStage + A_PtrSize
	, ORect := OHDC + A_PtrSize
	, OItemSpec := ORect + 16
	, OItemState := OItemSpec + A_PtrSize
	, LM := 4 ; left margin of the first column (determined experimentally)
	, TM := 6 ; left and right text margins (determined experimentally)
	, Grid := 1 ; Grid Yes or No
	;, DefGridClr := DllCall("GetSysColor", "Int", 15, "UInt") ; COLOR_3DFACE
	
	
	;
	Critical 1000 ; ?
	;
	
	
	If (M = 0x004E) && (NumGet(L + OCode, "Int") = -12)
	{
		; WM_NOTIFY -> NM_CUSTOMDRAW
		
		
		;GET: Sending control's HWND
			HHD := NumGet(L + OHWND, "UPtr")
		
	  
		;Note: It's BGR instead of RGB!
			RegExMatch(GUI_Color_BGControl, "O)(.{0,2})(.{0,2})(.{0,2})", Dummy_Value)
			, GUI_Color_BG := "0x" Dummy_Value.Value( 3 ) Dummy_Value.Value( 2 ) Dummy_Value.Value( 1 )
			, RegExMatch(GUI_Color_FontControl, "O)(.{0,2})(.{0,2})(.{0,2})", Dummy_Value)
			, GUI_Color_FontNormal := "0x" Dummy_Value.Value( 3 ) Dummy_Value.Value( 2 ) Dummy_Value.Value( 1 )
		
		DrawStage := NumGet(L + ODrawStage, "UInt")
		
		; -------------------------------------------------------------------------------------------------------------
		
		If (DrawStage = 0x00010001)
		{
			; CDDS_ITEMPREPAINT
			
			;GET: The item's text, format and column order
				Item := NumGet(L + OItemSpec, "Ptr")
				, VarSetCapacity(HDITEM, 24 + (6 * A_PtrSize), 0)
				, VarSetCapacity(ItemTxt, 520, 0)
				, NumPut(0x86, HDITEM, "UInt") ; HDI_TEXT (0x02) | HDI_FORMAT (0x04) | HDI_ORDER (0x80)
				, NumPut(&ItemTxt, HDITEM, 8, "Ptr")
				, NumPut(260, HDITEM, 8 + (2 * A_PtrSize), "Int")
				, DllCall("SendMessage", "Ptr", HHD, "UInt", HDM_GETITEM, "Ptr", Item, "Ptr", &HDITEM)
				, VarSetCapacity(ItemTxt, -1)
				, Fmt := NumGet(HDITEM, 12 + (2 * A_PtrSize), "UInt") & 3
				, Order := NumGet(HDITEM, 20 + (3 * A_PtrSize), "Int")
			  
			;GET: The device context
				HDC := NumGet(L + OHDC, "Ptr")
			
			;Draw: A solid rectangle for the background
				VarSetCapacity(RC, 16, 0)
				, DllCall("CopyRect", "Ptr", &RC, "Ptr", L + ORect)
				, NumPut(NumGet(RC, "Int") + (!(Item | Order) ? LM : 0), RC, "Int")
				, NumPut(NumGet(RC, 8, "Int") + 1, RC, 8, "Int")
				, DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", GUI_Color_BG)
				, DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)
			
			;Draw: The text
				DllCall("SetBkMode", "Ptr", HDC, "UInt", 0)
				, DllCall("SetTextColor", "Ptr", HDC, "UInt", GUI_Color_FontNormal)
				, DllCall("InflateRect", "Ptr", L + ORect, "Int", -TM, "Int", 0)
			
			; DT_EXTERNALLEADING (0x0200) | DT_SINGLELINE (0x20) | DT_VCENTER (0x04)
			; HDF_LEFT (0) -> DT_LEFT (0)
			; HDF_CENTER (2) -> DT_CENTER (1)
			; HDF_RIGHT (1) -> DT_RIGHT (2)
				DT_ALIGN := 0x0224 + ((Fmt & 1) ? 2 : (Fmt & 2) ? 1 : 0)
				, DllCall("DrawText", "Ptr", HDC, "Ptr", &ItemTxt, "Int", -1, "Ptr", L + ORect, "UInt", DT_ALIGN)
				
			
			;Draw: A 'Grid' Line
				If (Grid) && (Order)
				{
					DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
					, DllCall("SetDCPenColor", "Ptr", HDC, "UInt", GUI_Color_FontNormal)
					
					
					/*
					, L := NumGet(RC,  0, "Int") ; Left
					, T := NumGet(RC,  4, "Int") ; Top
					, R := NumGet(RC,  8, "Int") ; Right
					, B := NumGet(RC, 12, "Int") ; Bottom
					*/
					
					
					;Left
						, DllCall("Polyline", "Ptr", HDC, "Ptr", Func_SetRect( RCL, NumGet(RC,  0, "Int"), NumGet(RC,  4, "Int"), NumGet(RC,  0, "Int"), NumGet(RC, 12, "Int") ), "Int", 2)
					
					;Top
						, DllCall("Polyline", "Ptr", HDC, "Ptr", Func_SetRect( RCL, NumGet(RC,  0, "Int"), NumGet(RC,  4, "Int"), NumGet(RC,  8, "Int"), NumGet(RC, 4, "Int") ), "Int", 2)
					
					;Bottom
						, DllCall("Polyline", "Ptr", HDC, "Ptr", Func_SetRect( RCL, NumGet(RC,  0, "Int"), NumGet(RC, 12, "Int") - 1, NumGet(RC, 8, "Int"), NumGet(RC, 12, "Int") - 1 ), "Int", 2)
				}
			
			
			Return 4 ; CDRF_SKIPDEFAULT
		}
		
		; -------------------------------------------------------------------------------------------------------------
		
		If (DrawStage = 1)
		{
			; CDDS_PREPAINT
			Return 0x30 ; CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT
		}
		
		; -------------------------------------------------------------------------------------------------------------
		
		
		If (DrawStage = 2)
		{
			; CDDS_POSTPAINT
			
			VarSetCapacity(RC, 16, 0)
			, DllCall("GetClientRect", "Ptr", HHD, "Ptr", &RC, "UInt")
			, Cnt := DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1200, "Ptr", 0, "Ptr", 0, "Int") ; HDM_GETITEMCOUNT
			, VarSetCapacity(RCI, 16, 0)
			, DllCall("SendMessage", "Ptr", HHD, "UInt", 0x1207, "Ptr", Cnt - 1, "Ptr", &RCI) ; HDM_GETITEMRECT
			, R1 := NumGet(RC, 8, "Int")
			, R2 := NumGet(RCI, 8, "Int")
			
			If (R2 < R1)
			{
				
				;Conflict: with LVS_EX_LABELTIP LV0x4000 > shows only the background without text
				
				HDC := NumGet(L + OHDC, "UPtr")
				, NumPut(R2, RC, 0, "Int")
				, DllCall("SetDCBrushColor", "Ptr", HDC, "UInt", GUI_Color_BG)
				, DllCall("FillRect", "Ptr", HDC, "Ptr", &RC, "Ptr", DC_Brush)
				
				
				If (Grid)
				{
					DllCall("SelectObject", "Ptr", HDC, "Ptr", DC_Pen, "UPtr")
					, DllCall("SetDCPenColor", "Ptr", HDC, "UInt", GUI_Color_FontNormal)
					, NumPut(NumGet(RC, 0, "Int"), RC, 8, "Int")
					, DllCall("Polyline", "Ptr", HDC, "Ptr", &RC, "Int", 2)
				}
				
			}
			
			Return 4 ; CDRF_SKIPDEFAULT
		}
		
		
		; All other drawing stages ------------------------------------------------------------------------------------
		Return 0 ; CDRF_DODEFAULT
	}
	Else If (M = 0x0002)
	{
		; WM_DESTROY
		Func_GUI_Control_Subclass(H, "") ; remove the subclass procedure
	}
	
	
	; All messages not completely handled by the function must be passed to the DefSubclassProc:
	Return DllCall("DefSubclassProc", "Ptr", H, "UInt", M, "Ptr", W, "Ptr", L, "Ptr")
}


Func_SetRect(ByRef RC, L := 0, T := 0, R := 0, B := 0)
{
	VarSetCapacity(RC, 16, 0)
	, NumPut(L, RC,  0, "Int")
	, NumPut(T, RC,  4, "Int")
	, NumPut(R, RC,  8, "Int")
	, NumPut(B, RC, 12, "Int")
	
	Return &RC
}


Func_GUI_Control_Subclass(HCTL, FuncName, Data := 0)
{
	; ======================================================================================================================
	; SubclassControl	 Installs, updates, or removes the subclass callback for the specified control.
	; Parameters:		  HCTL	  -  Handle to the control.
	;						  FuncName -  Name of the callback function as string.
	;										  If you pass an empty string, the subclass callback will be removed.
	;						  Data	  -  Optional integer value passed as dwRefData to the callback function.
	; Return value:		Non-zero if the subclass callback was successfully installed, updated, or removed;
	;						  otherwise, False.
	; Remarks:			  The callback function must have exactly six parameters, see
	;						  SUBCLASSPROC -> msdn.microsoft.com/en-us/library/bb776774(v=vs.85).aspx
	; MSDN:				  Subclassing Controls -> msdn.microsoft.com/en-us/library/bb773183(v=vs.85).aspx
	; ======================================================================================================================
	
	Static ControlCB := []
	
	If ControlCB.HasKey(HCTL)
	{
		DllCall("RemoveWindowSubclass", "Ptr", HCTL, "Ptr", ControlCB[ HCTL ], "Ptr", HCTL)
		, DllCall("GlobalFree", "Ptr", ControlCB[ HCTL ], "Ptr")
		, ControlCB.Delete(HCTL)
		
		If (FuncName = "")
		{
			Return True
		}
	}
	
	If !DllCall("IsWindow", "Ptr", HCTL, "UInt")
	|| !IsFunc(FuncName) || (Func(FuncName).MaxParams <> 6)
	|| !(CB := RegisterCallback(FuncName, , 6))
		Return False
	
	If !DllCall("SetWindowSubclass", "Ptr", HCTL, "Ptr", CB, "Ptr", HCTL, "Ptr", Data)
	{
		Return (DllCall("GlobalFree", "Ptr", CB, "Ptr") & 0)
	}
	
	Return (ControlCB[ HCTL ] := CB)
}
OS: Windows 10: Version 21H1 (Build 19043.1348)
grafik.png
grafik.png (8.12 KiB) Viewed 3883 times
Many regards :salute:
KruschenZ

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Anput, mikeyww and 324 guests