Skip to content

Commit a841f7f

Browse files
committed
Add TreeView widget and basic demo
1 parent 95e9df1 commit a841f7f

File tree

7 files changed

+308
-1
lines changed

7 files changed

+308
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ https://docs.microsoft.com/zh-cn/windows/win32/controls/window-controls
175175

176176
- [ ] DateTimePicker
177177
- [ ] Month Calendar
178-
- [ ] TreeView Control
178+
- [x] TreeView Control
179179

180180
- [ ] Hot Key
181181
- [ ] Accelerator

examples/basic/main.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,110 @@ func bindWidgets(dlg *wingui.Dialog) {
364364
updateRichLabel()
365365
}
366366
tabcontrol.AddDialogPage("RichEdit", richPage)
367+
368+
// TreeView demo page
369+
treePage, err := wingui.NewDialog(IDD_DIALOG_TREE, tabcontrol.Handle(), nil)
370+
if err != nil {
371+
log.Println("tree page error", err)
372+
return
373+
}
374+
tree, _ := wingui.BindNewTreeView(IDC_TREE1, treePage)
375+
treeLabel, _ := wingui.BindNewStatic(IDC_TREE_LABEL, treePage)
376+
var treeSeq int32
377+
updateTreeLabel := func() {
378+
sel := tree.GetSelection()
379+
if sel == 0 {
380+
treeLabel.SetText(fmt.Sprintf("Sel: - (%d)", tree.ItemCount()))
381+
return
382+
}
383+
treeLabel.SetText(fmt.Sprintf("Sel:%s", tree.GetItemText(sel)))
384+
}
385+
386+
rootA := tree.InsertItem(win.TVI_ROOT, win.TVI_LAST, "Root A")
387+
_ = tree.InsertItem(rootA, win.TVI_LAST, "Child A1")
388+
_ = tree.InsertItem(rootA, win.TVI_LAST, "Child A2")
389+
rootB := tree.InsertItem(win.TVI_ROOT, win.TVI_LAST, "Root B")
390+
_ = tree.InsertItem(rootB, win.TVI_LAST, "Child B1")
391+
tree.Expand(rootA, win.TVE_EXPAND)
392+
tree.SelectItem(rootA)
393+
updateTreeLabel()
394+
395+
tree.OnSelChanged = func(item win.HTREEITEM) {
396+
_ = item
397+
updateTreeLabel()
398+
}
399+
tree.OnDblClick = func(item win.HTREEITEM) {
400+
if item != 0 {
401+
tree.Expand(item, win.TVE_TOGGLE)
402+
}
403+
}
404+
405+
addChildBtn, _ := wingui.BindNewButton(IDC_TREE_ADD_CHILD, treePage)
406+
addChildBtn.OnClicked = func() {
407+
sel := tree.GetSelection()
408+
parent := win.TVI_ROOT
409+
if sel != 0 {
410+
parent = sel
411+
}
412+
n := atomic.AddInt32(&treeSeq, 1)
413+
item := tree.InsertItem(parent, win.TVI_LAST, fmt.Sprintf("Node %d", n))
414+
if item != 0 {
415+
tree.Expand(parent, win.TVE_EXPAND)
416+
tree.SelectItem(item)
417+
tree.EnsureVisible(item)
418+
}
419+
updateTreeLabel()
420+
}
421+
422+
addSibBtn, _ := wingui.BindNewButton(IDC_TREE_ADD_SIB, treePage)
423+
addSibBtn.OnClicked = func() {
424+
sel := tree.GetSelection()
425+
parent := win.TVI_ROOT
426+
after := win.TVI_LAST
427+
if sel != 0 {
428+
parent = tree.ParentItem(sel)
429+
if parent == 0 {
430+
parent = win.TVI_ROOT
431+
}
432+
after = sel
433+
}
434+
n := atomic.AddInt32(&treeSeq, 1)
435+
item := tree.InsertItem(parent, after, fmt.Sprintf("Node %d", n))
436+
if item != 0 {
437+
tree.SelectItem(item)
438+
tree.EnsureVisible(item)
439+
}
440+
updateTreeLabel()
441+
}
442+
443+
delBtn, _ := wingui.BindNewButton(IDC_TREE_DELETE, treePage)
444+
delBtn.OnClicked = func() {
445+
sel := tree.GetSelection()
446+
if sel == 0 {
447+
return
448+
}
449+
tree.DeleteItem(sel)
450+
updateTreeLabel()
451+
}
452+
453+
expandBtn, _ := wingui.BindNewButton(IDC_TREE_EXPAND, treePage)
454+
expandBtn.OnClicked = func() {
455+
sel := tree.GetSelection()
456+
if sel == 0 {
457+
return
458+
}
459+
tree.Expand(sel, win.TVE_EXPAND)
460+
}
461+
collapseBtn, _ := wingui.BindNewButton(IDC_TREE_COLLAPSE, treePage)
462+
collapseBtn.OnClicked = func() {
463+
sel := tree.GetSelection()
464+
if sel == 0 {
465+
return
466+
}
467+
tree.Expand(sel, win.TVE_COLLAPSE)
468+
}
469+
470+
tabcontrol.AddDialogPage("TreeView", treePage)
367471
tabcontrol.Select(0)
368472
// other
369473

examples/basic/resource.h.go

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/basic/ui.syso

418 Bytes
Binary file not shown.

examples/basic/ui/resource.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define IDB_BITMAP2 111
1010
#define IDD_DIALOG_TAB1 113
1111
#define IDD_DIALOG_RICH 114
12+
#define IDD_DIALOG_TREE 115
1213
#define IDB_OK 40000
1314
#define IDB_CANCEL 40001
1415
#define IDC_TAB_LIST1 40001
@@ -38,3 +39,10 @@
3839
#define IDC_RICH_APPEND 40024
3940
#define IDC_RICH_CLEAR 40025
4041
#define IDC_RICH_LABEL_LEN 40026
42+
#define IDC_TREE1 40027
43+
#define IDC_TREE_ADD_CHILD 40028
44+
#define IDC_TREE_ADD_SIB 40029
45+
#define IDC_TREE_DELETE 40030
46+
#define IDC_TREE_EXPAND 40031
47+
#define IDC_TREE_COLLAPSE 40032
48+
#define IDC_TREE_LABEL 40033

examples/basic/ui/ui.rc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ FONT 8, "Ms Shell Dlg"
9494
LTEXT "Len: 0", IDC_RICH_LABEL_LEN, 7, 76, 141, 9, SS_LEFT, WS_EX_LEFT
9595
}
9696

97+
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
98+
IDD_DIALOG_TREE DIALOG 0, 0, 153, 86
99+
STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW | WS_SYSMENU
100+
EXSTYLE WS_EX_CLIENTEDGE
101+
FONT 8, "Ms Shell Dlg"
102+
{
103+
CONTROL "", IDC_TREE1, WC_TREEVIEW, WS_TABSTOP | WS_BORDER | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS, 7, 7, 141, 45, WS_EX_LEFT
104+
PUSHBUTTON "Add Child", IDC_TREE_ADD_CHILD, 7, 55, 45, 14, 0, WS_EX_LEFT
105+
PUSHBUTTON "Add Sib", IDC_TREE_ADD_SIB, 55, 55, 40, 14, 0, WS_EX_LEFT
106+
PUSHBUTTON "Delete", IDC_TREE_DELETE, 99, 55, 40, 14, 0, WS_EX_LEFT
107+
PUSHBUTTON "Expand", IDC_TREE_EXPAND, 7, 71, 45, 14, 0, WS_EX_LEFT
108+
PUSHBUTTON "Collapse", IDC_TREE_COLLAPSE, 55, 71, 45, 14, 0, WS_EX_LEFT
109+
LTEXT "Sel: (none)", IDC_TREE_LABEL, 99, 74, 49, 9, SS_LEFT, WS_EX_LEFT
110+
}
111+
97112

98113

99114
//

treeview.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package wingui
2+
3+
import (
4+
"syscall"
5+
"unsafe"
6+
7+
"github.com/lxn/win"
8+
)
9+
10+
// TreeView is a wrapper for the Win32 SysTreeView32 control.
11+
type TreeView struct {
12+
WindowBase
13+
14+
// OnSelChanged fires after selection has changed.
15+
OnSelChanged func(item win.HTREEITEM)
16+
// OnDblClick fires on NM_DBLCLK; item can be 0 if none.
17+
OnDblClick func(item win.HTREEITEM)
18+
// OnItemExpanding fires on TVN_ITEMEXPANDING. Return true to cancel.
19+
OnItemExpanding func(item win.HTREEITEM, action uint32) (cancel bool)
20+
}
21+
22+
const (
23+
tvgnRoot = 0
24+
tvgnNext = 1
25+
tvgnPrevious = 2
26+
tvgnParent = 3
27+
tvgnChild = 4
28+
)
29+
30+
func (tv *TreeView) WndProc(msg uint32, wParam, lParam uintptr) uintptr {
31+
switch msg {
32+
case win.WM_NOTIFY:
33+
nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam))
34+
code := uint32(nmhdr.Code)
35+
36+
switch code {
37+
case win.TVN_SELCHANGED:
38+
if tv.OnSelChanged != nil {
39+
nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam))
40+
tv.OnSelChanged(nmtv.ItemNew.HItem)
41+
}
42+
return 0
43+
case win.TVN_ITEMEXPANDING:
44+
if tv.OnItemExpanding != nil {
45+
nmtv := (*win.NMTREEVIEW)(unsafe.Pointer(lParam))
46+
if tv.OnItemExpanding(nmtv.ItemNew.HItem, nmtv.Action) {
47+
return 1
48+
}
49+
}
50+
return 0
51+
case win.NM_DBLCLK:
52+
if tv.OnDblClick != nil {
53+
tv.OnDblClick(tv.GetSelection())
54+
}
55+
return 0
56+
}
57+
return 0
58+
}
59+
return tv.AsWindowBase().WndProc(msg, wParam, lParam)
60+
}
61+
62+
// ItemCount returns the total number of items.
63+
func (tv *TreeView) ItemCount() int {
64+
return int(int32(tv.SendMessage(win.TVM_GETCOUNT, 0, 0)))
65+
}
66+
67+
// InsertItem inserts a new item and returns its handle (0 on failure).
68+
// parent can be win.TVI_ROOT; insertAfter can be win.TVI_LAST / win.TVI_SORT, etc.
69+
func (tv *TreeView) InsertItem(parent, insertAfter win.HTREEITEM, text string) win.HTREEITEM {
70+
utf16 := syscall.StringToUTF16(text)
71+
ins := win.TVINSERTSTRUCT{
72+
HParent: parent,
73+
HInsertAfter: insertAfter,
74+
Item: win.TVITEM{
75+
Mask: win.TVIF_TEXT,
76+
PszText: uintptr(unsafe.Pointer(&utf16[0])),
77+
},
78+
}
79+
ret := tv.SendMessage(win.TVM_INSERTITEM, 0, uintptr(unsafe.Pointer(&ins)))
80+
return win.HTREEITEM(ret)
81+
}
82+
83+
// DeleteItem deletes an item (or all children if item is win.TVI_ROOT) and returns success.
84+
func (tv *TreeView) DeleteItem(item win.HTREEITEM) bool {
85+
return tv.SendMessage(win.TVM_DELETEITEM, 0, uintptr(item)) != 0
86+
}
87+
88+
// Expand expands/collapses an item. action is one of win.TVE_*.
89+
func (tv *TreeView) Expand(item win.HTREEITEM, action uint32) bool {
90+
return tv.SendMessage(win.TVM_EXPAND, uintptr(action), uintptr(item)) != 0
91+
}
92+
93+
// EnsureVisible scrolls the tree view so the item is visible.
94+
func (tv *TreeView) EnsureVisible(item win.HTREEITEM) bool {
95+
return tv.SendMessage(win.TVM_ENSUREVISIBLE, 0, uintptr(item)) != 0
96+
}
97+
98+
// GetSelection returns the currently selected item (0 if none).
99+
func (tv *TreeView) GetSelection() win.HTREEITEM {
100+
return win.HTREEITEM(tv.SendMessage(win.TVM_GETNEXTITEM, win.TVGN_CARET, 0))
101+
}
102+
103+
// SelectItem selects an item.
104+
func (tv *TreeView) SelectItem(item win.HTREEITEM) bool {
105+
return tv.SendMessage(win.TVM_SELECTITEM, win.TVGN_CARET, uintptr(item)) != 0
106+
}
107+
108+
// SetItemText updates the item text.
109+
func (tv *TreeView) SetItemText(item win.HTREEITEM, text string) bool {
110+
utf16 := syscall.StringToUTF16(text)
111+
tvi := win.TVITEM{
112+
Mask: win.TVIF_TEXT,
113+
HItem: item,
114+
PszText: uintptr(unsafe.Pointer(&utf16[0])),
115+
}
116+
return tv.SendMessage(win.TVM_SETITEM, 0, uintptr(unsafe.Pointer(&tvi))) != 0
117+
}
118+
119+
// GetItemText reads the item text.
120+
func (tv *TreeView) GetItemText(item win.HTREEITEM) string {
121+
buf := make([]uint16, 512)
122+
tvi := win.TVITEM{
123+
Mask: win.TVIF_TEXT,
124+
HItem: item,
125+
PszText: uintptr(unsafe.Pointer(&buf[0])),
126+
CchTextMax: int32(len(buf)),
127+
}
128+
if tv.SendMessage(win.TVM_GETITEM, 0, uintptr(unsafe.Pointer(&tvi))) == 0 {
129+
return ""
130+
}
131+
return syscall.UTF16ToString(buf)
132+
}
133+
134+
// ParentItem returns the parent item or 0.
135+
func (tv *TreeView) ParentItem(item win.HTREEITEM) win.HTREEITEM {
136+
return win.HTREEITEM(tv.SendMessage(win.TVM_GETNEXTITEM, tvgnParent, uintptr(item)))
137+
}
138+
139+
// NextSibling returns the next sibling item or 0.
140+
func (tv *TreeView) NextSibling(item win.HTREEITEM) win.HTREEITEM {
141+
return win.HTREEITEM(tv.SendMessage(win.TVM_GETNEXTITEM, tvgnNext, uintptr(item)))
142+
}
143+
144+
// PrevSibling returns the previous sibling item or 0.
145+
func (tv *TreeView) PrevSibling(item win.HTREEITEM) win.HTREEITEM {
146+
return win.HTREEITEM(tv.SendMessage(win.TVM_GETNEXTITEM, tvgnPrevious, uintptr(item)))
147+
}
148+
149+
// ChildItem returns the first child item or 0.
150+
func (tv *TreeView) ChildItem(item win.HTREEITEM) win.HTREEITEM {
151+
return win.HTREEITEM(tv.SendMessage(win.TVM_GETNEXTITEM, tvgnChild, uintptr(item)))
152+
}
153+
154+
// NewTreeView creates a new TreeView, need bind to Dialog before use.
155+
func NewTreeView(idd uintptr) *TreeView {
156+
return &TreeView{WindowBase: WindowBase{idd: idd}}
157+
}
158+
159+
// BindNewTreeView creates a new TreeView and bind to target dlg.
160+
func BindNewTreeView(idd uintptr, dlg *Dialog) (*TreeView, error) {
161+
tv := NewTreeView(idd)
162+
err := dlg.BindWidgets(tv)
163+
return tv, err
164+
}

0 commit comments

Comments
 (0)