tatane

Icon transparency in Listview Header (bug ?) + centered icon in subitem column

10 posts in this topic

#1 ·  Posted (edited)

Hi,

I was trying to add some icons in listview header and I discovered that transparency was not handled correctly.

This first example shows the problem :

#include <GuiImageList.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>

GUICreate("Transparent icon on Listview header - bug ?")
$Listview = GUICtrlCreateListView("", 5, 5, 300, 300, BitOR($LVS_NOSORTHEADER, $LVS_SHOWSELALWAYS, $LVS_REPORT))
$h_listview = GUICtrlGetHandle($Listview)
_GUICtrlListView_SetExtendedListViewStyle($h_listview, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_SUBITEMIMAGES, $LVS_EX_GRIDLINES))

GUISetState()

_GUICtrlListView_SetBkColor($h_listview, $CLR_MONEYGREEN)

$hImagelist = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist, "shell32.dll", 10)
_GUIImageList_AddIcon($hImagelist, "shell32.dll", 5)
_GUIImageList_AddIcon($hImagelist, "shell32.dll", 25)
_GUICtrlListView_SetImageList($h_listview, $hImagelist, 1)


; add 2 columns with icon
_GUICtrlListView_AddColumn($h_listview, "column1", 140, -1, 1)
_GUICtrlListView_AddColumn($h_listview, "column2", 140, -1, 2)


_GUICtrlListView_AddItem($h_listview, "row1") ; no image index is equal to '0' : BUG ?
_GUICtrlListView_AddItem($h_listview, "row2", 0) ; image from $hImagelist index 0
_GUICtrlListView_AddItem($h_listview, "row3", 4) ; wrong index is equal to no icon

While 1
    If GUIGetMsg() = $GUI_EVENT_CLOSE Then _Exit()
    Sleep(10)
WEnd

Func _Exit()
    Exit
EndFunc

As you can see, the listview background color appears through the transparency of the icon. It passes through the header background. (answer in this >Post)

I found a workaround by seting icons with _GUICtrlHeader_SetItemImage() and 2 images lists.

here is the code :

#include <GuiImageList.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>

GUICreate("Transparent icon on Listview header")
$Listview = GUICtrlCreateListView("", 5, 5, 300, 300, BitOR($LVS_NOSORTHEADER, $LVS_SHOWSELALWAYS, $LVS_REPORT))
$h_listview = GUICtrlGetHandle($Listview)
_GUICtrlListView_SetExtendedListViewStyle($h_listview, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_SUBITEMIMAGES, $LVS_EX_GRIDLINES))

GUISetState()

_GUICtrlListView_SetBkColor($h_listview, $CLR_MONEYGREEN)

$hHeader = _GUICtrlListView_GetHeader($h_listview) ; HWnd(GUICtrlSendMsg($LV_Ordi_Gui_VNC, $LVM_GETHEADER, 0, 0))

; ****** this list first, otherwise all icons are identical ??? *******
; ****** this list is created for the inside of listview **************
$hImagelist_LV = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist_LV, "shell32.dll", 10)
_GUICtrlListView_SetImageList($h_listview, $hImagelist_LV, 1)
;**********************************************************************

; ****** this list is created for the columns header ******
$hImagelist_header = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist_header, "shell32.dll", 5)
_GUIImageList_AddIcon($hImagelist_header, "shell32.dll", 25)
_GUICtrlHeader_SetImageList($hHeader, $hImagelist_header)
;**********************************************************

; add 2 columns without icon
_GUICtrlListView_AddColumn($h_listview, "column1", 140)
_GUICtrlListView_AddColumn($h_listview, "column2", 140)

; set icons to column header
_GUICtrlHeader_SetItemImage($hHeader, 0, 0)
_GUICtrlHeader_SetItemImage($hHeader, 1, 1)


_GUICtrlListView_AddItem($h_listview, "row1") ; no image index is equal to '0' : BUG ?
_GUICtrlListView_AddItem($h_listview, "row2", 0) ; image from $hImagelist_LV index 0
_GUICtrlListView_AddItem($h_listview, "row3", 4) ; wrong index is equal to no icon

While 1
    If GUIGetMsg() = $GUI_EVENT_CLOSE Then _Exit()
    Sleep(10)
WEnd

Func _Exit()
    Exit
EndFunc

If someone can explain to me what's going on, I will be very grateful :)

Thanks.

P.S : There are some strange things with the 2 image lists implement order and _GUICtrlListView_AddItem() image setting too

P.S 2 : Is there a way to center an image/icon in a subitem ? WM_NOTIFY and CustomDraw ? (answer in next Post)

Edited by tatane

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

I will answer to my last question.
Here is the code for the WM_NOTIFY

Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $tInfo

    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iIDFrom = DllStructGetData($tNMHDR, "IDFrom")
    $iCode = DllStructGetData($tNMHDR, "Code")

    Switch $hWndFrom
        Case $hWnd_Listview
            Switch $iCode
                ; http://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx
                ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb775489(v=vs.85).aspx
                Case $NM_CUSTOMDRAW
                    Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $ilParam)
                    Local $iDrawStage = DllStructGetData($tCustDraw, "dwDrawStage")
                    Local $iSubItem = DllStructGetData($tCustDraw, "iSubItem")
                    Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec")

                    Switch $iDrawStage
                        ; first step
                        Case $CDDS_PREPAINT
                            Return $CDRF_NOTIFYITEMDRAW

                        ; second step (full item edition)
                        Case $CDDS_ITEMPREPAINT
                            Return $CDRF_NOTIFYSUBITEMDRAW
    
                        ; third step (subitem edition)
                        Case BitOR($CDDS_ITEMPREPAINT, $CDDS_SUBITEM)
                            If $iSubItem = 2 Then

                                Local $hDC = _WinAPI_GetDC($hWndFrom)
                                Local $rect_array = _GUICtrlListView_GetSubItemRect($hWnd_Listview, $iItem, 2, $iSubitem - 1)
                                Local $hIcon = _GUIImageList_GetIcon($hImagelist, _GUICtrlListView_GetItemImage($hWnd_Listview, $iItem, $iSubitem))

                                Local $rect_width = $rect_array[2] - $rect_array[0]
                                Local $new_x = $rect_array[0] + ($rect_width / 2) - 8

                                _WinAPI_DrawIconEx($hDC, $new_x, $rect_array[1], $hIcon, 16, 16)

                                _GUIImageList_DestroyIcon($hIcon)
                                _WinAPI_ReleaseDC($hWndFrom, $hDC)

                                Return $CDRF_SKIPDEFAULT
                            EndIf

                            Return $CDRF_NEWFONT
                    EndSwitch
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

I've got a new problem...
If I handle $NM_DBLCLK notification message, the icon previously painted by _WinAPI_DrawIconEx() disappears when I double click on an item and goes back as soon as the item is redrawn.
 

Example :

#include <GuiImageList.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>

GUICreate("Transparent icon on Listview header")
$Listview = GUICtrlCreateListView("", 5, 5, 300, 300, BitOR($LVS_NOSORTHEADER, $LVS_SHOWSELALWAYS, $LVS_REPORT))
$h_listview = GUICtrlGetHandle($Listview)
_GUICtrlListView_SetExtendedListViewStyle($h_listview, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_SUBITEMIMAGES, $LVS_EX_GRIDLINES))

GUISetState()

_GUICtrlListView_SetBkColor($h_listview, $CLR_MONEYGREEN)

$hHeader = _GUICtrlListView_GetHeader($h_listview) ; HWnd(GUICtrlSendMsg($LV_Ordi_Gui_VNC, $LVM_GETHEADER, 0, 0))

; ****** this list first, otherwise all icons are identical ??? *******
; ****** this list is created for the inside of listview **************
$hImagelist_LV = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist_LV, "shell32.dll", 10)
_GUICtrlListView_SetImageList($h_listview, $hImagelist_LV, 1)
;**********************************************************************

; ****** this list is created for the columns header ******
$hImagelist_header = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist_header, "shell32.dll", 5)
_GUIImageList_AddIcon($hImagelist_header, "shell32.dll", 25)
_GUICtrlHeader_SetImageList($hHeader, $hImagelist_header)
;**********************************************************

; add 2 columns without icon
_GUICtrlListView_AddColumn($h_listview, "column1", 80)
_GUICtrlListView_AddColumn($h_listview, "", 80, 2)
_GUICtrlListView_AddColumn($h_listview, "colonne3", 80)

; set icons to column header
_GUICtrlHeader_SetItemImage($hHeader, 0, 0)
_GUICtrlHeader_SetItemImage($hHeader, 1, 1)


_GUICtrlListView_AddItem($h_listview, "row1") ; no image index is equal to '0' : BUG ?
_GUICtrlListView_AddItem($h_listview, "row2", 0) ; image from $hImagelist_LV index 0
_GUICtrlListView_AddItem($h_listview, "row3", 4) ; wrong index is equal to no icon

_GUICtrlListView_AddSubItem($h_listview, 0, "colonne1", 1)
_GUICtrlListView_AddSubItem($h_listview, 1, "colonne2", 1)
_GUICtrlListView_AddSubItem($h_listview, 2, "colonne3", 1)

_GUICtrlListView_AddSubItem($h_listview, 0, "last1", 2)
_GUICtrlListView_AddSubItem($h_listview, 1, "last2", 2)
_GUICtrlListView_AddSubItem($h_listview, 2, "", 2)
GUICtrlCreateListViewItem("row4|col1|", $Listview)

_GUICtrlListView_SetItemImage($h_listview, 2, 0, 1)
;~ _GUICtrlListView_AddSubItem($h_listview, 2, "", 1, 0)

GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

_GUICtrlListView_RedrawItems($h_listview, 0, 2)

While 1
    If GUIGetMsg() = $GUI_EVENT_CLOSE Then _Exit()
    Sleep(10)
WEnd

Func _Exit()
    Exit
EndFunc

Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $tInfo

    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iIDFrom = DllStructGetData($tNMHDR, "IDFrom")
    $iCode = DllStructGetData($tNMHDR, "Code")

    Switch $hWndFrom
        Case $h_listview
            Switch $iCode
                Case $NM_DBLCLK
                    MsgBox(0, "test", "")

                ; http://msdn.microsoft.com/en-us/library/windows/desktop/ff919573%28v=vs.85%29.aspx
                Case $NM_CUSTOMDRAW
                    Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $ilParam)
                    Local $iDrawStage = DllStructGetData($tCustDraw, "dwDrawStage")
                    Local $iSubItem = DllStructGetData($tCustDraw, "iSubItem")
                    Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec")

                    Local $hDC = _WinAPI_GetDC($hWndFrom)
                    Local $tRect = DllStructCreate($tagRECT)
                    Local $pRect = DllStructGetPtr($tRect)


                    Switch $iDrawStage
                        Case $CDDS_PREPAINT
                            Return $CDRF_NOTIFYITEMDRAW

                        Case $CDDS_ITEMPREPAINT
                            Return $CDRF_NOTIFYSUBITEMDRAW

                        Case BitOR($CDDS_ITEMPREPAINT, $CDDS_SUBITEM)
                            If ($iItem = 2 And $iSubItem = 2) Or ($iItem = 3 And $iSubItem = 2) Then
                                Local $rect_array = _GUICtrlListView_GetSubItemRect($h_listview, $iItem, $iSubItem, 0)

                                Local $hIcon = _GUIImageList_GetIcon($hImagelist_LV, 0)

                                Local $rect_width = $rect_array[2] - $rect_array[0]
                                Local $new_x = $rect_array[0] + ($rect_width / 2) - 8

                                _WinAPI_DrawIconEx($hDC, $new_x, $rect_array[1], $hIcon, 16, 16)

                                _WinAPI_ReleaseDC($hWndFrom, $hDC)
                                Return $CDRF_SKIPDEFAULT
                            EndIf

                    EndSwitch
            EndSwitch
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY
Edited by tatane

Share this post


Link to post
Share on other sites

To get rid of the green background for the icons in the header in first codebox in first post you can probably just add

 

_GUIImageList_SetBkColor( $hImageList, $CLR_NONE )

Share this post


Link to post
Share on other sites

Thanks ! This works great. It seems this line have to be placed after the GUICtrlListView_SetImageList().

Share this post


Link to post
Share on other sites

Post 2. Isn't that just because the function you are executing for a double click is blocking the CustomDraw messages? Have you tried to use a dummy control in the main message loop, and only send a message to the dummy control on a double click event. Then the CustomDraw messages will not be blocked.

Share this post


Link to post
Share on other sites

Actually, when the function is triggered by double click, I disable my main windows and launch 1 or more external programs (vncviewer.exe) with the Run() function. When all of them are open I reactivate the main window. That's all.

I will retry tomorow but if I remember well, there is no notification message coming from the listview when I double click the -already selected- item and before the start of my function (the problem is the same if the item is not selected when I double click on it).

It's strange. If the icon disappears, doesn't that mean a repaint message was sent ?

Sorry I don't understand what you mean with the use of DummyControl. If I send a message to the dummy how can it repaint my icon in the listview ? Or should I add a Case statement in WM_Notify with the handle of the dummycontrol ?

Share this post


Link to post
Share on other sites

Like this:

#include <GuiImageList.au3>
#include <GuiListView.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>

GUICreate("Transparent icon on Listview header")
Global $idLvDblClickEvent = GUICtrlCreateDummy()
$Listview = GUICtrlCreateListView("", 5, 5, 300, 300, BitOR($LVS_NOSORTHEADER, $LVS_SHOWSELALWAYS, $LVS_REPORT))
$h_listview = GUICtrlGetHandle($Listview)
_GUICtrlListView_SetExtendedListViewStyle($h_listview, BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_SUBITEMIMAGES, $LVS_EX_GRIDLINES))

GUISetState()

_GUICtrlListView_SetBkColor($h_listview, $CLR_MONEYGREEN)

$hHeader = _GUICtrlListView_GetHeader($h_listview) ; HWnd(GUICtrlSendMsg($LV_Ordi_Gui_VNC, $LVM_GETHEADER, 0, 0))

; ****** this list first, otherwise all icons are identical ??? *******
; ****** this list is created for the inside of listview **************
$hImagelist_LV = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist_LV, "shell32.dll", 10)
_GUICtrlListView_SetImageList($h_listview, $hImagelist_LV, 1)
;**********************************************************************

; ****** this list is created for the columns header ******
$hImagelist_header = _GUIImageList_Create(16, 16, 5, 1)
_GUIImageList_AddIcon($hImagelist_header, "shell32.dll", 5)
_GUIImageList_AddIcon($hImagelist_header, "shell32.dll", 25)
_GUICtrlHeader_SetImageList($hHeader, $hImagelist_header)
;**********************************************************

; add 2 columns without icon
_GUICtrlListView_AddColumn($h_listview, "column1", 80)
_GUICtrlListView_AddColumn($h_listview, "", 80, 2)
_GUICtrlListView_AddColumn($h_listview, "colonne3", 80)

; set icons to column header
_GUICtrlHeader_SetItemImage($hHeader, 0, 0)
_GUICtrlHeader_SetItemImage($hHeader, 1, 1)


_GUICtrlListView_AddItem($h_listview, "row1") ; no image index is equal to '0' : BUG ?
_GUICtrlListView_AddItem($h_listview, "row2", 0) ; image from $hImagelist_LV index 0
_GUICtrlListView_AddItem($h_listview, "row3", 4) ; wrong index is equal to no icon

_GUICtrlListView_AddSubItem($h_listview, 0, "colonne1", 1)
_GUICtrlListView_AddSubItem($h_listview, 1, "colonne2", 1)
_GUICtrlListView_AddSubItem($h_listview, 2, "colonne3", 1)

_GUICtrlListView_AddSubItem($h_listview, 0, "last1", 2)
_GUICtrlListView_AddSubItem($h_listview, 1, "last2", 2)
_GUICtrlListView_AddSubItem($h_listview, 2, "", 2)
GUICtrlCreateListViewItem("row4|col1|", $Listview)

_GUICtrlListView_SetItemImage($h_listview, 2, 0, 1)
;~ _GUICtrlListView_AddSubItem($h_listview, 2, "", 1, 0)

GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")

_GUICtrlListView_RedrawItems($h_listview, 0, 3)

While 1
    Switch GUIGetMsg()
      Case $idLvDblClickEvent
        MsgBox(0, "test", "")
      Case $GUI_EVENT_CLOSE
        _Exit()
    EndSwitch
WEnd

Func _Exit()
    Exit
EndFunc

Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $tInfo

    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iIDFrom = DllStructGetData($tNMHDR, "IDFrom")
    $iCode = DllStructGetData($tNMHDR, "Code")

    Switch $hWndFrom
        Case $h_listview
            Switch $iCode
                Case $NM_DBLCLK
                    ;MsgBox(0, "test", "")
                    GUICtrlSendToDummy( $idLvDblClickEvent )

                ; http://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx
                Case $NM_CUSTOMDRAW
                    Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $ilParam)
                    Local $iDrawStage = DllStructGetData($tCustDraw, "dwDrawStage")
                    Local $iSubItem = DllStructGetData($tCustDraw, "iSubItem")
                    Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec")

                    Local $hDC = _WinAPI_GetDC($hWndFrom)
                    Local $tRect = DllStructCreate($tagRECT)
                    Local $pRect = DllStructGetPtr($tRect)


                    Switch $iDrawStage
                        Case $CDDS_PREPAINT
                            Return $CDRF_NOTIFYITEMDRAW

                        Case $CDDS_ITEMPREPAINT
                            Return $CDRF_NOTIFYSUBITEMDRAW

                        Case BitOR($CDDS_ITEMPREPAINT, $CDDS_SUBITEM)
                            If ($iItem = 2 And $iSubItem = 2) Or ($iItem = 3 And $iSubItem = 2) Then
                                Local $rect_array = _GUICtrlListView_GetSubItemRect($h_listview, $iItem, $iSubItem, 0)

                                Local $hIcon = _GUIImageList_GetIcon($hImagelist_LV, 0)

                                Local $rect_width = $rect_array[2] - $rect_array[0]
                                Local $new_x = $rect_array[0] + ($rect_width / 2) - 8

                                _WinAPI_DrawIconEx($hDC, $new_x, $rect_array[1], $hIcon, 16, 16)

                                _WinAPI_ReleaseDC($hWndFrom, $hDC)
                                Return $CDRF_SKIPDEFAULT
                            EndIf

                    EndSwitch
            EndSwitch
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

Share this post


Link to post
Share on other sites

Thank you very much ! It works perfectly.

But I don't undestand why :sweating: .

Share this post


Link to post
Share on other sites

It's not that hard. In AutoIt there are two kind of messages: AutoIt messages and Windows messages. AutoIt messages are the messages that are handled by the AutoIt message loop. Windows messages are the messages you can catch via GUIRegisterMsg.

A rule is that all Windows messages already queued up e.g. a bunch of CustomDraw notifications, are handled before any AutoIt messages. While these messages are handled the AutoIt message loop is not running.

Another rule is that the Windows messages should not be blocked. This will prevent in first place Windows from working properly, and later probably the entire AutoIt script.

One can easily imagine that if a bunch of CustomDraw notifications, which are used to visually update a row in the listview, are blocked, you'll be able to see some flicker. And you can begin to see this flicker even if the blocking is only 50 to 100 milliseconds.

If the function you are executing on a double click, is running for more than 50 milliseconds, you'll be able to see flicker in the listview, if it's a custom drawn listview.

The cure is to make sure that the code you are running on a double click is very very fast. GUICtrlSendToDummy is probably not running for more than 1 (one) millisecond. This command will not block anything. This seems to be a good candidate.

Let's use GUICtrlSendToDummy to send a message to the AutoIt message loop instead of running the double click function directly. So far the AutoIt message loop is still blocked. In first place the GUICtrlSendToDummy message is just queued up in the AutoIt message queue.

Now all the listview CustomDraw notifications can be handled without being blocked, and the row in the listview will be updated nice and smooth without any flicker.

When there are no more Windows messages (CustomDraw notifications), the AutoIt message loop will start running again. There can be several messages in the queue. GUIGetMsg reads the messages one by one in proper order (first in first out).

At some time GUIGetMsg will read the GUICtrlSendToDummy message. This message matches the corresponding dummy control, and the code for the control will run. This code executes the listview double click function. At this point not even a MsgBox has any consequences for the listview. The listview has finished updating long time ago.

Comprendo?

Share this post


Link to post
Share on other sites

#10 ·  Posted (edited)

Very well explained :)  ! Now I better understand the message internal mechanic.

Thank you very much for your time.

Edited by tatane

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