Jump to content

Recommended Posts

Posted (edited)

Hi All,

This one is a supporting thread for Ticket #4080. Feel free to add thoughts/comments...

New functions: 

  • _GUICtrlTreeView_GetExtendedStyle
  • _GUICtrlTreeView_SetExtendedStyle
  • _GUICtrlTreeView_SetCheck (not sure how to name this?)
    • the existing _GUICtrlTreeView_SetChecked takes a booloean, and sets the state to either 0x1000 or 0x2000.

New Constants:

  • Messages:
    • $TVM_SETEXTENDEDSTYLE
    • $TVM_GETEXTENDEDSTYLE
  • EXStyles
    • Checkboxes
      • $TVS_EX_PARTIALCHECKBOXES
      • $TVS_EX_EXCLUSIONCHECKBOXES
      • $TVS_EX_DIMMEDCHECKBOXES
    • $TVS_EX_DOUBLEBUFFER 
    • $TVS_EX_NOINDENTSTATE
    • $TVS_EX_AUTOHSCROLL
    • $TVS_EX_FADEINOUTEXPANDOS

Untested as of yet: (at least by me!)

  • $TVS_EX_DRAWIMAGEASYNC
  • $TVS_EX_RICHTOOLTIP

Excluded Styles (to not support.)

  • TVS_EX_NOSINGLECOLLAPSE (MS says "apps shouldn't use")
  • TVS_EX_MULTISELECT (MS says "do not use")

Examples:
 

Spoiler

Example 1: Extended checkboxes and _GUICtrlTreeView_GetExtendedStyle

#include <GuiTreeView.au3>
#include <GuiConstants.au3>

Global Const $TVM_SETEXTENDEDSTYLE = 0x112C
Global Const $TVM_GETEXTENDEDSTYLE = 0x112D

;Checkbox Options - Treeview must NOT have TVS_CHECKBOXES for this.
Global Const $TVS_EX_PARTIALCHECKBOXES = 0x0080
Global Const $TVS_EX_EXCLUSIONCHECKBOXES = 0x0100
Global Const $TVS_EX_DIMMEDCHECKBOXES = 0x0200 ;a node is selected because its parent is selected. Looks like an ordinary tick on win11.

;By default clicking items will cycle through the states.
;Programatically setting states on click isn't really straight forward - you have to handle NM_CLICK and TVN_KEYDOWN for spacebar.
;if its useful for the UDF, I've also written some code for 3-state TVIs that cascades checks down the tree, and updates the parents ...

Example()

Func Example()
    Local $hGUI = GUICreate("Example", 300, 200)
    Local $hTreeView = _GUICtrlTreeView_Create($hGUI, 4, 4, 292, 192)
    _GUICtrlTreeView_SetExtendedStyle($hTreeView, BitOR($TVS_EX_PARTIALCHECKBOXES, $TVS_EX_DIMMEDCHECKBOXES, $TVS_EX_EXCLUSIONCHECKBOXES))

    Local $iExStyle = _GUICtrlTreeView_GetExtendedStyle($hTreeView)
    ConsoleWrite("Extended Style = 0x" & Hex($iExStyle, 4) & @CRLF)
    ConsoleWrite("Support Partial Boxes = " & (BitAND($iExStyle, $TVS_EX_PARTIALCHECKBOXES) = $TVS_EX_PARTIALCHECKBOXES) & @CRLF)
    ConsoleWrite("Support Exclusion Boxes = " & (BitAND($iExStyle, $TVS_EX_EXCLUSIONCHECKBOXES) = $TVS_EX_EXCLUSIONCHECKBOXES) & @CRLF)
    ConsoleWrite("Support Dimmed Boxes = " & (BitAND($iExStyle, $TVS_EX_DIMMEDCHECKBOXES) = $TVS_EX_DIMMEDCHECKBOXES) & @CRLF)

    Local $hItem, $asCheckMarks[] = ["None", "Unchecked", "Checked", "Partial", "Dimmed", "Exclude"]
    For $i = 0 To 5
        $hItem = _GUICtrlTreeView_AddChild($hTreeView, $TVI_ROOT, _
            StringFormat("Index:%d   State:0x%04X   %s", $i, BitShift($i, -12), $asCheckMarks[$i]))
        _GUICtrlTreeView_SetCheck($hTreeView, $hItem, $i)
    Next

    GUISetState()
    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete($hGUI)
EndFunc

Func _GUICtrlTreeView_SetExtendedStyle($hTreeView, $iExStyle)
    Local $iResult = _SendMessage($hTreeView, $TVM_SETEXTENDEDSTYLE, 0x7FFD, $iExStyle)
    ;0x7FFD = bits to set. $TVS_EX_MULTISELECT (2) is unset as it's documented as "Do not Use."
    Return SetError(@error, @extended, $iResult)
EndFunc

Func _GUICtrlTreeView_GetExtendedStyle($hTreeView)
    Local $iResult = _SendMessage($hTreeView, $TVM_GETEXTENDEDSTYLE)
    Return SetError(@error, @extended, $iResult)
EndFunc


Func _GUICtrlTreeView_SetCheck($hWnd, $hItem, $iIndex)
    If Not IsHWnd($hItem) Then $hItem = _GUICtrlTreeView_GetItemHandle($hWnd, $hItem)
    If ($iIndex > 15) Or ($iIndex < 0) Then Return SetError(1, 0, False)

    Local $tItem = $__g_tTVItemEx
    DllStructSetData($tItem, "Mask", $TVIF_STATE)
    DllStructSetData($tItem, "hItem", $hItem)
    DllStructSetData($tItem, "State", BitShift($iIndex, -12)) ;bitshift = INDEXTOSTATEIMAGEMASK macro.
    ;(https://learn.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-indextostateimagemask)
    DllStructSetData($tItem, "StateMask", $TVIS_STATEIMAGEMASK)

    Return __GUICtrlTreeView_SetItem($hWnd, $tItem)
EndFunc

Example 2: TVS_EX_AUTOHSCROLL

#include <GuiTreeView.au3>
#include <GuiConstants.au3>
#include <ScrollbarConstants.au3>

Global Const $TVM_SETEXTENDEDSTYLE = 0x112C
Global Const $TVS_EX_AUTOHSCROLL = 0x0020

Example()

Func Example()
    Local $hGUI = GUICreate("Example", 300, 400)
    GUICtrlCreateLabel("With TVS_EX_AUTOHSCROLL", 4, 4, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView = _GUICtrlTreeView_Create($hGUI, 4, 26, 292, 172)

    GUICtrlCreateLabel("Without TVS_EX_AUTOHSCROLL", 4, 204, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView2 = _GUICtrlTreeView_Create($hGUI, 4, 226, 292, 172)
    _GUICtrlTreeView_SetExtendedStyle($hTreeView, $TVS_EX_AUTOHSCROLL)

    For $i = 0 To 5
        _GUICtrlTreeView_AddChild($hTreeView, $TVI_ROOT, "Some very long text is here to demonstrate auto horizontal scrolling")
        _GUICtrlTreeView_AddChild($hTreeView2, $TVI_ROOT, "Some very long text is here to demonstrate auto horizontal scrolling")
    Next

    ;Scroll right as starting point for demo.
    ;TreeView with $TVS_EX_AUTOHSCROLL will scroll back to the left with mouse hover.
    _SendMessage($hTreeView, $WM_HSCROLL, $SB_PAGERIGHT, 0)
    _SendMessage($hTreeView2, $WM_HSCROLL, $SB_PAGERIGHT, 0)

    GUISetState()

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete($hGUI)
EndFunc

Func _GUICtrlTreeView_SetExtendedStyle($hTreeView, $iExStyle)
    Local $iResult = _SendMessage($hTreeView, $TVM_SETEXTENDEDSTYLE, 0x07FD, $iExStyle)
    Return SetError(@error, @extended, $iResult)
EndFunc

Example 3: TVS_EX_FADEINOUTEXPANDOS and TVS_EX_DOUBLEBUFFER 

#include <GuiTreeView.au3>
#include <GuiConstants.au3>

Global Const $TVM_SETEXTENDEDSTYLE = 0x112C

Global Const $TVS_EX_DOUBLEBUFFER = 0x0004
Global Const $TVS_EX_FADEINOUTEXPANDOS = 0x0040

Example()

Func Example()
    ;click on items from treeview into another.

    Local $hGUI = GUICreate("Example", 300, 454)
    GUICtrlCreateLabel("With FADEINOUTEXPANDOS and DOUBLEBUFFER", 4, 4, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView = _GUICtrlTreeView_Create($hGUI, 4, 26, 292, 122)
    _GUICtrlTreeView_SetExtendedStyle($hTreeView, BitOR($TVS_EX_FADEINOUTEXPANDOS, $TVS_EX_DOUBLEBUFFER))

    GUICtrlCreateLabel("Without Double Buffer", 4, 154, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView2 = _GUICtrlTreeView_Create($hGUI, 4, 176, 292, 122)
    ;Flickers when you click away.
    _GUICtrlTreeView_SetExtendedStyle($hTreeView2, $TVS_EX_FADEINOUTEXPANDOS)

    GUICtrlCreateLabel("Without Expanded styles", 4, 304, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView3 = _GUICtrlTreeView_Create($hGUI, 4, 326, 292, 122)

    _AddItems($hTreeView)
    _AddItems($hTreeView2)
    _AddItems($hTreeView3)

    GUISetState()

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete($hGUI)
EndFunc

Func _AddItems($hTreeView)
    Local $hItem = $TVI_ROOT
    For $i = 0 To 5
        $hItem = _GUICtrlTreeView_AddChild($hTreeView, $hItem, "Tree View Item " & $i)
    Next
    _GUICtrlTreeView_Expand($hTreeView)
EndFunc

Func _GUICtrlTreeView_SetExtendedStyle($hTreeView, $iExStyle)
    Local $iResult = _SendMessage($hTreeView, $TVM_SETEXTENDEDSTYLE, 0x07FD, $iExStyle)
    Return SetError(@error, @extended, $iResult)
EndFunc

Example 4: TVS_EX_NOINDENTSTATE

#include <GuiTreeView.au3>
#include <GuiConstants.au3>
#include <ScrollBarConstants.au3>

Global Const $TVM_SETEXTENDEDSTYLE = 0x112C
Global Const $TVS_EX_NOINDENTSTATE = 0x0008

Example()

Func Example()
    Local $hGUI = GUICreate("Example", 300, 400)

    GUICtrlCreateLabel("With TVS_EX_NOINDENTSTATE", 4, 4, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView = _GUICtrlTreeView_Create($hGUI, 4, 26, 292, 172, BitOR($TVS_DEFAULT, $TVS_CHECKBOXES))
    _GUICtrlTreeView_SetExtendedStyle($hTreeView, $TVS_EX_NOINDENTSTATE)

    GUICtrlCreateLabel("Without TVS_EX_NOINDENTSTATE", 4, 204, 292, 18, BitOR($SS_CENTER, $SS_CENTERIMAGE))
    Local $hTreeView2 = _GUICtrlTreeView_Create($hGUI, 4, 226, 292, 172, BitOR($TVS_DEFAULT, $TVS_CHECKBOXES))

    _AddItems($hTreeView)
    _AddItems($hTreeView2)

    GUISetState()

    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
    GUIDelete($hGUI)
EndFunc

Func _AddItems($hTreeView)
    Local $hItem = $TVI_ROOT
    For $i = 0 To 5
        $hItem = _GUICtrlTreeView_AddChild($hTreeView, $hItem, "Tree View Item " & $i)
    Next
    _GUICtrlTreeView_Expand($hTreeView)
EndFunc

Func _GUICtrlTreeView_SetExtendedStyle($hTreeView, $iExStyle)
    Local $iResult = _SendMessage($hTreeView, $TVM_SETEXTENDEDSTYLE, 0x07FD, $iExStyle)
    Return SetError(@error, @extended, $iResult)
EndFunc


 

Edited by MattyD
techicalities
Posted

Some truly excellent work, Matty. I remember wanting to use TVS_EX_DOUBLEBUFFER somewhat recently but couldn't understand why it wasn't available in AutoIt. The addition of the various checkbox states is also incredibly helpful. Thanks for bringing all of these TreeView extended styles to AutoIt.

Posted

no worries,

FWIW here's that first attempt at cascading boxes, which makes the indeterminate box actually useful!

The "box clicked by mouse" logic was borrowed from here... I probably should've attempted to suppress the auto-state change rather than fixing things after the fact- but hey.

#include <GuiTreeView.au3>
#include <GuiConstants.au3>
#include <WinAPIvkeysConstants.au3>
#include <WinAPISysWin.au3>
#include <ScrollbarConstants.au3>

Global Const $TVM_SETEXTENDEDSTYLE = 0x112C
Global Const $TVM_GETEXTENDEDSTYLE = 0x112D
;~ Global Const $TVN_ITEMCHANGING = ($TVN_FIRST - 17)

Global Const $TVS_EX_PARTIALCHECKBOXES = 0x0080
Global Const $tagTVKEYDOWN = "struct;" & $tagNMHDR & ";word wVKey;uint flags;endstruct"

Global Const $UM_CHECKSTATECHANGE = $WM_USER + 100 ;user-defined message

Example()

Func Example()
    GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY")
    GUIRegisterMsg($UM_CHECKSTATECHANGE, "UM_CHECKSTATECHANGE")

    Local $hGUI = GUICreate("Example", 300, 400)
    Local $hTreeView = _GUICtrlTreeView_Create($hGUI, 4, 4, 292, 392)
    _GUICtrlTreeView_SetExtendedStyle($hTreeView, $TVS_EX_PARTIALCHECKBOXES)

    Local $ahItems[20] = [$TVI_ROOT]
    For $i = 0 To UBound($ahItems) - 1
        $ahItems[$i] = _GUICtrlTreeView_AddChild($hTreeView, $ahItems[Floor($i/3)], "item " & $i)
    Next
    _GUICtrlTreeView_Expand($hTreeView)

    GUISetState()
    Do
    Until GUIGetMsg() = $GUI_EVENT_CLOSE

    GUIDelete($hGUI)

    GUIRegisterMsg($WM_NOTIFY, "")
    GUIRegisterMsg($UM_CHECKSTATECHANGE, "")
EndFunc

Func _GUICtrlTreeView_GetExtendedStyle($hTreeView)
    Local $iResult = _SendMessage($hTreeView, $TVM_GETEXTENDEDSTYLE)
    Return SetError(@error, @extended, $iResult)
EndFunc

Func _GUICtrlTreeView_SetExtendedStyle($hTreeView, $iExStyle)
    Local $iResult = _SendMessage($hTreeView, $TVM_SETEXTENDEDSTYLE, 0x7FFD, $iExStyle)
    Return SetError(@error, @extended, $iResult)
EndFunc

Func _GUICtrlTreeView_SetCheck($hWnd, $hItem, $iIndex)
    If Not IsHWnd($hItem) Then $hItem = _GUICtrlTreeView_GetItemHandle($hWnd, $hItem)
    If ($iIndex > 15) Or ($iIndex < 0) Then Return SetError(1, 0, False)

    Local $tItem = $__g_tTVItemEx
    DllStructSetData($tItem, "Mask", $TVIF_STATE)
    DllStructSetData($tItem, "hItem", $hItem)
    DllStructSetData($tItem, "State", BitShift($iIndex, -12))
    DllStructSetData($tItem, "StateMask", $TVIS_STATEIMAGEMASK)

    Return __GUICtrlTreeView_SetItem($hWnd, $tItem)
EndFunc

Func _GUICtrlTreeView_GetCheck($hWnd, $hItem)
    If Not IsHWnd($hItem) Then $hItem = _GUICtrlTreeView_GetItemHandle($hWnd, $hItem)

    Local $tItem = $__g_tTVItemEx
    DllStructSetData($tItem, "Mask", $TVIF_STATE)
    DllStructSetData($tItem, "hItem", $hItem)
    DllStructSetData($tItem, "StateMask", $TVIS_STATEIMAGEMASK)
    __GUICtrlTreeView_GetItem($hWnd, $tItem)

    Return BitShift(DllStructGetData($tItem, "State"), 12)
EndFunc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam

    Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)

    Switch _WinAPI_GetClassName($tNMHDR.hWndFrom)
        Case "SysTreeView32"
            ;Check this is a 3-state TreeView.
            Local $iExStyle = _GUICtrlTreeView_GetExtendedStyle($tNMHDR.hWndFrom)
            If BitAND(0x380, $iExStyle) = $TVS_EX_PARTIALCHECKBOXES Then

                Switch $tNMHDR.Code
                    Case $NM_CLICK

                        ;Get mouse location when msg was fired.
                        Local $iMsgPos = _WinAPI_GetMessagePos()
                        Local $tPoint = DllStructCreate($tagPOINT)
                        $tPoint.X = _WinAPI_WordToShort(_WinAPI_LoWord($iMsgPos))
                        $tPoint.Y = _WinAPI_WordToShort(_WinAPI_HiWord($iMsgPos))
                        _WinAPI_ScreenToClient($tNMHDR.hWndFrom, $tPoint) ;Relative to control

                        ;Did we click a checkbox?
                        Local $tTVHITTESTINFO = _GUICtrlTreeView_HitTestEx($tNMHDR.hWndFrom, $tPoint.X, $tPoint.Y)
                        If BitAND($tTVHITTESTINFO.Flags, $TVHT_ONITEMSTATEICON) Then _
                            _WinAPI_PostMessage($hWnd, $UM_CHECKSTATECHANGE, $tNMHDR.hWndFrom, $tTVHITTESTINFO.Item)
                            ;At this point the checkbox hasn't changed state. We don't want to cascade now - hence the postmsg.

                    Case $TVN_KEYDOWN
                        Local $tVKeyDown = DllStructCreate($tagTVKEYDOWN, $lParam)
                        If $tVKeyDown.wVKey = $VK_SPACE Then _
                            _WinAPI_PostMessage($hWnd, $UM_CHECKSTATECHANGE, $tNMHDR.hWndFrom, _GUICtrlTreeView_GetSelection($tNMHDR.hWndFrom))

                EndSwitch
            EndIf
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY

Func UM_CHECKSTATECHANGE($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam
    _GUICtrlTreeView_BeginUpdate($wParam)

    Local $iTgtState = _GUICtrlTreeView_GetCheck($wParam, $lParam)

    Switch $iTgtState
        Case 1
            ; Item was indeterminate but click changed to unchecked. Correct this so indeterminate -> checked.
            $iTgtState = 2

        Case 3
            ; Item was checked, but click changed to indeterminate. Correct this so check -> unchecked.
            $iTgtState = 1
    EndSwitch

    _GUICtrlTreeView_SetCheck($wParam, $lParam, $iTgtState)

    Local $hCurrent, $hNext, $hParent
    
    ;Work up the chain...
    ;Get the first sibling on our checkbox's level.
    $hParent = _GUICtrlTreeView_GetParentHandle($wParam, $lParam)
    $hNext = _GUICtrlTreeView_GetFirstChild($wParam, $hParent)
    While $hNext
        ;Check all siblings on this level.
        ;If the states don't all match, then the parent should be indeterminate. Otherwise it should mimic its children.

        $hCurrent = $hNext
        If _GUICtrlTreeView_GetCheck($wParam, $hCurrent) <> $iTgtState Then $iTgtState = 3
        $hNext = _GUICtrlTreeView_GetNextSibling($wParam, $hCurrent)

        ;If there is a indeterminate box on this level, then the parent will also be indeterminate. So no need to check further in that case.
        If Not $hNext Or $iTgtState = 3 Then
            ;set the parent's state.
            $hParent = _GUICtrlTreeView_GetParentHandle($wParam, $hCurrent)
            If Not $hParent Then ExitLoop

            $hCurrent = $hParent
            If _GUICtrlTreeView_GetCheck($wParam, $hCurrent) <> $iTgtState Then _
                _GUICtrlTreeView_SetCheck($wParam, $hCurrent, $iTgtState)

            ;Get the first sibling on the parent's level.
            $hParent = _GUICtrlTreeView_GetParentHandle($wParam, $hCurrent)
            $hNext = _GUICtrlTreeView_GetFirstChild($wParam, $hParent)
        EndIf
    WEnd

    ;Cascade Down
    $iTgtState = _GUICtrlTreeView_GetCheck($wParam, $lParam)

    ;Get first descendant.
    $hNext = _GUICtrlTreeView_GetFirstChild($wParam, $lParam)
    While $hNext
        $hCurrent = $hNext
        _GUICtrlTreeView_SetCheck($wParam, $hCurrent, $iTgtState)

        ;Go down a level if possible.
        $hNext = _GUICtrlTreeView_GetFirstChild($wParam, $hCurrent)
        ;otherwise move across this level.
        If Not $hNext Then $hNext = _GUICtrlTreeView_GetNextSibling($wParam, $hCurrent)

        While Not $hNext
            ;done on this level and everything below, - go back to the parent (which has already been processed).
            $hParent = _GUICtrlTreeView_GetParentHandle($wParam, $hCurrent)
            If $hParent = $lParam Then ExitLoop ;Back at our original checkbox. breakout!

            ;Continue on the parent's level if possible.
            $hNext = _GUICtrlTreeView_GetNextSibling($wParam, $hParent)
            $hCurrent = $hParent
        WEnd
    WEnd

    _GUICtrlTreeView_EndUpdate($wParam)
EndFunc

Func _WinAPI_GetMessagePos()
    Local $aCall = DllCall("user32.dll", "dword", "GetMessagePos")
    If @error Then Return SetError(@error, @extended, $aCall)
    Return $aCall[0]
EndFunc

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...