github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/winc/listview.go (about) 1 //go:build windows 2 3 /* 4 * Copyright (C) 2019 The Winc Authors. All Rights Reserved. 5 */ 6 7 package winc 8 9 import ( 10 "errors" 11 "fmt" 12 "syscall" 13 "unsafe" 14 15 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32" 16 ) 17 18 // ListItem represents an item in a ListView widget. 19 type ListItem interface { 20 Text() []string // Text returns the text of the multi-column item. 21 ImageIndex() int // ImageIndex is used only if SetImageList is called on the listview 22 } 23 24 // ListItemChecker is used for checkbox support in ListView. 25 type ListItemChecker interface { 26 Checked() bool 27 SetChecked(checked bool) 28 } 29 30 // ListItemSetter is used in OnEndLabelEdit event. 31 type ListItemSetter interface { 32 SetText(s string) // set first item in the array via LabelEdit event 33 } 34 35 // StringListItem is helper for basic string lists. 36 type StringListItem struct { 37 ID int 38 Data string 39 Check bool 40 } 41 42 func (s StringListItem) Text() []string { return []string{s.Data} } 43 func (s StringListItem) Checked() bool { return s.Check } 44 func (s StringListItem) SetChecked(checked bool) { s.Check = checked } 45 func (s StringListItem) ImageIndex() int { return 0 } 46 47 type ListView struct { 48 ControlBase 49 50 iml *ImageList 51 lastIndex int 52 cols int // count of columns 53 54 item2Handle map[ListItem]uintptr 55 handle2Item map[uintptr]ListItem 56 57 onEndLabelEdit EventManager 58 onDoubleClick EventManager 59 onClick EventManager 60 onKeyDown EventManager 61 onItemChanging EventManager 62 onItemChanged EventManager 63 onCheckChanged EventManager 64 onViewChange EventManager 65 onEndScroll EventManager 66 } 67 68 func NewListView(parent Controller) *ListView { 69 lv := new(ListView) 70 71 lv.InitControl("SysListView32", parent /*w32.WS_EX_CLIENTEDGE*/, 0, 72 w32.WS_CHILD|w32.WS_VISIBLE|w32.WS_TABSTOP|w32.LVS_REPORT|w32.LVS_EDITLABELS|w32.LVS_SHOWSELALWAYS) 73 74 lv.item2Handle = make(map[ListItem]uintptr) 75 lv.handle2Item = make(map[uintptr]ListItem) 76 77 RegMsgHandler(lv) 78 79 lv.SetFont(DefaultFont) 80 lv.SetSize(200, 400) 81 82 if err := lv.SetTheme("Explorer"); err != nil { 83 // theme error is ignored 84 } 85 return lv 86 } 87 88 // FIXME: Changes the state of an item in a list-view control. Refer LVM_SETITEMSTATE message. 89 func (lv *ListView) setItemState(i int, state, mask uint) { 90 var item w32.LVITEM 91 item.State, item.StateMask = uint32(state), uint32(mask) 92 w32.SendMessage(lv.hwnd, w32.LVM_SETITEMSTATE, uintptr(i), uintptr(unsafe.Pointer(&item))) 93 } 94 95 func (lv *ListView) EnableSingleSelect(enable bool) { 96 SetStyle(lv.hwnd, enable, w32.LVS_SINGLESEL) 97 } 98 99 func (lv *ListView) EnableSortHeader(enable bool) { 100 SetStyle(lv.hwnd, enable, w32.LVS_NOSORTHEADER) 101 } 102 103 func (lv *ListView) EnableSortAscending(enable bool) { 104 SetStyle(lv.hwnd, enable, w32.LVS_SORTASCENDING) 105 } 106 107 func (lv *ListView) EnableEditLabels(enable bool) { 108 SetStyle(lv.hwnd, enable, w32.LVS_EDITLABELS) 109 } 110 111 func (lv *ListView) EnableFullRowSelect(enable bool) { 112 if enable { 113 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, w32.LVS_EX_FULLROWSELECT) 114 } else { 115 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, w32.LVS_EX_FULLROWSELECT, 0) 116 } 117 } 118 119 func (lv *ListView) EnableDoubleBuffer(enable bool) { 120 if enable { 121 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, w32.LVS_EX_DOUBLEBUFFER) 122 } else { 123 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, w32.LVS_EX_DOUBLEBUFFER, 0) 124 } 125 } 126 127 func (lv *ListView) EnableHotTrack(enable bool) { 128 if enable { 129 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, w32.LVS_EX_TRACKSELECT) 130 } else { 131 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, w32.LVS_EX_TRACKSELECT, 0) 132 } 133 } 134 135 func (lv *ListView) SetItemCount(count int) bool { 136 return w32.SendMessage(lv.hwnd, w32.LVM_SETITEMCOUNT, uintptr(count), 0) != 0 137 } 138 139 func (lv *ListView) ItemCount() int { 140 return int(w32.SendMessage(lv.hwnd, w32.LVM_GETITEMCOUNT, 0, 0)) 141 } 142 143 func (lv *ListView) ItemAt(x, y int) ListItem { 144 hti := w32.LVHITTESTINFO{Pt: w32.POINT{int32(x), int32(y)}} 145 w32.SendMessage(lv.hwnd, w32.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) 146 return lv.findItemByIndex(int(hti.IItem)) 147 } 148 149 func (lv *ListView) Items() (list []ListItem) { 150 for item := range lv.item2Handle { 151 list = append(list, item) 152 } 153 return list 154 } 155 156 func (lv *ListView) AddColumn(caption string, width int) { 157 var lc w32.LVCOLUMN 158 lc.Mask = w32.LVCF_TEXT 159 if width != 0 { 160 lc.Mask = lc.Mask | w32.LVCF_WIDTH 161 lc.Cx = int32(width) 162 } 163 lc.PszText = syscall.StringToUTF16Ptr(caption) 164 lv.insertLvColumn(&lc, lv.cols) 165 lv.cols++ 166 } 167 168 // StretchLastColumn makes the last column take up all remaining horizontal 169 // space of the *ListView. 170 // The effect of this is not persistent. 171 func (lv *ListView) StretchLastColumn() error { 172 if lv.cols == 0 { 173 return nil 174 } 175 if w32.SendMessage(lv.hwnd, w32.LVM_SETCOLUMNWIDTH, uintptr(lv.cols-1), w32.LVSCW_AUTOSIZE_USEHEADER) == 0 { 176 //panic("LVM_SETCOLUMNWIDTH failed") 177 } 178 return nil 179 } 180 181 // CheckBoxes returns if the *TableView has check boxes. 182 func (lv *ListView) CheckBoxes() bool { 183 return w32.SendMessage(lv.hwnd, w32.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0)&w32.LVS_EX_CHECKBOXES > 0 184 } 185 186 // SetCheckBoxes sets if the *TableView has check boxes. 187 func (lv *ListView) SetCheckBoxes(value bool) { 188 exStyle := w32.SendMessage(lv.hwnd, w32.LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0) 189 oldStyle := exStyle 190 if value { 191 exStyle |= w32.LVS_EX_CHECKBOXES 192 } else { 193 exStyle &^= w32.LVS_EX_CHECKBOXES 194 } 195 if exStyle != oldStyle { 196 w32.SendMessage(lv.hwnd, w32.LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle) 197 } 198 199 mask := w32.SendMessage(lv.hwnd, w32.LVM_GETCALLBACKMASK, 0, 0) 200 if value { 201 mask |= w32.LVIS_STATEIMAGEMASK 202 } else { 203 mask &^= w32.LVIS_STATEIMAGEMASK 204 } 205 206 if w32.SendMessage(lv.hwnd, w32.LVM_SETCALLBACKMASK, mask, 0) == w32.FALSE { 207 panic("SendMessage(LVM_SETCALLBACKMASK)") 208 } 209 } 210 211 func (lv *ListView) applyImage(lc *w32.LVITEM, imIndex int) { 212 if lv.iml != nil { 213 lc.Mask |= w32.LVIF_IMAGE 214 lc.IImage = int32(imIndex) 215 } 216 } 217 218 func (lv *ListView) AddItem(item ListItem) { 219 lv.InsertItem(item, lv.ItemCount()) 220 } 221 222 func (lv *ListView) InsertItem(item ListItem, index int) { 223 text := item.Text() 224 li := &w32.LVITEM{ 225 Mask: w32.LVIF_TEXT | w32.LVIF_PARAM, 226 PszText: syscall.StringToUTF16Ptr(text[0]), 227 IItem: int32(index), 228 } 229 230 lv.lastIndex++ 231 ix := new(int) 232 *ix = lv.lastIndex 233 li.LParam = uintptr(*ix) 234 lv.handle2Item[li.LParam] = item 235 lv.item2Handle[item] = li.LParam 236 237 lv.applyImage(li, item.ImageIndex()) 238 lv.insertLvItem(li) 239 240 for i := 1; i < len(text); i++ { 241 li.Mask = w32.LVIF_TEXT 242 li.PszText = syscall.StringToUTF16Ptr(text[i]) 243 li.ISubItem = int32(i) 244 lv.setLvItem(li) 245 } 246 } 247 248 func (lv *ListView) UpdateItem(item ListItem) bool { 249 lparam, ok := lv.item2Handle[item] 250 if !ok { 251 return false 252 } 253 254 index := lv.findIndexByItem(item) 255 if index == -1 { 256 return false 257 } 258 259 text := item.Text() 260 li := &w32.LVITEM{ 261 Mask: w32.LVIF_TEXT | w32.LVIF_PARAM, 262 PszText: syscall.StringToUTF16Ptr(text[0]), 263 LParam: lparam, 264 IItem: int32(index), 265 } 266 267 lv.applyImage(li, item.ImageIndex()) 268 lv.setLvItem(li) 269 270 for i := 1; i < len(text); i++ { 271 li.Mask = w32.LVIF_TEXT 272 li.PszText = syscall.StringToUTF16Ptr(text[i]) 273 li.ISubItem = int32(i) 274 lv.setLvItem(li) 275 } 276 return true 277 } 278 279 func (lv *ListView) insertLvColumn(lvColumn *w32.LVCOLUMN, iCol int) { 280 w32.SendMessage(lv.hwnd, w32.LVM_INSERTCOLUMN, uintptr(iCol), uintptr(unsafe.Pointer(lvColumn))) 281 } 282 283 func (lv *ListView) insertLvItem(lvItem *w32.LVITEM) { 284 w32.SendMessage(lv.hwnd, w32.LVM_INSERTITEM, 0, uintptr(unsafe.Pointer(lvItem))) 285 } 286 287 func (lv *ListView) setLvItem(lvItem *w32.LVITEM) { 288 w32.SendMessage(lv.hwnd, w32.LVM_SETITEM, 0, uintptr(unsafe.Pointer(lvItem))) 289 } 290 291 func (lv *ListView) DeleteAllItems() bool { 292 if w32.SendMessage(lv.hwnd, w32.LVM_DELETEALLITEMS, 0, 0) == w32.TRUE { 293 lv.item2Handle = make(map[ListItem]uintptr) 294 lv.handle2Item = make(map[uintptr]ListItem) 295 return true 296 } 297 return false 298 } 299 300 func (lv *ListView) DeleteItem(item ListItem) error { 301 index := lv.findIndexByItem(item) 302 if index == -1 { 303 return errors.New("item not found") 304 } 305 306 if w32.SendMessage(lv.hwnd, w32.LVM_DELETEITEM, uintptr(index), 0) == 0 { 307 return errors.New("SendMessage(TVM_DELETEITEM) failed") 308 } 309 310 h := lv.item2Handle[item] 311 delete(lv.item2Handle, item) 312 delete(lv.handle2Item, h) 313 return nil 314 } 315 316 func (lv *ListView) findIndexByItem(item ListItem) int { 317 lparam, ok := lv.item2Handle[item] 318 if !ok { 319 return -1 320 } 321 322 it := &w32.LVFINDINFO{ 323 Flags: w32.LVFI_PARAM, 324 LParam: lparam, 325 } 326 var i int = -1 327 return int(w32.SendMessage(lv.hwnd, w32.LVM_FINDITEM, uintptr(i), uintptr(unsafe.Pointer(it)))) 328 } 329 330 func (lv *ListView) findItemByIndex(i int) ListItem { 331 it := &w32.LVITEM{ 332 Mask: w32.LVIF_PARAM, 333 IItem: int32(i), 334 } 335 336 if w32.SendMessage(lv.hwnd, w32.LVM_GETITEM, 0, uintptr(unsafe.Pointer(it))) == w32.TRUE { 337 if item, ok := lv.handle2Item[it.LParam]; ok { 338 return item 339 } 340 } 341 return nil 342 } 343 344 func (lv *ListView) EnsureVisible(item ListItem) bool { 345 if i := lv.findIndexByItem(item); i != -1 { 346 return w32.SendMessage(lv.hwnd, w32.LVM_ENSUREVISIBLE, uintptr(i), 1) == 0 347 } 348 return false 349 } 350 351 func (lv *ListView) SelectedItem() ListItem { 352 if items := lv.SelectedItems(); len(items) > 0 { 353 return items[0] 354 } 355 return nil 356 } 357 358 func (lv *ListView) SetSelectedItem(item ListItem) bool { 359 if i := lv.findIndexByItem(item); i > -1 { 360 lv.SetSelectedIndex(i) 361 return true 362 } 363 return false 364 } 365 366 // mask is used to set the LVITEM.Mask for ListView.GetItem which indicates which attributes you'd like to receive 367 // of LVITEM. 368 func (lv *ListView) SelectedItems() []ListItem { 369 var items []ListItem 370 371 var i int = -1 372 for { 373 if i = int(w32.SendMessage(lv.hwnd, w32.LVM_GETNEXTITEM, uintptr(i), uintptr(w32.LVNI_SELECTED))); i == -1 { 374 break 375 } 376 377 if item := lv.findItemByIndex(i); item != nil { 378 items = append(items, item) 379 } 380 } 381 return items 382 } 383 384 func (lv *ListView) SelectedCount() uint { 385 return uint(w32.SendMessage(lv.hwnd, w32.LVM_GETSELECTEDCOUNT, 0, 0)) 386 } 387 388 // GetSelectedIndex first selected item index. Returns -1 if no item is selected. 389 func (lv *ListView) SelectedIndex() int { 390 var i int = -1 391 return int(w32.SendMessage(lv.hwnd, w32.LVM_GETNEXTITEM, uintptr(i), uintptr(w32.LVNI_SELECTED))) 392 } 393 394 // Set i to -1 to select all items. 395 func (lv *ListView) SetSelectedIndex(i int) { 396 lv.setItemState(i, w32.LVIS_SELECTED, w32.LVIS_SELECTED) 397 } 398 399 func (lv *ListView) SetImageList(imageList *ImageList) { 400 w32.SendMessage(lv.hwnd, w32.LVM_SETIMAGELIST, w32.LVSIL_SMALL, uintptr(imageList.Handle())) 401 lv.iml = imageList 402 } 403 404 // Event publishers 405 func (lv *ListView) OnEndLabelEdit() *EventManager { 406 return &lv.onEndLabelEdit 407 } 408 409 func (lv *ListView) OnDoubleClick() *EventManager { 410 return &lv.onDoubleClick 411 } 412 413 func (lv *ListView) OnClick() *EventManager { 414 return &lv.onClick 415 } 416 417 func (lv *ListView) OnKeyDown() *EventManager { 418 return &lv.onKeyDown 419 } 420 421 func (lv *ListView) OnItemChanging() *EventManager { 422 return &lv.onItemChanging 423 } 424 425 func (lv *ListView) OnItemChanged() *EventManager { 426 return &lv.onItemChanged 427 } 428 429 func (lv *ListView) OnCheckChanged() *EventManager { 430 return &lv.onCheckChanged 431 } 432 433 func (lv *ListView) OnViewChange() *EventManager { 434 return &lv.onViewChange 435 } 436 437 func (lv *ListView) OnEndScroll() *EventManager { 438 return &lv.onEndScroll 439 } 440 441 // Message processer 442 func (lv *ListView) WndProc(msg uint32, wparam, lparam uintptr) uintptr { 443 switch msg { 444 /*case w32.WM_ERASEBKGND: 445 lv.StretchLastColumn() 446 println("case w32.WM_ERASEBKGND") 447 return 1*/ 448 449 case w32.WM_NOTIFY: 450 nm := (*w32.NMHDR)(unsafe.Pointer(lparam)) 451 code := int32(nm.Code) 452 453 switch code { 454 case w32.LVN_BEGINLABELEDITW: 455 // println("Begin label edit") 456 case w32.LVN_ENDLABELEDITW: 457 nmdi := (*w32.NMLVDISPINFO)(unsafe.Pointer(lparam)) 458 if nmdi.Item.PszText != nil { 459 fmt.Println(nmdi.Item.PszText, nmdi.Item) 460 if item, ok := lv.handle2Item[nmdi.Item.LParam]; ok { 461 lv.onEndLabelEdit.Fire(NewEvent(lv, 462 &LabelEditEventData{Item: item, 463 Text: w32.UTF16PtrToString(nmdi.Item.PszText)})) 464 } 465 return w32.TRUE 466 } 467 case w32.NM_DBLCLK: 468 lv.onDoubleClick.Fire(NewEvent(lv, nil)) 469 470 case w32.NM_CLICK: 471 ac := (*w32.NMITEMACTIVATE)(unsafe.Pointer(lparam)) 472 var hti w32.LVHITTESTINFO 473 hti.Pt = w32.POINT{ac.PtAction.X, ac.PtAction.Y} 474 w32.SendMessage(lv.hwnd, w32.LVM_HITTEST, 0, uintptr(unsafe.Pointer(&hti))) 475 476 if hti.Flags == w32.LVHT_ONITEMSTATEICON { 477 if item := lv.findItemByIndex(int(hti.IItem)); item != nil { 478 if item, ok := item.(ListItemChecker); ok { 479 checked := !item.Checked() 480 item.SetChecked(checked) 481 lv.onCheckChanged.Fire(NewEvent(lv, item)) 482 483 if w32.SendMessage(lv.hwnd, w32.LVM_UPDATE, uintptr(hti.IItem), 0) == w32.FALSE { 484 panic("SendMessage(LVM_UPDATE)") 485 } 486 } 487 } 488 } 489 490 hti.Pt = w32.POINT{ac.PtAction.X, ac.PtAction.Y} 491 w32.SendMessage(lv.hwnd, w32.LVM_SUBITEMHITTEST, 0, uintptr(unsafe.Pointer(&hti))) 492 lv.onClick.Fire(NewEvent(lv, hti.ISubItem)) 493 494 case w32.LVN_KEYDOWN: 495 nmkey := (*w32.NMLVKEYDOWN)(unsafe.Pointer(lparam)) 496 if nmkey.WVKey == w32.VK_SPACE && lv.CheckBoxes() { 497 if item := lv.SelectedItem(); item != nil { 498 if item, ok := item.(ListItemChecker); ok { 499 checked := !item.Checked() 500 item.SetChecked(checked) 501 lv.onCheckChanged.Fire(NewEvent(lv, item)) 502 } 503 504 index := lv.findIndexByItem(item) 505 if w32.SendMessage(lv.hwnd, w32.LVM_UPDATE, uintptr(index), 0) == w32.FALSE { 506 panic("SendMessage(LVM_UPDATE)") 507 } 508 } 509 } 510 lv.onKeyDown.Fire(NewEvent(lv, nmkey.WVKey)) 511 key := nmkey.WVKey 512 w32.SendMessage(lv.Parent().Handle(), w32.WM_KEYDOWN, uintptr(key), 0) 513 514 case w32.LVN_ITEMCHANGING: 515 // This event also fires when listview has changed via code. 516 nmlv := (*w32.NMLISTVIEW)(unsafe.Pointer(lparam)) 517 item := lv.findItemByIndex(int(nmlv.IItem)) 518 lv.onItemChanging.Fire(NewEvent(lv, item)) 519 520 case w32.LVN_ITEMCHANGED: 521 // This event also fires when listview has changed via code. 522 nmlv := (*w32.NMLISTVIEW)(unsafe.Pointer(lparam)) 523 item := lv.findItemByIndex(int(nmlv.IItem)) 524 lv.onItemChanged.Fire(NewEvent(lv, item)) 525 526 case w32.LVN_GETDISPINFO: 527 nmdi := (*w32.NMLVDISPINFO)(unsafe.Pointer(lparam)) 528 if nmdi.Item.StateMask&w32.LVIS_STATEIMAGEMASK > 0 { 529 if item, ok := lv.handle2Item[nmdi.Item.LParam]; ok { 530 if item, ok := item.(ListItemChecker); ok { 531 532 checked := item.Checked() 533 if checked { 534 nmdi.Item.State = 0x2000 535 } else { 536 nmdi.Item.State = 0x1000 537 } 538 } 539 } 540 } 541 542 lv.onViewChange.Fire(NewEvent(lv, nil)) 543 544 case w32.LVN_ENDSCROLL: 545 lv.onEndScroll.Fire(NewEvent(lv, nil)) 546 } 547 } 548 return w32.DefWindowProc(lv.hwnd, msg, wparam, lparam) 549 }