TreeView with tri-state checkboxes - anyone know how?
TreeView with tri-state checkboxes - anyone know how?
I'd like to have tri-state checkboxes in a Treeview control, and it looks like it's feasible, but I don't know how to do it. User rbrtryn seemed to have a solution in 2013 using TVM_SETITEM message. Unfortunately, the link to his Dropbox where he had an example is now 404, and Windows API calls are a dark art to me. If anyone has a solution, it would be much appreciated!
Re: TreeView with tri-state checkboxes - anyone know how?
We are having some horrible weather right now, so i let myself get carried away with this one.
If you don't use icons on your treeview, then the following code can mimic what you ask. Right now there are 5 available check states, but you can add or replace icons in the iconLookup variable as needed.
It hinges on a class to keep track of the state of each node.
There is a slight bug, however. If you double click on a standard checkbox it will just toggle the value, but if you double click on an icon it will collapse the row. Maybe not a big deal... i wonder if there is a way to intercept and block that action.
If you don't use icons on your treeview, then the following code can mimic what you ask. Right now there are 5 available check states, but you can add or replace icons in the iconLookup variable as needed.
It hinges on a class to keep track of the state of each node.
There is a slight bug, however. If you double click on a standard checkbox it will just toggle the value, but if you double click on an icon it will collapse the row. Maybe not a big deal... i wonder if there is a way to intercept and block that action.
Code: Select all
;"C:\Windows\ShellNew\Template.ahk"
#NoEnv
#SingleInstance Force
SetBatchLines -1
coordmode, tooltip , Window
;struct used for determining if clicking "CHECKBOX" or "ROW"
;https://www.autohotkey.com/boards/viewtopic.php?t=61294
global TVHITTESTINFO
VarSetCapacity(TVHITTESTINFO, A_PtrSize=8?24:16)
NumPut(0, &TVHITTESTINFO, 0, "Int") ;pt ;x
NumPut(0, &TVHITTESTINFO, 4, "Int") ;pt ;y
NumPut(0, &TVHITTESTINFO, 8, "UInt") ;flags
NumPut(0, &TVHITTESTINFO, (A_PtrSize = 8) ? 16 : 12, "Ptr") ;hItem
;icons to replace checkbox
ImageListID := IL_Create()
;variable that stores available checkstates
global iconLookup := ["empty.ico","cross.ico","filled.ico","green.ico","red.ico"]
for index,fn in iconLookup
{
IL_Add(ImageListID, iconLookup[index], index, True)
}
margin := 5
w := 300
h := 400
gw := margin*3 + w*2
gh := margin*2 + h
Gui, Add, TreeView,x%margin% y%margin% h%h% w%w% hwndtv1HWND gtvClickEvent vgTv1 altsubmit ImageList%ImageListID%
Gui, Add, TreeView,yp hp wp x+%margin% hwndtv2HWND gtv2ClickEvent vgTv2 altsubmit ImageList%ImageListID%
Gui, Show ,w%gw% h%gh%,Treeview Test
;build first treeview
tv := new tvObj(tv1HWND)
a := tv.addNode("A",icon := 1)
a.addNode("AA",icon := 2)
b := tv.addNode("B",icon := 3)
bb := b.addNode("BB",icon := 4)
bb.addNode("BBB",icon := 5)
cc := b.addNode("CC",icon := 5)
cc.addNode("CCC",icon := 4)
tv.expandChildren()
;build second treeview
tv2 := new tvObj(tv2HWND)
a := tv2.addNode("A2",icon := 1)
a.addNode("AA2",icon := 2)
b := tv2.addNode("B2",icon := 3)
bb := b.addNode("BB2",icon := 4)
bb.addNode("BBB2",icon := 5)
cc := b.addNode("CC2",icon := 5)
cc.addNode("CCC2",icon := 4)
tv2.expandChildren()
return
tvClickEvent:
if(A_GuiEvent == "Normal")
{
tooltip % A_GuiControl . ":" . A_GuiEvent . ":" . A_EventInfo, 0, -25
performCheckOperation(tv,A_EventInfo)
}
else
{
;tooltip % "UNHANDLED EVENT : " . A_GuiEvent ,0,-50
}
setTimer clearTT, -2000
return
tv2ClickEvent:
if(A_GuiEvent == "Normal")
{
tooltip % A_GuiControl . ":" . A_GuiEvent . ":" . A_EventInfo , 0, -25
performCheckOperation(tv2,A_EventInfo)
}
else
{
;tooltip % "UNHANDLED EVENT : " . A_GuiEvent ,0,-50
}
setTimer clearTT, -2000
return
clearTT:
tooltip
return
performCheckOperation(activeTV,tvID) ;increment the checkbox to next icon
{
hoveredFlag := whatInRowIsHovered(activeTV.rootHWND) ;make sure they are hovering over the icon when clicked (icon: flag = 2)
if(hoveredFlag == 2)
{
selNode := activeTV.getNode(tvID) ;search treeview obj for node that matches id
incrementIcon(selNode) ;update the icon and node properties
return true
}
return false
}
whatInRowIsHovered(ctrlHWND)
{
;https://www.autohotkey.com/boards/viewtopic.php?t=61294
static TVM_HITTEST := 0x1111
coordmode mouse,Window
MouseGetPos ,mx,my
ControlGetPos ,cx,cy,,,, ahk_id %ctrlHWND%
x := mx - cx
y := my - cy
;ToolTip % cx . "," . cy . "`n" . mx . "," . my . "`n" . x . "," . y
NumPut(x, &TVHITTESTINFO, 0, "Int") ;pt ;x
NumPut(y, &TVHITTESTINFO, 4, "Int") ;pt ;y
SendMessage % TVM_HITTEST, 0, % &TVHITTESTINFO,, % "ahk_id " . ctrlHWND
;msgbox % "hItem > " . (hItem:=ErrorLevel)
;msgbox % "flag > " . NumGet(TVHITTESTINFO, 8, "UInt")
flag := NumGet(TVHITTESTINFO, 8, "UInt")
;tooltip % "flag > " . flag
return flag
}
incrementIcon(selecteNode)
{
curIcon := selecteNode.icon
curIcon++
if(curIcon > iconLookup.count())
{
curIcon := 1
}
selecteNode.changeIcon(curIcon)
selecteNode.changeText(strSplit(selecteNode.text," ")[1] . " - MODIFIED TO ICON : " . iconLookup[curIcon])
}
GuiClose:
if(getKeyState("CTRL"))
{
reload
}
ExitApp
return
class tvObj
{
__new(rootHWND,parent := 0,text := "",icon := 1)
{
this.rootHWND := rootHWND
this.children := {}
this.parent := parent
if(text) ;assuming root node will be only node without text
{
this.icon := icon
this.text := text
this.tvID := TV_Add(this.text, this.parent,"Icon" . this.icon)
}
}
activate()
{
Gui,TreeView,% this.rootHWND
}
addNode(text,icon := 1)
{
this.activate()
tvNode := new tvObj(this.rootHWND,this.tvID,text,icon)
this.children[tvNode.tvID] := tvNode
return tvNode
}
expandChildren()
{
this.activate()
for tvID,node in this.children
{
TV_Modify(tvID,"Expand")
node.expandChildren()
}
}
getNode(tvIdSearch)
{
if(res := this.children.haskey(tvIdSearch)) ;it is direct child of this node
{
return this.children[tvIdSearch]
}
else
{
for tvID,node in this.children
{
if(res := node.getNode(tvIdSearch))
{
return res ;was somwhere downstream
}
}
}
return false ;not in this branch
}
changeIcon(icon)
{
this.activate()
this.icon := icon
TV_Modify(this.tvID,"Icon" . this.icon)
}
changeText(text)
{
this.activate()
this.text := text
TV_Modify(this.tvID,,this.text)
}
}
- Attachments
-
- icons.zip
- icons needed for iconlookup, or you can create your own 12x12 ico file.
- (1.11 KiB) Downloaded 30 times
Re: TreeView with tri-state checkboxes - anyone know how?
Thanks, @colt, three icons is the route I started going down when I didn't find a solution - although not as well-ordered and neat as yours. It works fine, and I don't think the double-clicking issue is a bug, just a slightly unconventional behaviour (and why double click a checkbox - they cycle with a single click).
In my case, unfortunately, I did decide I needed the icons for file types - it's for a backup routine where I can select files and folders from a drive, with the intermediate state for folders where some data is selected but not all. It's just lacking too much without the icon for file type. There's code on the forum for getting that using a DllCall to Shell32\ExtractAssociatedIconA.
So I've been experimenting with prefixing the name of the items with a simple text indicator, e.g. # Program Files or ? AppData when adding them to the TV, which looks good enough (with a monospaced font so they're aligned). The state could change (cycle) by clicking, I guess - I haven't got that far yet - or with a GuiContextMenu or hotkeys.
I hope you enjoyed the exercise and the weather improves!
In my case, unfortunately, I did decide I needed the icons for file types - it's for a backup routine where I can select files and folders from a drive, with the intermediate state for folders where some data is selected but not all. It's just lacking too much without the icon for file type. There's code on the forum for getting that using a DllCall to Shell32\ExtractAssociatedIconA.
So I've been experimenting with prefixing the name of the items with a simple text indicator, e.g. # Program Files or ? AppData when adding them to the TV, which looks good enough (with a monospaced font so they're aligned). The state could change (cycle) by clicking, I guess - I haven't got that far yet - or with a GuiContextMenu or hotkeys.
I hope you enjoyed the exercise and the weather improves!
Who is online
Users browsing this forum: Google [Bot] and 200 guests