Jump to content
Sign in to follow this  
footswitch

ListView row-by-row coloring (on-the-fly and with ListView UDF)

Recommended Posts

footswitch

Hi there,

Since finding I've been experimenting with ListView customized, row-by-row coloring.

I hope the example below - which took me a while to extract from my script - can be of help to someone in the future.

Still, I have some questions / unresolved issues that lie over additional aspects of the pursued customization.

I've commented stuff around in order to get some logic out of it, but some things are currently beyond my knowledge / comprehension.

So, if someone could lend a hand, I'd be most grateful.

Problems / Questions as explained in the example:

PROBLEM #1: When a subitem is changed, the row isn't fully redrawn (only that subitem is) EDIT: Use _GUICtrlListView_RedrawItems($hWnd, $iFirst, $iLast)

PROBLEM #2: Alignment doesn't work as expected for the first item of each row (the item text is centered horizontally in relation to the entire width of the ListView)

QUESTION #1: How to paint the background of subitems individually? EDIT: check post below

Questions not in the example:

1. How to enable click on column header for sorting the items, with the little arrow included in the header? Can't seem to make it work... Please note: my ListView starts empty with no headers, and the items are loaded/changed/deleted several times.

2. How to customize the item's text color? Would it be possible to set the text color for a single item/subitem? EDIT: check post below

3. Checkboxes with ownerdraw: is there an easy workaround? (this one isn't critical for my current needs, but could be useful in the future) EDIT: check post below

Here's the example script:

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

Global Const $ODT_LISTVIEW = 102

Global Const $ODA_DRAWENTIRE = 0x1
Global Const $ODA_SELECT = 0x2
Global Const $ODA_FOCUS = 0x4

Global Const $ODS_SELECTED = 0x0001

$hGUI = GUICreate("Test GUI", 400, 300)

; not using _GUICtrlListView_Create() because the ListView is inside a Tab Item
; (using GUICtrlCreateListView() shows/hides automatically when changing between Tab Items).
; Also in WM_DRAWITEM we look for the ControlID, not the Window Handle
$hGUI_listview = GUICtrlCreateListView( "Items|SubItems|Column index 2", 10, 10, 280, 180, _
    $LVS_REPORT + $LVS_SINGLESEL + $LVS_SHOWSELALWAYS + $LVS_OWNERDRAWFIXED, _ ; control styles
    $LVS_EX_FULLROWSELECT + $LVS_EX_GRIDLINES) ; control extended styles; + $LVS_EX_CHECKBOXES doesn't work with OWNERDRAW...?

GUIRegisterMsg($WM_DRAWITEM, "WM_DRAWITEM")

GUISetState()


For $i = 1 To 150
    _GUICtrlListView_AddItem($hGUI_listview, "Item " & $i)
    _GUICtrlListView_AddSubItem($hGUI_listview, $i - 1, "Random " & Random(0,$i,1), 1)
    _GUICtrlListView_AddSubItem($hGUI_listview, $i - 1, $i, 2)
Next

; PROBLEM #1 : change some sub-items, as you can see they're not fully redrawn.
; Not until you force it manually by hiding and then showing:
; scroll down/up, minimize/restore, move the window outside of the screen area...).
Sleep(2000)
_GUICtrlListView_SetItemText($hGUI_listview,0,"4",2)
Sleep(2000)
_GUICtrlListView_SetItemText($hGUI_listview,5,"4",2)
Sleep(2000)
_GUICtrlListView_SetItemText($hGUI_listview,7,"4",2)
; ==>> END OF PROBLEM #1 ----------------------------------------------------------

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE


#region GUI Owner Draw Callback

Func __WM_DRAWITEM_GetColor($hListView_local,$itmID)
    ; internal function that I use to determine the color for the row.
    ; let's pretend these are the calculations.
    ; don't forget, this is BGR, not RGB.

    Local $aGetItem
    $aGetItem = _GUICtrlListView_GetItem($hListView_local,$itmID,2) ; Gets info about the last subitem of the row
    If UBound($aGetItem)>3 Then ; just a safety measure to prevent script error
        If $aGetItem[3]==4 Then ; if the text of last subitem in this row is = 4
            Return 0xDDDDDD ; light grey
        EndIf
    EndIf

    If IsInt($itmID/2) Then ; for instance we're coloring the odd rows light green ($itmID is a zero based index)
        Return 0xAAEEAA ; light green
    Else
        Return 0xFFFFFF ; white
    EndIf
EndFunc


Func WM_DRAWITEM($hWnd, $Msg, $wParam, $lParam)
    Local $tagDRAWITEMSTRUCT, $iBrushColor, $cID, $itmID, $itmAction, $itmState, $hItm, $hDC, $bSelected

    $tagDRAWITEMSTRUCT = DllStructCreate( _
        "uint cType;" & _ ; ?
        "uint cID;" & _ ; ControlID of the ListView
        "uint itmID;" & _ ; ListView item index
        "uint itmAction;" & _ ; ?
        "uint itmState;" & _ ; ? with the BitAND operation below, allows seeing if the item is selected
        "hwnd hItm;" & _ ; ?
        "hwnd hDC;" & _ ; ?
        "int itmRect[4];" & _ ; Item rectangle coordinates (entire row)
        "dword itmData" _ ; ?
        , $lParam) ; is this the ListView handle?

    If DllStructGetData($tagDRAWITEMSTRUCT, "cType") <> $ODT_LISTVIEW Then Return $GUI_RUNDEFMSG

    $cID = DllStructGetData($tagDRAWITEMSTRUCT, "cID")
    $itmID = DllStructGetData($tagDRAWITEMSTRUCT, "itmID")
    $itmAction = DllStructGetData($tagDRAWITEMSTRUCT, "itmAction")
    $itmState = DllStructGetData($tagDRAWITEMSTRUCT, "itmState")
    $hItm = DllStructGetData($tagDRAWITEMSTRUCT, "hItm")
    $hDC = DllStructGetData($tagDRAWITEMSTRUCT, "hDC")

    $bSelected = BitAND($itmState, $ODS_SELECTED)
    Switch $cID ; see which ControlID is requesting OWNERDRAW
        Case $hGUI_listview ; (there's more than one listview in the original GUI)
            ; if you want all the ListViews with the same formatting, just comment out the parent Switch-Case-EndSwitch
            Switch $itmAction
                Case $ODA_DRAWENTIRE
                    ConsoleWrite("Drawing Item "&$itmID+1&@CRLF) ; debugging
                    ; don't forget, this is BGR, not RGB
                    If $itmState = $bSelected Then ; item is NOT selected
                        $iBrushColor = __WM_DRAWITEM_GetColor($hGUI_listview,$itmID)
                    Else ; item is selected (when an item is selected, always force this color)
                        $iBrushColor = 0xFFCCAA ; light blue
                    EndIf

                    ; QUESTION #1 : paints the whole item with a single color. -----------
                    ; how to paint different subitems with different colors? -------------
                    Local $aBrush = DLLCall("gdi32.dll","hwnd","CreateSolidBrush", "int", $iBrushColor)

                    Local $aBrushOld = _WinAPI_SelectObject($hDC, $aBrush[0])

                    Local $iLeft = DllStructGetData($tagDRAWITEMSTRUCT, "itmRect", 1)
                    DllStructSetData($tagDRAWITEMSTRUCT, "itmRect", $iLeft + 1, 1) ; rectangle coordinates for coloring
                    ; +1 is the left margin

                    _WinAPI_FillRect($hDC, DllStructGetPtr($tagDRAWITEMSTRUCT, "itmRect"), $aBrush[0])

                    _WinAPI_SelectObject($hDC, $aBrushOld)

                    _WinAPI_DeleteObject($aBrush[0])
                    ; ==>> END OF QUESTION #1 ---------------------------------------------

                    ; draw the respective text for each column of the row
                    $local_alignment=$DT_LEFT ; default text alignment
                    For $i=0 To _GUICtrlListView_GetColumnCount($hGUI_listview)-1
                        ; 1. get subitem text:
                        Local $iSubItmText = _GUICtrlListView_GetItemText($hGUI_listview, $itmID, $i)
                        ; 2. get subitem coordinates for drawing its respective text
                        Local $aSubItmRect = _GUICtrlListView_GetSubItemRect($hGUI_listview, $itmID, $i)
                            ; the function above accepts not only subitems (one-based index), but also main item (index=0)
                        ; 3. pass the coordinates to a DLL struct
                        Local $iSubItmRect = DllStructCreate("int[4]")
                        DllStructSetData($iSubItmRect, 1, $aSubItmRect[0]+6, 1) ; +6 is left margin (X)
                        DllStructSetData($iSubItmRect, 1, $aSubItmRect[1]+3, 2) ; +3 is upper margin (Y)
                        DllStructSetData($iSubItmRect, 1, $aSubItmRect[2], 3)
                        DllStructSetData($iSubItmRect, 1, $aSubItmRect[3], 4)
                        ; 4. draw the subitem text
                        ; PROBLEM #2 --------------------------
                        #cs
                        If $i==0 Then
                            $local_alignment=$DT_CENTER ; not working... it's centering the item in relation to the entire listview.
                            ; don't know why that happens but the main item is always trickier than the subitems
                        Else
                            $local_alignment=$DT_LEFT
                        EndIf
                        #ce
                        ; ==>> END OF PROBLEM #2 --------------
                        DllCall("user32.dll", "int", "DrawText", "hwnd", $hDC, "str", $iSubItmText, "int", StringLen($iSubItmText), _
                            "ptr", DllStructGetPtr($iSubItmRect), "int", $local_alignment)
                    Next
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc
#endregion GUI Owner Draw Callback

Looking forward for any remarks you may have on the subject.

Thanks again for reading.

footswitch

Edited by footswitch

Share this post


Link to post
Share on other sites
Spiff59

I believe intercepting the WM_NOTIFY message and manipulating the ListView from that level is much easier?

Maybe the following example will be of some use:

#include <GuiConstants.au3>
#include <GuiListView.au3>
#include <WindowsConstants.au3>

Global $hListView
Global $Color[6][2] = [[0xFF00FF, 0xFFFFFF],[0x00FFFF, 0x808000],[0x008080, 0xFFFFFF],[0xFFFFFF, 0x000000],[0x0000FF, 0xC0C0C0]]
GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

$Main_GUI = GuiCreate("", 800, 600, -1, -1)
$hListView = GuiCtrlCreateListView("", 10, 10, 780, 580, -1, BitOR($LVS_EX_GRIDLINES, $LVS_EX_DOUBLEBUFFER, $LVS_EX_FULLROWSELECT))
;$hListView = GUICtrlGetHandle($hListView)
_GUICtrlListView_AddColumn($hListView, "TIME", 40)
_GUICtrlListView_AddColumn($hListView, "CHART # ", 70)
_GUICtrlListView_AddColumn($hListView, "PATIENT NAME ", 150)
GUISetState()

For $x = 0 to 4
    _GUICtrlListView_AddItem($hListView, "TIME" & $x)
    _GUICtrlListView_AddSubItem($hListView, $x, "CHART" & $x, 1)
    _GUICtrlListView_AddSubItem($hListView, $x, "PATIENT" & $x, 2)
Next

While 1
    $msg = GUIGetMsg()
    Switch $msg
        Case $GUI_EVENT_CLOSE
                ExitLoop
    EndSwitch
Wend
Exit

;-------------------------------------------------------------------------------
Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    If IsHWnd($hListView) Then
        $hWndListView = $hListView
    Else
        $hWndListView = GUICtrlGetHandle($hListView)
    EndIf
    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iIDFrom = DllStructGetData($tNMHDR, "IDFrom")
    $iCode = DllStructGetData($tNMHDR, "Code")
    Switch $hWndFrom
        Case $hWndListView
            Switch $iCode
;               Case $LVN_ENDSCROLL ; Force redraw of gridlines lost when scrolling
;                   $tNMHDR = DllStructCreate("hwnd hWnd;uint cID;int code", $ilParam)
;                   $hLV = HWnd(DllStructGetData($tNMHDR, "hWnd"))
;                   _WinAPI_InvalidateRect($hLV)
;               Case $NM_DBLCLK ; Process clicks
;                   $tInfo = DllStructCreate($tagNMITEMACTIVATE, $ilParam)
;                   $aHit = _GUICtrlListView_SubItemHitTest($hWndListView) ; $ahit[0] = row clicked, $ahit[1] = column clicked
;                   If $aHit[0] < 0 Then Return $GUI_RUNDEFMSG
;                   Switch $aHit[1]
;                       Case 1
;                           ShellExecute($ScanPgm, Chr(34) & $PatientPath[$aHit[0]] & Chr(34))
;                   EndSwitch
                Case $NM_CUSTOMDRAW
                    Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $ilParam)
                    Local $iDrawStage = DllStructGetData($tCustDraw, "dwDrawStage")
                    If $iDrawStage = $CDDS_PREPAINT Then Return $CDRF_NOTIFYITEMDRAW
                    If $iDrawStage = $CDDS_ITEMPREPAINT Then Return $CDRF_NOTIFYSUBITEMDRAW
                    Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec")
                    Local $iSubItem = DllStructGetData($tCustDraw, "iSubItem")
                    $iColor = 0x000000 ; reset default
                    $iColorBk = 0xFFFFFF ; reset default
                    If $iItem  = 4 Then ; row 4
                        $iColorBk = 0xC0C0C0
                    EndIf
                    If $iSubItem  = 1 Then ; column 1
                        $iColor = 0xFF0000
                    EndIf
                    If $iSubItem  = 2 Then ; column 2
                        $iColor = $Color[$iItem][0]
                        $iColorBk = $Color[$iItem][1]
                    EndIf
                    DllStructSetData($tCustDraw, "clrTextBk", $iColorBk)
                    DllStructSetData($tCustDraw, "clrText", $iColor)
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc

typos

Edited by Spiff59
  • Like 1

Share this post


Link to post
Share on other sites
footswitch

Wow, that looks so much cleaner. Thank you.

This solves QUESTION #1 in the example, and additional Questions 2+3.

However, it raises an issue I didn't have before:

Would you be kind enough to provide an example on how to draw depending on the item state (selected/unselected)?

This is what I've tried inside of your WM_NOTIFY function:

Local $iItemState = DllStructGetData($tCustDraw, "uItemState")
If BitAND($iItemState,$CDIS_SELECTED) Then
; item is selected
; some code
EndIf

But this always returns True.

Also, this isn't the same as an item being clicked (you can also change selection with the keyboard arrows and/or UDF functions).

Thanks again for your input, Spiff59.

footswitch


Share this post


Link to post
Share on other sites

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
Sign in to follow this  

×