Jump to content

Subclassing A Combobox


 Share

Recommended Posts

Hello everyone. I'm very familiar with AutoIt, but not subclassing so I am a bit in over my head.
Apologies for the lengthy post, but I want to give as much detail as I can.

The problem

I'm trying to make a combobox that has a custom look. My understanding is I need to do the following:

  • Create combobox with $CBS_OWNERDRAWVARIABLE style
  • Subclass the GUI for WM_MEASUREITEM & WM_DRAWITEM messages
  • Subclass the combobox for WM_PAINT messages

I've managed to piece together examples from other languages and topics on this forum.
Using GUIRegisterMsg for WM_MEASUREITEM & WM_DRAWITEM seems to work well along with my combobox subclass function.
Much of my understanding comes from this post. how to use all messages in guigetmsg

HOWEVER! I would like to make it into a UDF, so instead of GUIRegisteMsg, I would use _WinAPI_SetWindowSubclass and process the MEASUREITEM & DRAWITEM messages in the subclass function. I've set it up how I think it should work, but I am having an issue with drawitem. When I move the cursor up and down through the items, the items get randomly painted as seen in the image below.

Combobox_bad.jpg

The problem only occurs when using both the window subclass AND combobox subclass:
  - If I comment out my combobox subclass, comment out the window subclass and use GUIRegisterMsg for DRAWITEM, the drawitem looks fine.
  - If I comment out my combobox subclass, comment out GUIRegisterMsg, and uncomment the window subclass, the drawitem looks fine.
  - If I uncomment the combobox subclass, comment out the window subclass, and use GUIRegisterMsg, the drawitem looks fine.

Why does it work properly with GUIRegisterMsg but not with _WinAPI_SetWindowSubclass? This happens even when the combobox subclass function is not doing anything.

 

#include <GUIComboBox.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIGdi.au3>

Global Const $clrWindowText = 0xEEEEEE
Global Const $clrHighlightText = 0xFFFFFF
Global Const $clrHighlight = 0x777777
Global Const $clrWindow = 0x444444

Global $hBrushNorm = _WinAPI_CreateSolidBrush($clrWindow)
Global $hBrushSel = _WinAPI_CreateSolidBrush($clrHighlight)
Global $hPen = _WinAPI_CreatePen($PS_SOLID, 2, $clrHighlight)

Global $hGUI, $ComboBox, $hComboBox
Global $windowcallback, $pwindowcallback, $combocallback, $pcombocallback

Example()

Func Example()
    OnAutoItExitRegister("_customcombobox_cleanup")
    
    GUIRegisterMsg($WM_MEASUREITEM, '_WM_MEASUREITEM')
;~  GUIRegisterMsg($WM_DRAWITEM, '_WM_DRAWITEM')

    $hGUI = GUICreate('Test', 220, 300)
    $ComboBox = GUICtrlCreateCombo('', 10, 10, 200, 300, BitOR($WS_CHILD, $CBS_OWNERDRAWVARIABLE, $CBS_HASSTRINGS, $CBS_DROPDOWNLIST))
    GUICtrlSetData($ComboBox, "Apple|Banana|Orange|Pear|Plum|WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon")
    $hComboBox = GUICtrlGetHandle($ComboBox)
    
    ;subclass window for WM_DRAWITEM
    $windowcallback = DllCallbackRegister("_customCombobox_WindowCallback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pwindowcallback = DllCallbackGetPtr($windowcallback)
    _WinAPI_SetWindowSubclass($hGUI, $pwindowcallback, 1, $ComboBox) ; $iSubclassId = 1, $pData = CtrlID
    
    ;subclass combobox for WM_PAINT
    $combocallback = DllCallbackRegister("_customCombobox_Callback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pcombocallback = DllCallbackGetPtr($combocallback)
    _WinAPI_SetWindowSubclass(GUICtrlGetHandle($ComboBox), $pcombocallback, 2, $ComboBox) ; $iSubclassId = 2, $pData = CtrlID
    
    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
        EndSwitch
    WEnd
EndFunc   ;==>Example

;subclassed window function --> WM_DRAWITEM
Func _customCombobox_WindowCallback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    If $iMsg <> $WM_DRAWITEM Then Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
    
    Local Const $tagDRAWITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemAction;' & _
            'uint itemState;' & _
            'hwnd hwndItem;' & _
            'hwnd hDC;' & _
            $tagRECT & _
            ';ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Switch $iMsg
        Case $WM_DRAWITEM
            Local $tDIS
            Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
            Local $clrForeground, $clrBackground
            Local $hWndItem, $hDC, $hOldPen, $hOldBrush
            Local $tRect, $aRect[4]
            Local $sText, $iCode, $iIDFrom
            
            $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $lParam)
            $iCtlType = DllStructGetData($tDIS, 'CtlType')
            $iCtlID = DllStructGetData($tDIS, 'CtlID')
            $iItemID = DllStructGetData($tDIS, 'itemID')
            $iItemAction = DllStructGetData($tDIS, 'itemAction')
            $iItemState = DllStructGetData($tDIS, 'itemState')
            $hWndItem = DllStructGetData($tDIS, 'hwndItem')
            $hDC = DllStructGetData($tDIS, 'hDC')
            $tRect = DllStructCreate($tagRECT)
            
            If $iCtlType = $ODT_COMBOBOX Then
                
                If $iCtlID == $pData Then ;if my combobox
                    For $i = 1 To 4
                        DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                        $aRect[$i - 1] = DllStructGetData($tRect, $i)
                    Next

                    _GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

                    If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                        $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                        $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                        _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                        _WinAPI_SelectObject($hDC, $hOldPen)
                        _WinAPI_SelectObject($hDC, $hOldBrush)

                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                    Else
                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                        _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                    EndIf
                    DllStructSetData($tRect, "Left", $aRect[0] + 4)
                    DllStructSetData($tRect, "Top", $aRect[1] + 4)
                    DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                    _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                    _WinAPI_SetTextColor($hDC, $clrForeground)
                    _WinAPI_SetBkColor($hDC, $clrBackground)
                    Return True
                EndIf
                
            EndIf
    EndSwitch
    
    ;default subclass proc
    Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
EndFunc   ;==>_customCombobox_WindowCallback

;guiregistermsg WM_DRAWITEM
Func _WM_DRAWITEM($hWnd, $iMsg, $iwParam, $ilParam)
    Local Const $tagDRAWITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemAction;' & _
            'uint itemState;' & _
            'hwnd hwndItem;' & _
            'hwnd hDC;' & _
            $tagRECT & _
            ';ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Local $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $ilParam)
    Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
    Local $clrForeground, $clrBackground
    Local $hWndItem, $hDC, $hOldPen, $hOldBrush
    Local $tRect, $aRect[4]
    Local $sText

    $iCtlType = DllStructGetData($tDIS, 'CtlType')
    $iCtlID = DllStructGetData($tDIS, 'CtlID')
    $iItemID = DllStructGetData($tDIS, 'itemID')
    $iItemAction = DllStructGetData($tDIS, 'itemAction')
    $iItemState = DllStructGetData($tDIS, 'itemState')
    $hWndItem = DllStructGetData($tDIS, 'hwndItem')
    $hDC = DllStructGetData($tDIS, 'hDC')
    $tRect = DllStructCreate($tagRECT)

    If $iCtlType = $ODT_COMBOBOX Then
        Switch $iCtlID
            Case $ComboBox
                For $i = 1 To 4
                    DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                    $aRect[$i - 1] = DllStructGetData($tRect, $i)
                Next

                _GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

                If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                    $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                    $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                    _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                    _WinAPI_SelectObject($hDC, $hOldPen)
                    _WinAPI_SelectObject($hDC, $hOldBrush)

                    $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                    $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                Else
                    $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                    $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                    _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                EndIf
                DllStructSetData($tRect, "Left", $aRect[0] + 4)
                DllStructSetData($tRect, "Top", $aRect[1] + 4)
                DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                _WinAPI_SetTextColor($hDC, $clrForeground)
                _WinAPI_SetBkColor($hDC, $clrBackground)
        EndSwitch
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_DRAWITEM

;callback function for combobox
Func _customCombobox_Callback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    ;do stuff here
    
    ;run default subclass proc
    Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
EndFunc   ;==>_customCombobox_Callback

;guiregistermsg WM_MEASUREITEM
Func _WM_MEASUREITEM($hWnd, $iMsg, $iwParam, $ilParam)
    Local Const $tagMEASUREITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemWidth;' & _
            'uint itemHeight;' & _
            'ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3

    Local $tMIS = DllStructCreate($tagMEASUREITEMSTRUCT, $ilParam)
    Local $iCtlType, $iCtlID, $iItemID, $iItemWidth, $iItemHeight
    Local $hComboBox
    Local $tSize
    Local $sText

    $iCtlType = DllStructGetData($tMIS, 'CtlType')
    $iCtlID = DllStructGetData($tMIS, 'CtlID')
    $iItemID = DllStructGetData($tMIS, 'itemID')
    $iItemWidth = DllStructGetData($tMIS, 'itemWidth')
    $iItemHeight = DllStructGetData($tMIS, 'itemHeight')
    $hComboBox = GUICtrlGetHandle($iCtlID)

    If $iCtlType = $ODT_COMBOBOX Then
        DllStructSetData($tMIS, 'itemHeight', 20)
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_MEASUREITEM

Func _WinAPI_Rectangle_Pts($hDC, $iLeft, $iTop, $iRight, $iBottom)
    Local $aResult = DllCall("gdi32.dll", "int", "Rectangle", "hwnd", $hDC, "int", $iLeft, "int", $iTop, "int", $iRight, "int", $iBottom)

    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0] <> 0
EndFunc   ;==>_WinAPI_Rectangle_Pts

Func _onExit()
    _WinAPI_DeleteObject($hPen)
    _WinAPI_DeleteObject($hBrushSel)
    _WinAPI_DeleteObject($hBrushNorm)

    Exit
EndFunc   ;==>_onExit

Func _customcombobox_cleanup()
    _WinAPI_RemoveWindowSubclass($hGUI, $pwindowcallback, 1)
    DllCallbackFree($windowcallback)

    _WinAPI_RemoveWindowSubclass($hGUI, $pcombocallback, 2)
    DllCallbackFree($combocallback)
EndFunc   ;==>_customcombobox_cleanup

 

Edited by kurtykurtyboy
more information
Link to comment
Share on other sites

The code is almost OK. But you make it a little bit harder than it really is.

To implement message handlers through subclassing you can pretty much just make a direct translation of the message handlers based on GUIRegisterMsg.

If you don't have to handle WM_PAINT messages in the GUIRegisterMsg code, you don't have to handle WM_PAINT messages in the subclassing code. Delete the WM_PAINT code and everything will work.

In AutoIt you usually never need to handle WM_PAINT messages in your own code. They are handled very well in the internal code.

When you implement subclassing, you can create a subclass with _WinAPI_SetWindowSubclass for each control or each child window or the main GUI. But you do not create a subclass for each WM_MESSAGE. This will inevitably lead to performance issues.

Here are two versions of your code that handles $WM_MEASUREITEM (height changed from 20 to 30 to show that it's working) and $WM_DRAWITEM messages. The first is based on message  handlers created with GUIRegisterMsg. The second is based on a message  handler implemented through subclassing. Note how similar the code is in the two versions.

GUIRegisterMsg:

#include <GUIComboBox.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIGdi.au3>

Global Const $clrWindowText = 0xEEEEEE
Global Const $clrHighlightText = 0xFFFFFF
Global Const $clrHighlight = 0x777777
Global Const $clrWindow = 0x444444

Global $hBrushNorm = _WinAPI_CreateSolidBrush($clrWindow)
Global $hBrushSel = _WinAPI_CreateSolidBrush($clrHighlight)
Global $hPen = _WinAPI_CreatePen($PS_SOLID, 2, $clrHighlight)

Global $hGUI, $ComboBox, $hComboBox
Global $windowcallback, $pwindowcallback, $combocallback, $pcombocallback

OnAutoItExitRegister("_customcombobox_cleanup") ; OnAutoItExitRegister should be placed here, not in the function <<<<<<<<<<<<<<<<<<<<<

Example()

Func Example()
    GUIRegisterMsg($WM_MEASUREITEM, '_WM_MEASUREITEM')
    GUIRegisterMsg($WM_DRAWITEM, '_WM_DRAWITEM')

    $hGUI = GUICreate('Test', 220, 300)
    $ComboBox = GUICtrlCreateCombo('', 10, 10, 200, 300, BitOR($WS_CHILD, $CBS_OWNERDRAWVARIABLE, $CBS_HASSTRINGS, $CBS_DROPDOWNLIST))
    GUICtrlSetData($ComboBox, "Apple|Banana|Orange|Pear|Plum|WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon")
    $hComboBox = GUICtrlGetHandle($ComboBox)
    
    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
        EndSwitch
    WEnd
EndFunc   ;==>Example

;guiregistermsg WM_DRAWITEM
Func _WM_DRAWITEM($hWnd, $iMsg, $iwParam, $ilParam)
    Local Const $tagDRAWITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemAction;' & _
            'uint itemState;' & _
            'hwnd hwndItem;' & _
            'hwnd hDC;' & _
            $tagRECT & _
            ';ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Local $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $ilParam)
    Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
    Local $clrForeground, $clrBackground
    Local $hWndItem, $hDC, $hOldPen, $hOldBrush
    Local $tRect, $aRect[4]
    Local $sText

    $iCtlType = DllStructGetData($tDIS, 'CtlType')
    $iCtlID = DllStructGetData($tDIS, 'CtlID')
    $iItemID = DllStructGetData($tDIS, 'itemID')
    $iItemAction = DllStructGetData($tDIS, 'itemAction')
    $iItemState = DllStructGetData($tDIS, 'itemState')
    $hWndItem = DllStructGetData($tDIS, 'hwndItem')
    $hDC = DllStructGetData($tDIS, 'hDC')
    $tRect = DllStructCreate($tagRECT)

    If $iCtlType = $ODT_COMBOBOX Then
        Switch $iCtlID
            Case $ComboBox
                For $i = 1 To 4
                    DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                    $aRect[$i - 1] = DllStructGetData($tRect, $i)
                Next

                _GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

                If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                    $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                    $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                    _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                    _WinAPI_SelectObject($hDC, $hOldPen)
                    _WinAPI_SelectObject($hDC, $hOldBrush)

                    $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                    $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                Else
                    $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                    $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                    _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                EndIf
                DllStructSetData($tRect, "Left", $aRect[0] + 4)
                DllStructSetData($tRect, "Top", $aRect[1] + 4)
                DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                _WinAPI_SetTextColor($hDC, $clrForeground)
                _WinAPI_SetBkColor($hDC, $clrBackground)
        EndSwitch
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_DRAWITEM

;guiregistermsg WM_MEASUREITEM
Func _WM_MEASUREITEM($hWnd, $iMsg, $iwParam, $ilParam)
    Local Const $tagMEASUREITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemWidth;' & _
            'uint itemHeight;' & _
            'ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3

    Local $tMIS = DllStructCreate($tagMEASUREITEMSTRUCT, $ilParam)
    Local $iCtlType, $iCtlID, $iItemID, $iItemWidth, $iItemHeight
    Local $hComboBox
    Local $tSize
    Local $sText

    $iCtlType = DllStructGetData($tMIS, 'CtlType')
    $iCtlID = DllStructGetData($tMIS, 'CtlID')
    $iItemID = DllStructGetData($tMIS, 'itemID')
    $iItemWidth = DllStructGetData($tMIS, 'itemWidth')
    $iItemHeight = DllStructGetData($tMIS, 'itemHeight')
    $hComboBox = GUICtrlGetHandle($iCtlID)

    If $iCtlType = $ODT_COMBOBOX Then
        DllStructSetData($tMIS, 'itemHeight', 30) ; 20 -> 30 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_MEASUREITEM

Func _WinAPI_Rectangle_Pts($hDC, $iLeft, $iTop, $iRight, $iBottom)
    Local $aResult = DllCall("gdi32.dll", "int", "Rectangle", "hwnd", $hDC, "int", $iLeft, "int", $iTop, "int", $iRight, "int", $iBottom)

    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0] <> 0
EndFunc   ;==>_WinAPI_Rectangle_Pts

Func _onExit()
    _WinAPI_DeleteObject($hPen)
    _WinAPI_DeleteObject($hBrushSel)
    _WinAPI_DeleteObject($hBrushNorm)

    Exit
EndFunc   ;==>_onExit

Func _customcombobox_cleanup()
    GUIRegisterMsg($WM_MEASUREITEM, '')

    GUIRegisterMsg($WM_DRAWITEM, '')
EndFunc   ;==>_customcombobox_cleanup

_WinAPI_SetWindowSubclass:

#include <GUIComboBox.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIGdi.au3>

Global Const $clrWindowText = 0xEEEEEE
Global Const $clrHighlightText = 0xFFFFFF
Global Const $clrHighlight = 0x777777
Global Const $clrWindow = 0x444444

Global $hBrushNorm = _WinAPI_CreateSolidBrush($clrWindow)
Global $hBrushSel = _WinAPI_CreateSolidBrush($clrHighlight)
Global $hPen = _WinAPI_CreatePen($PS_SOLID, 2, $clrHighlight)

Global $hGUI, $ComboBox, $hComboBox
Global $windowcallback, $pwindowcallback, $combocallback, $pcombocallback

OnAutoItExitRegister("_customcombobox_cleanup") ; OnAutoItExitRegister should be placed here, not in the function <<<<<<<<<<<<<<<<<<<<<

Example()

Func Example()
    $hGUI = GUICreate('Test', 220, 300)
    
    ;subclass the GUI
    ; The subclass must be implemented here to be able to respond to $WM_MEASUREITEM messages when the Combobox is created
    $windowcallback = DllCallbackRegister("_customCombobox_WindowCallback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pwindowcallback = DllCallbackGetPtr($windowcallback)
    _WinAPI_SetWindowSubclass($hGUI, $pwindowcallback, 1, 0) ; $iSubclassId = 1, $pData = 0 ($ComboBox is not available)
    
    $ComboBox = GUICtrlCreateCombo('', 10, 10, 200, 300, BitOR($WS_CHILD, $CBS_OWNERDRAWVARIABLE, $CBS_HASSTRINGS, $CBS_DROPDOWNLIST))
    GUICtrlSetData($ComboBox, "Apple|Banana|Orange|Pear|Plum|WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon")
    $hComboBox = GUICtrlGetHandle($ComboBox)

    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
        EndSwitch
    WEnd
EndFunc   ;==>Example

;subclassed window function --> GUI
Func _customCombobox_WindowCallback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    If $iMsg <> $WM_DRAWITEM And $iMsg <> $WM_MEASUREITEM Then Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
    
    Local Const $ODT_COMBOBOX = 3
    Local Const $ODT_STATIC = 5
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Switch $iMsg
        Case $WM_DRAWITEM
            Local Const $tagDRAWITEMSTRUCT = _
                    'uint CtlType;' & _
                    'uint CtlID;' & _
                    'uint itemID;' & _
                    'uint itemAction;' & _
                    'uint itemState;' & _
                    'hwnd hwndItem;' & _
                    'hwnd hDC;' & _
                    $tagRECT & _
                    ';ulong_ptr itemData;'

            Local $tDIS
            Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
            Local $clrForeground, $clrBackground
            Local $hWndItem, $hDC, $hOldPen, $hOldBrush
            Local $tRect, $aRect[4]
            Local $sText, $iCode, $iIDFrom
            
            $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $lParam)
            $iCtlType = DllStructGetData($tDIS, 'CtlType')
            $iCtlID = DllStructGetData($tDIS, 'CtlID')
            $iItemID = DllStructGetData($tDIS, 'itemID')
            $iItemAction = DllStructGetData($tDIS, 'itemAction')
            $iItemState = DllStructGetData($tDIS, 'itemState')
            $hWndItem = DllStructGetData($tDIS, 'hwndItem')
            $hDC = DllStructGetData($tDIS, 'hDC')
            $tRect = DllStructCreate($tagRECT)
            
            If $iCtlType = $ODT_COMBOBOX Then
                
                If $iCtlID = $ComboBox Then ;if my combobox
                    For $i = 1 To 4
                        DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                        $aRect[$i - 1] = DllStructGetData($tRect, $i)
                    Next

                    _GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

                    If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                        $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                        $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                        _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                        _WinAPI_SelectObject($hDC, $hOldPen)
                        _WinAPI_SelectObject($hDC, $hOldBrush)

                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                    Else
                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                        _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                    EndIf
                    DllStructSetData($tRect, "Left", $aRect[0] + 4)
                    DllStructSetData($tRect, "Top", $aRect[1] + 4)
                    DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                    _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                    _WinAPI_SetTextColor($hDC, $clrForeground)
                    _WinAPI_SetBkColor($hDC, $clrBackground)
                    Return True
                EndIf
                
            EndIf
                
        Case $WM_MEASUREITEM
            Local Const $tagMEASUREITEMSTRUCT = _
                    'uint CtlType;' & _
                    'uint CtlID;' & _
                    'uint itemID;' & _
                    'uint itemWidth;' & _
                    'uint itemHeight;' & _
                    'ulong_ptr itemData;'

            Local $tMIS = DllStructCreate($tagMEASUREITEMSTRUCT, $lparam)
            Local $iCtlType, $iCtlID, $iItemID, $iItemWidth, $iItemHeight
            Local $hComboBox
            Local $tSize
            Local $sText

            $iCtlType = DllStructGetData($tMIS, 'CtlType')
            $iCtlID = DllStructGetData($tMIS, 'CtlID')
            $iItemID = DllStructGetData($tMIS, 'itemID')
            $iItemWidth = DllStructGetData($tMIS, 'itemWidth')
            $iItemHeight = DllStructGetData($tMIS, 'itemHeight')
            $hComboBox = GUICtrlGetHandle($iCtlID)
            
            If $iCtlType = $ODT_COMBOBOX Then
                DllStructSetData($tMIS, 'itemHeight', 30) ; 20 -> 30 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            EndIf
    EndSwitch
    
    ;default subclass proc
    Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
EndFunc   ;==>_customCombobox_WindowCallback

Func _WinAPI_Rectangle_Pts($hDC, $iLeft, $iTop, $iRight, $iBottom)
    Local $aResult = DllCall("gdi32.dll", "int", "Rectangle", "hwnd", $hDC, "int", $iLeft, "int", $iTop, "int", $iRight, "int", $iBottom)

    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0] <> 0
EndFunc   ;==>_WinAPI_Rectangle_Pts

Func _onExit()
    _WinAPI_DeleteObject($hPen)
    _WinAPI_DeleteObject($hBrushSel)
    _WinAPI_DeleteObject($hBrushNorm)

    Exit
EndFunc   ;==>_onExit

Func _customcombobox_cleanup()
    _WinAPI_RemoveWindowSubclass($hGUI, $pwindowcallback, 1)
    DllCallbackFree($windowcallback)
EndFunc   ;==>_customcombobox_cleanup

One more thing. The risk of a user of your UDF also want to use WM_MEASUREITEM & WM_DRAWITEM messages in his own code is probably very small. So I think you can use GUIRegisterMsg in your UDF for these two messages. But if you want to be on the completely safe side you can use subclassing.

Edited by LarsJ
One more thing
Link to comment
Share on other sites

  • Moderators

kurtykurtyboy,

If you do decide to go with GUIRegisterMsg, you can always use a UDF function to register (or not) the message(s) you need and provide separate individual handler(s) which the user can add to his own handler if it already exists. Take a look at some of my UDFs (links hidden in my sig) to see how I did it - GUIListViewEx and ChooseFileFolder are good examples.

I still hope that one day we will get the ability to chain messages in core code, but I am not holding my breath. I have tried several times to introduce a standard UDF to do it but the resistance from others was too strong.

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

@LarsJ  I would like to change how the combobox itself looks as well. As I understand it, the combobox handles its own WM_PAINT; so I need to subclass the combobox to handle WM_PAINT messages. Is there a better way to do that in AutoIT?

Starting from your code above, I added my subclass / WM_PAINT code. It looks ok until you mouse over the items quickly.

#include <GUIComboBox.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIGdi.au3>

Global Const $clrWindowText = 0xEEEEEE
Global Const $clrHighlightText = 0xFFFFFF
Global Const $clrHighlight = 0x777777
Global Const $clrWindow = 0x444444

Global $hBrushNorm = _WinAPI_CreateSolidBrush($clrWindow)
Global $hBrushSel = _WinAPI_CreateSolidBrush($clrHighlight)
Global $hPen = _WinAPI_CreatePen($PS_SOLID, 2, $clrHighlight)

Global $hGUI, $ComboBox, $hComboBox
Global $windowcallback, $pwindowcallback, $combocallback, $pcombocallback
Global $comboOpen, $comboChanged, $comboHover

OnAutoItExitRegister("_customcombobox_cleanup") ; OnAutoItExitRegister should be placed here, not in the function <<<<<<<<<<<<<<<<<<<<<

Example()

Func Example()
    $hGUI = GUICreate('Test', 220, 300)

    ;subclass the GUI
    ; The subclass must be implemented here to be able to respond to $WM_MEASUREITEM messages when the Combobox is created
    $windowcallback = DllCallbackRegister("_customCombobox_WindowCallback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pwindowcallback = DllCallbackGetPtr($windowcallback)
    _WinAPI_SetWindowSubclass($hGUI, $pwindowcallback, 1, 0) ; $iSubclassId = 1, $pData = 0 ($ComboBox is not available)

    $ComboBox = GUICtrlCreateCombo('', 10, 10, 200, 300, BitOR($WS_CHILD, $CBS_OWNERDRAWVARIABLE, $CBS_HASSTRINGS, $CBS_DROPDOWNLIST))
    GUICtrlSetData($ComboBox, "Apple|Banana|Orange|Pear|Plum|WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon")
    $hComboBox = GUICtrlGetHandle($ComboBox)

    ;subclass combobox for WM_PAINT
    $combocallback = DllCallbackRegister("_customCombobox_Callback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pcombocallback = DllCallbackGetPtr($combocallback)
    _WinAPI_SetWindowSubclass(GUICtrlGetHandle($ComboBox), $pcombocallback, 2, $ComboBox) ; $iSubclassId = 2, $pData = CtrlID

    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
        EndSwitch
    WEnd
EndFunc   ;==>Example

;callback function for combobox
Func _customCombobox_Callback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    Local $tPAINTSTRUCT, $hDC

;~  ConsoleWrite(Hex($iMsg)&@CRLF)
    If $iMsg == $WM_PAINT Then
        ;within the scope of this function, 'background' refers to the space between the selected item and the border
        $buttonWidth = _WinAPI_GetSystemMetrics($SM_CXVSCROLL)
        $hDC = _WinAPI_BeginPaint($hWnd, $tPAINTSTRUCT)

        ;Get client rect
        $cRect = _WinAPI_GetClientRect($hWnd)

        ;shrink rect by 3 (space for border and background)
        _WinAPI_InflateRect ( $cRect, -3, -3 )
        ;shrink right side (space for button)
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")-$buttonWidth-1)

        ;remove border, button, and background from clipping region
        _WinAPI_IntersectClipRect ( $hDC, $cRect )

        ;draw the default combobox using the $hDC
        DllCall( "comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $hDC, "lparam", $lParam )

        ;remove the clipping region
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;get area between middle and border (our background)
        $cRect = _WinAPI_GetClientRect($hWnd)
        _WinAPI_InflateRect ( $cRect, -1, -1 )
        _WinAPI_IntersectClipRect ( $hDC, $cRect )
        _WinAPI_InflateRect ( $cRect, -2, -2 )
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")+1)
        _WinAPI_ExcludeClipRect($hDC, $cRect)
        _WinAPI_InflateRect ( $cRect, 2, 2 )
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")-1-$buttonWidth-3)
        ;draw the background
        _WinAPI_FillRect($hDC, DllStructGetPtr($cRect), $hBrushNorm)

        ;reset the clipping region again
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;get the rect for the button and set clipping region
        $bRect = _WinAPI_GetClientRect($hWnd)
        DllStructSetData($bRect, "Left", DllStructGetData($bRect, "Right")-1-$buttonWidth-3)
        DllStructSetData($bRect, "Right", DllStructGetData($bRect, "Right")-1)
        DllStructSetData($bRect, "Top", DllStructGetData($bRect, "Top")+1)
        DllStructSetData($bRect, "Bottom", DllStructGetData($bRect, "Bottom")-1)
        _WinAPI_IntersectClipRect ( $hDC, $bRect )
        ;draw the button
        If $comboHover Then
            $buttonColor = 0xABABAB
            $borderColor = 0x222222
            $arrowColor = 0xEFEFEF
        Else
            $buttonColor = 0x858585
            $borderColor = 0x222222
            $arrowColor = 0xBBBBBB
        EndIf
        $hBrushButton = _WinAPI_CreateSolidBrush($buttonColor)
        _WinAPI_FillRect($hDC, DllStructGetPtr($bRect), $hBrushNorm)

        ;Create the arrow path
        $leftpos = DllStructGetData($bRect, "Left")
        $toppos = DllStructGetData($bRect, "Top")
        $buttonMiddle = ($buttonWidth+3)/2
        $buttonVMiddle = (DllStructGetData($cRect, "Bottom")-$toppos)/2
        $arrowWidth = 12
        $arrowHeight = 12
        _WinAPI_BeginPath($hDC)
        _WinAPI_MoveTo($hDC, $leftpos+$buttonMiddle, $toppos+$buttonVMiddle+$arrowHeight/2)
        _WinAPI_LineTo($hDC, $leftpos+$buttonMiddle+$arrowWidth/2, $toppos++$buttonVMiddle-$arrowHeight/2)
        _WinAPI_LineTo($hDC, $leftpos+$buttonMiddle-$arrowWidth/2, $toppos++$buttonVMiddle-$arrowHeight/2)
        _WinAPI_CloseFigure($hDC)
        _WinAPI_EndPath($hDC)

        ;draw the arrow
        $hOldBrush = _WinAPI_SelectObject($hDC, _WinAPI_GetStockObject($DC_BRUSH))
        $hOldPen = _WinAPI_SelectObject($hDC, _WinAPI_GetStockObject($DC_PEN))
        _WinAPI_SetDCBrushColor($hDC, $arrowColor)
        _WinAPI_SetDCPenColor($hDC, 0x222222)
        _WinAPI_StrokeAndFillPath($hDC)
        _WinAPI_SelectObject($hDC, $hOldBrush)
        _WinAPI_SelectObject($hDC, $hOldPen)

        ;remove the clipping region
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;remove inside from clipping region (keep only the border)
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")+_WinAPI_GetSystemMetrics($SM_CXVSCROLL)+3)
        _WinAPI_ExcludeClipRect($hDC, $cRect)
        $cRect = _WinAPI_GetClientRect($hWnd)

        ;create border brush
        $hBrushBorder = _WinAPI_CreateSolidBrush($borderColor)

        ;get full rect size again and fill border
        $cRect = _WinAPI_GetClientRect($hWnd)
        _WinAPI_FillRect($hDC, DllStructGetPtr($cRect), $hBrushBorder)

        ;clean up
        _WinAPI_DeleteObject($hBrushBorder)
        _WinAPI_DeleteObject($hBrushButton)
        _WinAPI_DeleteObject($hOldBrush)
        _WinAPI_DeleteObject($hOldPen)

        _WinAPI_EndPaint($hWnd, $tPAINTSTRUCT)
        Return 0
    ElseIf $iMsg == $WM_MOUSEFIRST  Then
        $comboHover = 1
    ElseIf $iMsg == $WM_MOUSELEAVE  Then
        $comboHover = 0
    EndIf

    ;run default subclass proc
    Return DllCall( "comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam )[0]
EndFunc   ;==>_customCombobox_Callback

;subclassed window function --> GUI
Func _customCombobox_WindowCallback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    If $iMsg <> $WM_DRAWITEM And $iMsg <> $WM_MEASUREITEM Then Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]

    Local Const $ODT_COMBOBOX = 3
    Local Const $ODT_STATIC = 5
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Switch $iMsg
        Case $WM_DRAWITEM
            Local Const $tagDRAWITEMSTRUCT = _
                    'uint CtlType;' & _
                    'uint CtlID;' & _
                    'uint itemID;' & _
                    'uint itemAction;' & _
                    'uint itemState;' & _
                    'hwnd hwndItem;' & _
                    'hwnd hDC;' & _
                    $tagRECT & _
                    ';ulong_ptr itemData;'

            Local $tDIS
            Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
            Local $clrForeground, $clrBackground
            Local $hWndItem, $hDC, $hOldPen, $hOldBrush
            Local $tRect, $aRect[4]
            Local $sText, $iCode, $iIDFrom

            $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $lParam)
            $iCtlType = DllStructGetData($tDIS, 'CtlType')
            $iCtlID = DllStructGetData($tDIS, 'CtlID')
            $iItemID = DllStructGetData($tDIS, 'itemID')
            $iItemAction = DllStructGetData($tDIS, 'itemAction')
            $iItemState = DllStructGetData($tDIS, 'itemState')
            $hWndItem = DllStructGetData($tDIS, 'hwndItem')
            $hDC = DllStructGetData($tDIS, 'hDC')
            $tRect = DllStructCreate($tagRECT)

            If $iCtlType = $ODT_COMBOBOX Then

                If $iCtlID = $ComboBox Then ;if my combobox
                    For $i = 1 To 4
                        DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                        $aRect[$i - 1] = DllStructGetData($tRect, $i)
                    Next

                    _GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

                    If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                        $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                        $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                        _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                        _WinAPI_SelectObject($hDC, $hOldPen)
                        _WinAPI_SelectObject($hDC, $hOldBrush)

                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                    Else
                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                        _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                    EndIf
                    DllStructSetData($tRect, "Left", $aRect[0] + 4)
                    DllStructSetData($tRect, "Top", $aRect[1] + 4)
                    DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                    _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                    _WinAPI_SetTextColor($hDC, $clrForeground)
                    _WinAPI_SetBkColor($hDC, $clrBackground)
                    Return True
                EndIf

            EndIf

        Case $WM_MEASUREITEM
            Local Const $tagMEASUREITEMSTRUCT = _
                    'uint CtlType;' & _
                    'uint CtlID;' & _
                    'uint itemID;' & _
                    'uint itemWidth;' & _
                    'uint itemHeight;' & _
                    'ulong_ptr itemData;'

            Local $tMIS = DllStructCreate($tagMEASUREITEMSTRUCT, $lparam)
            Local $iCtlType, $iCtlID, $iItemID, $iItemWidth, $iItemHeight
            Local $hComboBox
            Local $tSize
            Local $sText

            $iCtlType = DllStructGetData($tMIS, 'CtlType')
            $iCtlID = DllStructGetData($tMIS, 'CtlID')
            $iItemID = DllStructGetData($tMIS, 'itemID')
            $iItemWidth = DllStructGetData($tMIS, 'itemWidth')
            $iItemHeight = DllStructGetData($tMIS, 'itemHeight')
            $hComboBox = GUICtrlGetHandle($iCtlID)

            If $iCtlType = $ODT_COMBOBOX Then
                DllStructSetData($tMIS, 'itemHeight', 30) ; 20 -> 30 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            EndIf
    EndSwitch

    ;default subclass proc
    Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
EndFunc   ;==>_customCombobox_WindowCallback

Func _WinAPI_Rectangle_Pts($hDC, $iLeft, $iTop, $iRight, $iBottom)
    Local $aResult = DllCall("gdi32.dll", "int", "Rectangle", "hwnd", $hDC, "int", $iLeft, "int", $iTop, "int", $iRight, "int", $iBottom)

    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0] <> 0
EndFunc   ;==>_WinAPI_Rectangle_Pts

Func _onExit()
    _WinAPI_DeleteObject($hPen)
    _WinAPI_DeleteObject($hBrushSel)
    _WinAPI_DeleteObject($hBrushNorm)

    Exit
EndFunc   ;==>_onExit

Func _customcombobox_cleanup()
    _WinAPI_RemoveWindowSubclass($hGUI, $pwindowcallback, 1)
    DllCallbackFree($windowcallback)
EndFunc   ;==>_customcombobox_cleanup

 

Link to comment
Share on other sites

I realized I could simplify this by hiding the combobox and using a label (or anything really!) instead. Then I just need to add a few things to open the list on click

...
    GUICtrlSetState($ComboBox, 32) ;hide combobox
    
    $label = GUICtrlCreateLabel("combo", 10, 10, 200, 30)
    GUICtrlSetBkColor(-1, 0x555555)
    
    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
            Case $label
                ConsoleWrite("hit"&@CRLF)
                _showComboList()
        EndSwitch
    WEnd
    
...

Func _showComboList()
    _GUICtrlComboBox_ShowDropDown($ComboBox, True)
EndFunc

This needs some tweaking of course, but I think it is an acceptable solution (in my case) to keep moving forward.

 

Melba23,

I like your suggestion and will probably use it for this. Thanks for the tip!

 

I am still curious, though, if my original idea is possible with WM_PAINT...

Link to comment
Share on other sites

There is no doubt that it's WM_PAINT messages from the rest of the GUI (WM_PAINT messages that does not belong to the Combo Box) that's causing the flicker.

If you implement WM_MEASUREITEM and WM_DRAWITEM messages with GUIRegisterMsg and WM_PAINT messages from the Combo Box through subclassing everything is working:

#include <GUIComboBox.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIGdi.au3>

Global Const $clrWindowText = 0xEEEEEE
Global Const $clrHighlightText = 0xFFFFFF
Global Const $clrHighlight = 0x777777
Global Const $clrWindow = 0x444444

Global $hBrushNorm = _WinAPI_CreateSolidBrush($clrWindow)
Global $hBrushSel = _WinAPI_CreateSolidBrush($clrHighlight)
Global $hPen = _WinAPI_CreatePen($PS_SOLID, 2, $clrHighlight)

Global $hGUI, $ComboBox, $hComboBox
Global $windowcallback, $pwindowcallback, $combocallback, $pcombocallback
Global $comboOpen, $comboChanged, $comboHover

OnAutoItExitRegister("_customcombobox_cleanup") ; OnAutoItExitRegister should be placed here, not in the function <<<<<<<<<<<<<<<<<<<<<

Example()

Func Example()
    GUIRegisterMsg($WM_MEASUREITEM, '_WM_MEASUREITEM')
    GUIRegisterMsg($WM_DRAWITEM, '_WM_DRAWITEM')

    $hGUI = GUICreate('Test', 220, 300)
    $ComboBox = GUICtrlCreateCombo('', 10, 10, 200, 300, BitOR($WS_CHILD, $CBS_OWNERDRAWVARIABLE, $CBS_HASSTRINGS, $CBS_DROPDOWNLIST))
    GUICtrlSetData($ComboBox, "Apple|Banana|Orange|Pear|Plum|WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon")
    $hComboBox = GUICtrlGetHandle($ComboBox)

    ;subclass combobox for WM_PAINT
    $combocallback = DllCallbackRegister("_customCombobox_Callback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pcombocallback = DllCallbackGetPtr($combocallback)
    _WinAPI_SetWindowSubclass(GUICtrlGetHandle($ComboBox), $pcombocallback, 2, $ComboBox) ; $iSubclassId = 2, $pData = CtrlID

    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
        EndSwitch
    WEnd
EndFunc   ;==>Example

;callback function for combobox
Func _customCombobox_Callback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    Local $tPAINTSTRUCT, $hDC

;~  ConsoleWrite(Hex($iMsg)&@CRLF)
    If $iMsg == $WM_PAINT Then
        ;within the scope of this function, 'background' refers to the space between the selected item and the border
        $buttonWidth = _WinAPI_GetSystemMetrics($SM_CXVSCROLL)
        $hDC = _WinAPI_BeginPaint($hWnd, $tPAINTSTRUCT)

        ;Get client rect
        $cRect = _WinAPI_GetClientRect($hWnd)

        ;shrink rect by 3 (space for border and background)
        _WinAPI_InflateRect ( $cRect, -3, -3 )
        ;shrink right side (space for button)
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")-$buttonWidth-1)

        ;remove border, button, and background from clipping region
        _WinAPI_IntersectClipRect ( $hDC, $cRect )

        ;draw the default combobox using the $hDC
        DllCall( "comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $hDC, "lparam", $lParam )

        ;remove the clipping region
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;get area between middle and border (our background)
        $cRect = _WinAPI_GetClientRect($hWnd)
        _WinAPI_InflateRect ( $cRect, -1, -1 )
        _WinAPI_IntersectClipRect ( $hDC, $cRect )
        _WinAPI_InflateRect ( $cRect, -2, -2 )
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")+1)
        _WinAPI_ExcludeClipRect($hDC, $cRect)
        _WinAPI_InflateRect ( $cRect, 2, 2 )
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")-1-$buttonWidth-3)
        ;draw the background
        _WinAPI_FillRect($hDC, DllStructGetPtr($cRect), $hBrushNorm)

        ;reset the clipping region again
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;get the rect for the button and set clipping region
        $bRect = _WinAPI_GetClientRect($hWnd)
        DllStructSetData($bRect, "Left", DllStructGetData($bRect, "Right")-1-$buttonWidth-3)
        DllStructSetData($bRect, "Right", DllStructGetData($bRect, "Right")-1)
        DllStructSetData($bRect, "Top", DllStructGetData($bRect, "Top")+1)
        DllStructSetData($bRect, "Bottom", DllStructGetData($bRect, "Bottom")-1)
        _WinAPI_IntersectClipRect ( $hDC, $bRect )
        ;draw the button
        If $comboHover Then
            $buttonColor = 0xABABAB
            $borderColor = 0x222222
            $arrowColor = 0xEFEFEF
        Else
            $buttonColor = 0x858585
            $borderColor = 0x222222
            $arrowColor = 0xBBBBBB
        EndIf
        $hBrushButton = _WinAPI_CreateSolidBrush($buttonColor)
        _WinAPI_FillRect($hDC, DllStructGetPtr($bRect), $hBrushNorm)

        ;Create the arrow path
        $leftpos = DllStructGetData($bRect, "Left")
        $toppos = DllStructGetData($bRect, "Top")
        $buttonMiddle = ($buttonWidth+3)/2
        $buttonVMiddle = (DllStructGetData($cRect, "Bottom")-$toppos)/2
        $arrowWidth = 12
        $arrowHeight = 12
        _WinAPI_BeginPath($hDC)
        _WinAPI_MoveTo($hDC, $leftpos+$buttonMiddle, $toppos+$buttonVMiddle+$arrowHeight/2)
        _WinAPI_LineTo($hDC, $leftpos+$buttonMiddle+$arrowWidth/2, $toppos++$buttonVMiddle-$arrowHeight/2)
        _WinAPI_LineTo($hDC, $leftpos+$buttonMiddle-$arrowWidth/2, $toppos++$buttonVMiddle-$arrowHeight/2)
        _WinAPI_CloseFigure($hDC)
        _WinAPI_EndPath($hDC)

        ;draw the arrow
        $hOldBrush = _WinAPI_SelectObject($hDC, _WinAPI_GetStockObject($DC_BRUSH))
        $hOldPen = _WinAPI_SelectObject($hDC, _WinAPI_GetStockObject($DC_PEN))
        _WinAPI_SetDCBrushColor($hDC, $arrowColor)
        _WinAPI_SetDCPenColor($hDC, 0x222222)
        _WinAPI_StrokeAndFillPath($hDC)
        _WinAPI_SelectObject($hDC, $hOldBrush)
        _WinAPI_SelectObject($hDC, $hOldPen)

        ;remove the clipping region
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;remove inside from clipping region (keep only the border)
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")+_WinAPI_GetSystemMetrics($SM_CXVSCROLL)+3)
        _WinAPI_ExcludeClipRect($hDC, $cRect)
        $cRect = _WinAPI_GetClientRect($hWnd)

        ;create border brush
        $hBrushBorder = _WinAPI_CreateSolidBrush($borderColor)

        ;get full rect size again and fill border
        $cRect = _WinAPI_GetClientRect($hWnd)
        _WinAPI_FillRect($hDC, DllStructGetPtr($cRect), $hBrushBorder)

        ;clean up
        _WinAPI_DeleteObject($hBrushBorder)
        _WinAPI_DeleteObject($hBrushButton)
        _WinAPI_DeleteObject($hOldBrush)
        _WinAPI_DeleteObject($hOldPen)

        _WinAPI_EndPaint($hWnd, $tPAINTSTRUCT)
        Return 0
    ElseIf $iMsg == $WM_MOUSEFIRST  Then
        $comboHover = 1
    ElseIf $iMsg == $WM_MOUSELEAVE  Then
        $comboHover = 0
    EndIf

    ;run default subclass proc
    Return DllCall( "comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam )[0]
EndFunc   ;==>_customCombobox_Callback

;guiregistermsg WM_DRAWITEM
Func _WM_DRAWITEM($hWnd, $iMsg, $iwParam, $ilParam)
    Local Const $tagDRAWITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemAction;' & _
            'uint itemState;' & _
            'hwnd hwndItem;' & _
            'hwnd hDC;' & _
            $tagRECT & _
            ';ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Local $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $ilParam)
    Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
    Local $clrForeground, $clrBackground
    Local $hWndItem, $hDC, $hOldPen, $hOldBrush
    Local $tRect, $aRect[4]
    Local $sText

    $iCtlType = DllStructGetData($tDIS, 'CtlType')
    $iCtlID = DllStructGetData($tDIS, 'CtlID')
    $iItemID = DllStructGetData($tDIS, 'itemID')
    $iItemAction = DllStructGetData($tDIS, 'itemAction')
    $iItemState = DllStructGetData($tDIS, 'itemState')
    $hWndItem = DllStructGetData($tDIS, 'hwndItem')
    $hDC = DllStructGetData($tDIS, 'hDC')
    $tRect = DllStructCreate($tagRECT)

    If $iCtlType = $ODT_COMBOBOX Then
        Switch $iCtlID
            Case $ComboBox
                For $i = 1 To 4
                    DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                    $aRect[$i - 1] = DllStructGetData($tRect, $i)
                Next

                _GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

                If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                    $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                    $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                    _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                    _WinAPI_SelectObject($hDC, $hOldPen)
                    _WinAPI_SelectObject($hDC, $hOldBrush)

                    $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                    $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                Else
                    $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                    $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                    _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                EndIf
                DllStructSetData($tRect, "Left", $aRect[0] + 4)
                DllStructSetData($tRect, "Top", $aRect[1] + 4)
                DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                _WinAPI_SetTextColor($hDC, $clrForeground)
                _WinAPI_SetBkColor($hDC, $clrBackground)
        EndSwitch
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_DRAWITEM

;guiregistermsg WM_MEASUREITEM
Func _WM_MEASUREITEM($hWnd, $iMsg, $iwParam, $ilParam)
    Local Const $tagMEASUREITEMSTRUCT = _
            'uint CtlType;' & _
            'uint CtlID;' & _
            'uint itemID;' & _
            'uint itemWidth;' & _
            'uint itemHeight;' & _
            'ulong_ptr itemData;'

    Local Const $ODT_COMBOBOX = 3

    Local $tMIS = DllStructCreate($tagMEASUREITEMSTRUCT, $ilParam)
    Local $iCtlType, $iCtlID, $iItemID, $iItemWidth, $iItemHeight
    Local $hComboBox
    Local $tSize
    Local $sText

    $iCtlType = DllStructGetData($tMIS, 'CtlType')
    $iCtlID = DllStructGetData($tMIS, 'CtlID')
    $iItemID = DllStructGetData($tMIS, 'itemID')
    $iItemWidth = DllStructGetData($tMIS, 'itemWidth')
    $iItemHeight = DllStructGetData($tMIS, 'itemHeight')
    $hComboBox = GUICtrlGetHandle($iCtlID)

    If $iCtlType = $ODT_COMBOBOX Then
        DllStructSetData($tMIS, 'itemHeight', 30) ; 20 -> 30 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    EndIf

    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_MEASUREITEM

Func _WinAPI_Rectangle_Pts($hDC, $iLeft, $iTop, $iRight, $iBottom)
    Local $aResult = DllCall("gdi32.dll", "int", "Rectangle", "hwnd", $hDC, "int", $iLeft, "int", $iTop, "int", $iRight, "int", $iBottom)

    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0] <> 0
EndFunc   ;==>_WinAPI_Rectangle_Pts

Func _onExit()
    _WinAPI_DeleteObject($hPen)
    _WinAPI_DeleteObject($hBrushSel)
    _WinAPI_DeleteObject($hBrushNorm)

    Exit
EndFunc   ;==>_onExit

Func _customcombobox_cleanup()
    GUIRegisterMsg($WM_MEASUREITEM, '')

    GUIRegisterMsg($WM_DRAWITEM, '')
EndFunc   ;==>_customcombobox_cleanup

Nice combobox.

Link to comment
Share on other sites

I've figured it out. This command interacts too much with your ComboBox at the same time as you are owner-drawing it:

_GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)

If you extracts the texts from an array it's working:

#include <GUIComboBox.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <WinAPIShellEx.au3>
#include <WinAPIGdi.au3>

Global Const $clrWindowText = 0xEEEEEE
Global Const $clrHighlightText = 0xFFFFFF
Global Const $clrHighlight = 0x777777
Global Const $clrWindow = 0x444444

Global $hBrushNorm = _WinAPI_CreateSolidBrush($clrWindow)
Global $hBrushSel = _WinAPI_CreateSolidBrush($clrHighlight)
Global $hPen = _WinAPI_CreatePen($PS_SOLID, 2, $clrHighlight)

Global $hGUI, $ComboBox, $hComboBox
Global $windowcallback, $pwindowcallback, $combocallback, $pcombocallback
Global $comboOpen, $comboChanged, $comboHover

Global $aTexts = [ "Apple", "Banana", "Orange", "Pear", "Plum", "WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon" ]

OnAutoItExitRegister("_customcombobox_cleanup") ; OnAutoItExitRegister should be placed here, not in the function <<<<<<<<<<<<<<<<<<<<<

Example()

Func Example()
    $hGUI = GUICreate('Test', 220, 300)

    ;subclass the GUI
    ; The subclass must be implemented here to be able to respond to $WM_MEASUREITEM messages when the Combobox is created
    $windowcallback = DllCallbackRegister("_customCombobox_WindowCallback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pwindowcallback = DllCallbackGetPtr($windowcallback)
    _WinAPI_SetWindowSubclass($hGUI, $pwindowcallback, 1, 0) ; $iSubclassId = 1, $pData = 0 ($ComboBox is not available)

    $ComboBox = GUICtrlCreateCombo('', 10, 10, 200, 300, BitOR($WS_CHILD, $CBS_OWNERDRAWVARIABLE, $CBS_HASSTRINGS, $CBS_DROPDOWNLIST))
    GUICtrlSetData($ComboBox, "Apple|Banana|Orange|Pear|Plum|WatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelonWatermelon")
    $hComboBox = GUICtrlGetHandle($ComboBox)

    ;subclass combobox for WM_PAINT
    $combocallback = DllCallbackRegister("_customCombobox_Callback", "lresult", "hwnd;uint;wparam;lparam;uint_ptr;dword_ptr")
    $pcombocallback = DllCallbackGetPtr($combocallback)
    _WinAPI_SetWindowSubclass(GUICtrlGetHandle($ComboBox), $pcombocallback, 2, $ComboBox) ; $iSubclassId = 2, $pData = CtrlID

    GUISetState()
    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                _onExit()
        EndSwitch
    WEnd
EndFunc   ;==>Example

;callback function for combobox
Func _customCombobox_Callback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    Local $tPAINTSTRUCT, $hDC

;~  ConsoleWrite(Hex($iMsg)&@CRLF)
    If $iMsg == $WM_PAINT Then
        ;within the scope of this function, 'background' refers to the space between the selected item and the border
        $buttonWidth = _WinAPI_GetSystemMetrics($SM_CXVSCROLL)
        $hDC = _WinAPI_BeginPaint($hWnd, $tPAINTSTRUCT)

        ;Get client rect
        $cRect = _WinAPI_GetClientRect($hWnd)

        ;shrink rect by 3 (space for border and background)
        _WinAPI_InflateRect ( $cRect, -3, -3 )
        ;shrink right side (space for button)
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")-$buttonWidth-1)

        ;remove border, button, and background from clipping region
        _WinAPI_IntersectClipRect ( $hDC, $cRect )

        ;draw the default combobox using the $hDC
        DllCall( "comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $hDC, "lparam", $lParam )

        ;remove the clipping region
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;get area between middle and border (our background)
        $cRect = _WinAPI_GetClientRect($hWnd)
        _WinAPI_InflateRect ( $cRect, -1, -1 )
        _WinAPI_IntersectClipRect ( $hDC, $cRect )
        _WinAPI_InflateRect ( $cRect, -2, -2 )
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")+1)
        _WinAPI_ExcludeClipRect($hDC, $cRect)
        _WinAPI_InflateRect ( $cRect, 2, 2 )
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")-1-$buttonWidth-3)
        ;draw the background
        _WinAPI_FillRect($hDC, DllStructGetPtr($cRect), $hBrushNorm)

        ;reset the clipping region again
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;get the rect for the button and set clipping region
        $bRect = _WinAPI_GetClientRect($hWnd)
        DllStructSetData($bRect, "Left", DllStructGetData($bRect, "Right")-1-$buttonWidth-3)
        DllStructSetData($bRect, "Right", DllStructGetData($bRect, "Right")-1)
        DllStructSetData($bRect, "Top", DllStructGetData($bRect, "Top")+1)
        DllStructSetData($bRect, "Bottom", DllStructGetData($bRect, "Bottom")-1)
        _WinAPI_IntersectClipRect ( $hDC, $bRect )
        ;draw the button
        If $comboHover Then
            $buttonColor = 0xABABAB
            $borderColor = 0x222222
            $arrowColor = 0xEFEFEF
        Else
            $buttonColor = 0x858585
            $borderColor = 0x222222
            $arrowColor = 0xBBBBBB
        EndIf
        $hBrushButton = _WinAPI_CreateSolidBrush($buttonColor)
        _WinAPI_FillRect($hDC, DllStructGetPtr($bRect), $hBrushNorm)

        ;Create the arrow path
        $leftpos = DllStructGetData($bRect, "Left")
        $toppos = DllStructGetData($bRect, "Top")
        $buttonMiddle = ($buttonWidth+3)/2
        $buttonVMiddle = (DllStructGetData($cRect, "Bottom")-$toppos)/2
        $arrowWidth = 12
        $arrowHeight = 12
        _WinAPI_BeginPath($hDC)
        _WinAPI_MoveTo($hDC, $leftpos+$buttonMiddle, $toppos+$buttonVMiddle+$arrowHeight/2)
        _WinAPI_LineTo($hDC, $leftpos+$buttonMiddle+$arrowWidth/2, $toppos++$buttonVMiddle-$arrowHeight/2)
        _WinAPI_LineTo($hDC, $leftpos+$buttonMiddle-$arrowWidth/2, $toppos++$buttonVMiddle-$arrowHeight/2)
        _WinAPI_CloseFigure($hDC)
        _WinAPI_EndPath($hDC)

        ;draw the arrow
        $hOldBrush = _WinAPI_SelectObject($hDC, _WinAPI_GetStockObject($DC_BRUSH))
        $hOldPen = _WinAPI_SelectObject($hDC, _WinAPI_GetStockObject($DC_PEN))
        _WinAPI_SetDCBrushColor($hDC, $arrowColor)
        _WinAPI_SetDCPenColor($hDC, 0x222222)
        _WinAPI_StrokeAndFillPath($hDC)
        _WinAPI_SelectObject($hDC, $hOldBrush)
        _WinAPI_SelectObject($hDC, $hOldPen)

        ;remove the clipping region
        _WinAPI_SelectClipRgn ( $hDC, Null )

        ;remove inside from clipping region (keep only the border)
        DllStructSetData($cRect, "Right", DllStructGetData($cRect, "Right")+_WinAPI_GetSystemMetrics($SM_CXVSCROLL)+3)
        _WinAPI_ExcludeClipRect($hDC, $cRect)
        $cRect = _WinAPI_GetClientRect($hWnd)

        ;create border brush
        $hBrushBorder = _WinAPI_CreateSolidBrush($borderColor)

        ;get full rect size again and fill border
        $cRect = _WinAPI_GetClientRect($hWnd)
        _WinAPI_FillRect($hDC, DllStructGetPtr($cRect), $hBrushBorder)

        ;clean up
        _WinAPI_DeleteObject($hBrushBorder)
        _WinAPI_DeleteObject($hBrushButton)
        _WinAPI_DeleteObject($hOldBrush)
        _WinAPI_DeleteObject($hOldPen)

        _WinAPI_EndPaint($hWnd, $tPAINTSTRUCT)
        Return 0
    ElseIf $iMsg == $WM_MOUSEFIRST  Then
        $comboHover = 1
    ElseIf $iMsg == $WM_MOUSELEAVE  Then
        $comboHover = 0
    EndIf

    ;run default subclass proc
    Return DllCall( "comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam )[0]
EndFunc   ;==>_customCombobox_Callback

;subclassed window function --> GUI
Func _customCombobox_WindowCallback($hWnd, $iMsg, $wParam, $lParam, $iSubclassId, $pData)
    If $iMsg <> $WM_DRAWITEM And $iMsg <> $WM_MEASUREITEM Then Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]

    Local Const $ODT_COMBOBOX = 3
    Local Const $ODT_STATIC = 5
    Local Const $ODS_SELECTED = 1
    Local Const $ODS_COMBOBOXEDIT = 4096

    Switch $iMsg
        Case $WM_DRAWITEM
            Local Const $tagDRAWITEMSTRUCT = _
                    'uint CtlType;' & _
                    'uint CtlID;' & _
                    'uint itemID;' & _
                    'uint itemAction;' & _
                    'uint itemState;' & _
                    'hwnd hwndItem;' & _
                    'hwnd hDC;' & _
                    $tagRECT & _
                    ';ulong_ptr itemData;'

            Local $tDIS
            Local $iCtlType, $iCtlID, $iItemID, $iItemAction, $iItemState
            Local $clrForeground, $clrBackground
            Local $hWndItem, $hDC, $hOldPen, $hOldBrush
            Local $tRect, $aRect[4]
            Local $sText, $iCode, $iIDFrom

            $tDIS = DllStructCreate($tagDRAWITEMSTRUCT, $lParam)
            $iCtlType = DllStructGetData($tDIS, 'CtlType')
            $iCtlID = DllStructGetData($tDIS, 'CtlID')
            $iItemID = DllStructGetData($tDIS, 'itemID')
            $iItemAction = DllStructGetData($tDIS, 'itemAction')
            $iItemState = DllStructGetData($tDIS, 'itemState')
            $hWndItem = DllStructGetData($tDIS, 'hwndItem')
            $hDC = DllStructGetData($tDIS, 'hDC')
            $tRect = DllStructCreate($tagRECT)

            If $iCtlType = $ODT_COMBOBOX Then

                If $iCtlID = $ComboBox Then ;if my combobox
                    For $i = 1 To 4
                        DllStructSetData($tRect, $i, DllStructGetData($tDIS, $i + 7))
                        $aRect[$i - 1] = DllStructGetData($tRect, $i)
                    Next

                    ;_GUICtrlComboBox_GetLBText($hWndItem, $iItemID, $sText)
                    $sText = $iItemID < 6 ? $aTexts[$iItemID] : ""

                    If BitAND($iItemState, $ODS_SELECTED) And Not BitAND($iItemState, $ODS_COMBOBOXEDIT) Then
                        $hOldBrush = _WinAPI_SelectObject($hDC, $hBrushSel)
                        $hOldPen = _WinAPI_SelectObject($hDC, $hPen)
                        _WinAPI_Rectangle_Pts($hDC, $aRect[0] + 1, $aRect[1] + 1, $aRect[2], $aRect[3])
                        _WinAPI_SelectObject($hDC, $hOldPen)
                        _WinAPI_SelectObject($hDC, $hOldBrush)

                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrHighlightText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrHighlight)
                    Else
                        $clrForeground = _WinAPI_SetTextColor($hDC, $clrWindowText)
                        $clrBackground = _WinAPI_SetBkColor($hDC, $clrWindow)
                        _WinAPI_FillRect($hDC, DllStructGetPtr($tRect), $hBrushNorm)
                    EndIf
                    DllStructSetData($tRect, "Left", $aRect[0] + 4)
                    DllStructSetData($tRect, "Top", $aRect[1] + 4)
                    DllStructSetData($tRect, "Bottom", $aRect[3] - 2)

                    _WinAPI_DrawText($hDC, $sText, $tRect, BitOR($DT_LEFT, $DT_VCENTER))
                    _WinAPI_SetTextColor($hDC, $clrForeground)
                    _WinAPI_SetBkColor($hDC, $clrBackground)
                    Return True
                EndIf

            EndIf

        Case $WM_MEASUREITEM
            Local Const $tagMEASUREITEMSTRUCT = _
                    'uint CtlType;' & _
                    'uint CtlID;' & _
                    'uint itemID;' & _
                    'uint itemWidth;' & _
                    'uint itemHeight;' & _
                    'ulong_ptr itemData;'

            Local $tMIS = DllStructCreate($tagMEASUREITEMSTRUCT, $lparam)
            Local $iCtlType, $iCtlID, $iItemID, $iItemWidth, $iItemHeight
            Local $hComboBox
            Local $tSize
            Local $sText

            $iCtlType = DllStructGetData($tMIS, 'CtlType')
            $iCtlID = DllStructGetData($tMIS, 'CtlID')
            $iItemID = DllStructGetData($tMIS, 'itemID')
            $iItemWidth = DllStructGetData($tMIS, 'itemWidth')
            $iItemHeight = DllStructGetData($tMIS, 'itemHeight')
            $hComboBox = GUICtrlGetHandle($iCtlID)

            If $iCtlType = $ODT_COMBOBOX Then
                DllStructSetData($tMIS, 'itemHeight', 30) ; 20 -> 30 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            EndIf
    EndSwitch

    ;default subclass proc
    Return DllCall("comctl32.dll", "lresult", "DefSubclassProc", "hwnd", $hWnd, "uint", $iMsg, "wparam", $wParam, "lparam", $lParam)[0]
EndFunc   ;==>_customCombobox_WindowCallback

Func _WinAPI_Rectangle_Pts($hDC, $iLeft, $iTop, $iRight, $iBottom)
    Local $aResult = DllCall("gdi32.dll", "int", "Rectangle", "hwnd", $hDC, "int", $iLeft, "int", $iTop, "int", $iRight, "int", $iBottom)

    If @error Then Return SetError(@error, @extended, 0)
    Return $aResult[0] <> 0
EndFunc   ;==>_WinAPI_Rectangle_Pts

Func _onExit()
    _WinAPI_DeleteObject($hPen)
    _WinAPI_DeleteObject($hBrushSel)
    _WinAPI_DeleteObject($hBrushNorm)

    Exit
EndFunc   ;==>_onExit

Func _customcombobox_cleanup()
    _WinAPI_RemoveWindowSubclass($hGUI, $pwindowcallback, 1)
    DllCallbackFree($windowcallback)
EndFunc   ;==>_customcombobox_cleanup

It has nothing to do with WM_PAINT messages. Sorry.

Link to comment
Share on other sites

LarsJ, what an amazing discovery! Thank you so much.

Quick question... is it not necessary to do RemoveWindowSubclass and DllCallbackFree for the combobox callback?

Func _customcombobox_cleanup()
    _WinAPI_RemoveWindowSubclass($hGUI, $pcombocallback, 1)
    DllCallbackFree($combocallback)

    _WinAPI_RemoveWindowSubclass($hGUI, $pwindowcallback, 1)
    DllCallbackFree($windowcallback)
EndFunc   ;==>_customcombobox_cleanup

 

Link to comment
Share on other sites

You are welcome.

You can leave the cleanup to AutoIt. But I would add the code in a UDF.

Have you created a listbox scrollbar if you add more items?

Link to comment
Share on other sites

  • 4 months later...

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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...