Skip to content

Commit 4addc96

Browse files
committed
Add ListView widget and basic demo
1 parent 498cebd commit 4addc96

File tree

4 files changed

+179
-7
lines changed

4 files changed

+179
-7
lines changed

examples/basic/main.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"log"
55
"os"
66
"strconv"
7+
"sync/atomic"
78
"time"
89

910
"github.com/lxn/win"
@@ -189,23 +190,54 @@ func bindWidgets(dlg *wingui.Dialog) {
189190
log.Println("tab1 error", err)
190191
return
191192
}
193+
lv1, _ := wingui.BindNewListView(IDC_TAB_LIST1, tab1)
194+
lv1.SetExtendedStyle(win.LVS_EX_FULLROWSELECT | win.LVS_EX_GRIDLINES | win.LVS_EX_DOUBLEBUFFER)
195+
lv1.InsertColumn(0, "Name", 120, win.LVCFMT_LEFT)
196+
lv1.InsertColumn(1, "Value", 80, win.LVCFMT_LEFT)
197+
for i := 0; i < 5; i++ {
198+
idx := lv1.InsertItem(-1, "Item "+strconv.Itoa(i))
199+
lv1.SetItemText(idx, 1, "V"+strconv.Itoa(i))
200+
}
201+
lv1.OnItemActivate = func(index int) {
202+
if index < 0 {
203+
return
204+
}
205+
tab1.MessageBox("Activate: "+lv1.GetItemText(index, 0), "ListView", win.MB_OK|win.MB_ICONINFORMATION)
206+
}
207+
192208
tabBtn, _ := wingui.BindNewButton(IDC_TAB_BUTTON1, tab1)
209+
tabBtn.SetText("Add Item")
210+
var addSeq int32 = 5
193211
tabBtn.OnClicked = func() {
194-
log.Println("tab page button clicked")
195-
tabBtn.MessageBox("tabBtn clicked: hello from wingui", "MessageBox", win.MB_OK|win.MB_ICONINFORMATION)
212+
n := int(atomic.AddInt32(&addSeq, 1) - 1)
213+
idx := lv1.InsertItem(-1, "Item "+strconv.Itoa(n))
214+
lv1.SetItemText(idx, 1, "V"+strconv.Itoa(n))
196215
}
197-
tabBtn.SetText("MessageBox #1")
198216
tabcontrol.AddDialogPage("Page1", tab1)
199217

200218
tab2, err := wingui.NewDialog(IDD_DIALOG_TAB1, tabcontrol.Handle(), nil)
201219
if err != nil {
202220
log.Println("tab2 error", err)
203221
return
204222
}
223+
lv2, _ := wingui.BindNewListView(IDC_TAB_LIST1, tab2)
224+
lv2.SetExtendedStyle(win.LVS_EX_FULLROWSELECT | win.LVS_EX_GRIDLINES | win.LVS_EX_DOUBLEBUFFER)
225+
lv2.InsertColumn(0, "Name", 120, win.LVCFMT_LEFT)
226+
lv2.InsertColumn(1, "Value", 80, win.LVCFMT_LEFT)
227+
for i := 0; i < 3; i++ {
228+
idx := lv2.InsertItem(-1, "Row "+strconv.Itoa(i))
229+
lv2.SetItemText(idx, 1, time.Now().Format("15:04:05"))
230+
}
231+
205232
tabBtn2, _ := wingui.BindNewButton(IDC_TAB_BUTTON1, tab2)
206-
tabBtn2.SetText("MessageBox #2")
233+
tabBtn2.SetText("Show Selected")
207234
tabBtn2.OnClicked = func() {
208-
tabBtn2.MessageBox("tab2 button clicked", "MessageBox", win.MB_OK|win.MB_ICONINFORMATION)
235+
sel := lv2.SelectedIndex()
236+
if sel < 0 {
237+
tabBtn2.MessageBox("No selection", "ListView", win.MB_OK|win.MB_ICONWARNING)
238+
return
239+
}
240+
tabBtn2.MessageBox("Selected: "+lv2.GetItemText(sel, 0)+" / "+lv2.GetItemText(sel, 1), "ListView", win.MB_OK|win.MB_ICONINFORMATION)
209241
}
210242
tabcontrol.AddDialogPage("Page2", tab2)
211243
tabcontrol.Select(0)

examples/basic/ui.syso

0 Bytes
Binary file not shown.

examples/basic/ui/ui.rc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW | WS_SY
7171
EXSTYLE WS_EX_CLIENTEDGE
7272
FONT 8, "Ms Shell Dlg"
7373
{
74-
PUSHBUTTON "Button1", IDC_TAB_BUTTON1, 85, 7, 63, 20, 0, WS_EX_LEFT
75-
CONTROL "", IDC_TAB_LIST1, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_ICON, 7, 7, 75, 55, WS_EX_LEFT
74+
CONTROL "", IDC_TAB_LIST1, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS, 7, 7, 141, 55, WS_EX_LEFT
75+
PUSHBUTTON "Button1", IDC_TAB_BUTTON1, 7, 66, 70, 14, 0, WS_EX_LEFT
7676
}
7777

7878

listview.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package wingui
2+
3+
import (
4+
"syscall"
5+
"unsafe"
6+
7+
"github.com/lxn/win"
8+
"github.com/whtiehack/wingui/winapi"
9+
)
10+
11+
// ListView is a wrapper for the Win32 SysListView32 control.
12+
// Notes:
13+
// - Most notifications are delivered as WM_NOTIFY to the parent dialog (routed by Dialog.dialogWndProc).
14+
// - For report view with columns, prefer creating the control with LVS_REPORT style in resources.
15+
type ListView struct {
16+
WindowBase
17+
18+
// OnItemActivate fires on item activation (double click / Enter), if available.
19+
OnItemActivate func(index int)
20+
// OnItemChanged fires on LVN_ITEMCHANGED.
21+
OnItemChanged func(index int)
22+
}
23+
24+
func (lv *ListView) WndProc(msg uint32, wParam, lParam uintptr) uintptr {
25+
switch msg {
26+
case win.WM_NOTIFY:
27+
nmhdr := (*win.NMHDR)(unsafe.Pointer(lParam))
28+
code := uint32(nmhdr.Code)
29+
30+
switch code {
31+
case win.LVN_ITEMCHANGED:
32+
if lv.OnItemChanged != nil {
33+
nmlv := (*win.NMLISTVIEW)(unsafe.Pointer(lParam))
34+
lv.OnItemChanged(int(nmlv.IItem))
35+
}
36+
return 0
37+
case win.NM_DBLCLK:
38+
if lv.OnItemActivate != nil {
39+
nmia := (*win.NMITEMACTIVATE)(unsafe.Pointer(lParam))
40+
lv.OnItemActivate(int(nmia.IItem))
41+
}
42+
return 0
43+
}
44+
return 0
45+
}
46+
return lv.AsWindowBase().WndProc(msg, wParam, lParam)
47+
}
48+
49+
// SetExtendedStyle sets extended list-view styles (e.g. LVS_EX_FULLROWSELECT).
50+
func (lv *ListView) SetExtendedStyle(style uint32) {
51+
lv.SendMessage(winapi.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, uintptr(style))
52+
}
53+
54+
// InsertColumn inserts a column at index and returns the actual inserted index (or -1).
55+
func (lv *ListView) InsertColumn(index int, text string, width int, fmt int32) int {
56+
utf16 := syscall.StringToUTF16(text)
57+
col := win.LVCOLUMN{
58+
Mask: win.LVCF_TEXT | win.LVCF_WIDTH | win.LVCF_FMT | win.LVCF_SUBITEM,
59+
Fmt: fmt,
60+
Cx: int32(width),
61+
PszText: &utf16[0],
62+
ISubItem: int32(index),
63+
}
64+
ret := lv.SendMessage(winapi.LVM_INSERTCOLUMNW, uintptr(index), uintptr(unsafe.Pointer(&col)))
65+
return int(int32(ret))
66+
}
67+
68+
// InsertItem inserts a new item at index (or appends if index < 0) and returns its index (or -1).
69+
func (lv *ListView) InsertItem(index int, text string) int {
70+
if index < 0 {
71+
index = lv.ItemCount()
72+
}
73+
utf16 := syscall.StringToUTF16(text)
74+
item := win.LVITEM{
75+
Mask: win.LVIF_TEXT,
76+
IItem: int32(index),
77+
ISubItem: 0,
78+
PszText: &utf16[0],
79+
}
80+
ret := lv.SendMessage(winapi.LVM_INSERTITEMW, 0, uintptr(unsafe.Pointer(&item)))
81+
return int(int32(ret))
82+
}
83+
84+
// SetItemText sets the text for an item/subitem.
85+
func (lv *ListView) SetItemText(itemIndex int, subItem int, text string) bool {
86+
utf16 := syscall.StringToUTF16(text)
87+
item := win.LVITEM{
88+
IItem: int32(itemIndex),
89+
ISubItem: int32(subItem),
90+
PszText: &utf16[0],
91+
}
92+
ret := lv.SendMessage(winapi.LVM_SETITEMTEXTW, uintptr(itemIndex), uintptr(unsafe.Pointer(&item)))
93+
return ret != 0
94+
}
95+
96+
// GetItemText returns the text for an item/subitem.
97+
func (lv *ListView) GetItemText(itemIndex int, subItem int) string {
98+
buf := make([]uint16, 512)
99+
item := win.LVITEM{
100+
IItem: int32(itemIndex),
101+
ISubItem: int32(subItem),
102+
CchTextMax: int32(len(buf)),
103+
PszText: &buf[0],
104+
}
105+
lv.SendMessage(winapi.LVM_GETITEMTEXTW, uintptr(itemIndex), uintptr(unsafe.Pointer(&item)))
106+
return syscall.UTF16ToString(buf)
107+
}
108+
109+
// ItemCount returns the number of items in the list.
110+
func (lv *ListView) ItemCount() int {
111+
return int(int32(lv.SendMessage(winapi.LVM_GETITEMCOUNT, 0, 0)))
112+
}
113+
114+
// DeleteAllItems deletes all items.
115+
func (lv *ListView) DeleteAllItems() bool {
116+
return lv.SendMessage(winapi.LVM_DELETEALLITEMS, 0, 0) != 0
117+
}
118+
119+
// DeleteItem deletes an item at index.
120+
func (lv *ListView) DeleteItem(index int) bool {
121+
return lv.SendMessage(winapi.LVM_DELETEITEM, uintptr(index), 0) != 0
122+
}
123+
124+
// SelectedIndex returns the first selected item index, or -1 if none.
125+
func (lv *ListView) SelectedIndex() int {
126+
ret := lv.SendMessage(winapi.LVM_GETNEXTITEM, ^uintptr(0), uintptr(win.LVNI_SELECTED))
127+
return int(int32(ret))
128+
}
129+
130+
// NewListView creates a new ListView, need bind to Dialog before use.
131+
func NewListView(idd uintptr) *ListView {
132+
return &ListView{WindowBase: WindowBase{idd: idd}}
133+
}
134+
135+
// BindNewListView creates a new ListView and bind to target dlg.
136+
func BindNewListView(idd uintptr, dlg *Dialog) (*ListView, error) {
137+
lv := NewListView(idd)
138+
err := dlg.BindWidgets(lv)
139+
return lv, err
140+
}

0 commit comments

Comments
 (0)