753 lines
18 KiB
Go
753 lines
18 KiB
Go
|
// Copyright 2012 The Walk Authors. All rights reserved.
|
||
|
// Use of lb source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
// +build windows
|
||
|
|
||
|
package walk
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"math/big"
|
||
|
"reflect"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/lxn/win"
|
||
|
)
|
||
|
|
||
|
type ListBox struct {
|
||
|
WidgetBase
|
||
|
model ListModel
|
||
|
providedModel interface{}
|
||
|
styler ListItemStyler
|
||
|
style ListItemStyle
|
||
|
dataMember string
|
||
|
format string
|
||
|
precision int
|
||
|
prevCurIndex int
|
||
|
itemsResetHandlerHandle int
|
||
|
itemChangedHandlerHandle int
|
||
|
itemsInsertedHandlerHandle int
|
||
|
itemsRemovedHandlerHandle int
|
||
|
maxItemTextWidth int // in native pixels
|
||
|
lastWidth int // in native pixels
|
||
|
lastWidthsMeasuredFor []int // in native pixels
|
||
|
currentIndexChangedPublisher EventPublisher
|
||
|
selectedIndexesChangedPublisher EventPublisher
|
||
|
itemActivatedPublisher EventPublisher
|
||
|
themeNormalBGColor Color
|
||
|
themeNormalTextColor Color
|
||
|
themeSelectedBGColor Color
|
||
|
themeSelectedTextColor Color
|
||
|
themeSelectedNotFocusedBGColor Color
|
||
|
trackingMouseEvent bool
|
||
|
}
|
||
|
|
||
|
func NewListBox(parent Container) (*ListBox, error) {
|
||
|
return NewListBoxWithStyle(parent, 0)
|
||
|
}
|
||
|
|
||
|
func NewListBoxWithStyle(parent Container, style uint32) (*ListBox, error) {
|
||
|
lb := new(ListBox)
|
||
|
|
||
|
err := InitWidget(
|
||
|
lb,
|
||
|
parent,
|
||
|
"LISTBOX",
|
||
|
win.WS_BORDER|win.WS_TABSTOP|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_HSCROLL|win.LBS_NOINTEGRALHEIGHT|win.LBS_NOTIFY|style,
|
||
|
0)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
succeeded := false
|
||
|
defer func() {
|
||
|
if !succeeded {
|
||
|
lb.Dispose()
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
lb.setTheme("Explorer")
|
||
|
|
||
|
lb.style.dpi = lb.DPI()
|
||
|
|
||
|
lb.ApplySysColors()
|
||
|
|
||
|
lb.GraphicsEffects().Add(InteractionEffect)
|
||
|
lb.GraphicsEffects().Add(FocusEffect)
|
||
|
|
||
|
lb.MustRegisterProperty("CurrentIndex", NewProperty(
|
||
|
func() interface{} {
|
||
|
return lb.CurrentIndex()
|
||
|
},
|
||
|
func(v interface{}) error {
|
||
|
return lb.SetCurrentIndex(assertIntOr(v, -1))
|
||
|
},
|
||
|
lb.CurrentIndexChanged()))
|
||
|
|
||
|
lb.MustRegisterProperty("CurrentItem", NewReadOnlyProperty(
|
||
|
func() interface{} {
|
||
|
if i := lb.CurrentIndex(); i > -1 {
|
||
|
if rm, ok := lb.providedModel.(reflectModel); ok {
|
||
|
return reflect.ValueOf(rm.Items()).Index(i).Interface()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
},
|
||
|
lb.CurrentIndexChanged()))
|
||
|
|
||
|
lb.MustRegisterProperty("HasCurrentItem", NewReadOnlyBoolProperty(
|
||
|
func() bool {
|
||
|
return lb.CurrentIndex() != -1
|
||
|
},
|
||
|
lb.CurrentIndexChanged()))
|
||
|
|
||
|
succeeded = true
|
||
|
|
||
|
return lb, nil
|
||
|
}
|
||
|
|
||
|
func (*ListBox) LayoutFlags() LayoutFlags {
|
||
|
return ShrinkableHorz | ShrinkableVert | GrowableHorz | GrowableVert | GreedyHorz | GreedyVert
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) ItemStyler() ListItemStyler {
|
||
|
return lb.styler
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SetItemStyler(styler ListItemStyler) {
|
||
|
lb.styler = styler
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) ApplySysColors() {
|
||
|
lb.WidgetBase.ApplySysColors()
|
||
|
|
||
|
var hc win.HIGHCONTRAST
|
||
|
hc.CbSize = uint32(unsafe.Sizeof(hc))
|
||
|
if win.SystemParametersInfo(win.SPI_GETHIGHCONTRAST, hc.CbSize, unsafe.Pointer(&hc), 0) {
|
||
|
lb.style.highContrastActive = hc.DwFlags&win.HCF_HIGHCONTRASTON != 0
|
||
|
}
|
||
|
|
||
|
lb.themeNormalBGColor = Color(win.GetSysColor(win.COLOR_WINDOW))
|
||
|
lb.themeNormalTextColor = Color(win.GetSysColor(win.COLOR_WINDOWTEXT))
|
||
|
lb.themeSelectedBGColor = Color(win.GetSysColor(win.COLOR_HIGHLIGHT))
|
||
|
lb.themeSelectedTextColor = Color(win.GetSysColor(win.COLOR_HIGHLIGHTTEXT))
|
||
|
lb.themeSelectedNotFocusedBGColor = Color(win.GetSysColor(win.COLOR_BTNFACE))
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) ApplyDPI(dpi int) {
|
||
|
lb.style.dpi = dpi
|
||
|
|
||
|
lb.WidgetBase.ApplyDPI(dpi)
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) applyFont(font *Font) {
|
||
|
lb.WidgetBase.applyFont(font)
|
||
|
|
||
|
for i := range lb.lastWidthsMeasuredFor {
|
||
|
lb.lastWidthsMeasuredFor[i] = 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) itemString(index int) string {
|
||
|
switch val := lb.model.Value(index).(type) {
|
||
|
case string:
|
||
|
return val
|
||
|
|
||
|
case time.Time:
|
||
|
return val.Format(lb.format)
|
||
|
|
||
|
case *big.Rat:
|
||
|
return val.FloatString(lb.precision)
|
||
|
|
||
|
default:
|
||
|
return fmt.Sprintf(lb.format, val)
|
||
|
}
|
||
|
|
||
|
panic("unreachable")
|
||
|
}
|
||
|
|
||
|
//insert one item from list model
|
||
|
func (lb *ListBox) insertItemAt(index int) error {
|
||
|
str := lb.itemString(index)
|
||
|
lp := uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(str)))
|
||
|
ret := int(lb.SendMessage(win.LB_INSERTSTRING, uintptr(index), lp))
|
||
|
if ret == win.LB_ERRSPACE || ret == win.LB_ERR {
|
||
|
return newError("SendMessage(LB_INSERTSTRING)")
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) removeItem(index int) error {
|
||
|
if win.LB_ERR == int(lb.SendMessage(win.LB_DELETESTRING, uintptr(index), 0)) {
|
||
|
return newError("SendMessage(LB_DELETESTRING)")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// reread all the items from list model
|
||
|
func (lb *ListBox) resetItems() error {
|
||
|
lb.SetSuspended(true)
|
||
|
defer lb.SetSuspended(false)
|
||
|
|
||
|
lb.SendMessage(win.LB_RESETCONTENT, 0, 0)
|
||
|
|
||
|
lb.maxItemTextWidth = 0
|
||
|
|
||
|
lb.SetCurrentIndex(-1)
|
||
|
|
||
|
if lb.model == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
count := lb.model.ItemCount()
|
||
|
|
||
|
lb.lastWidthsMeasuredFor = make([]int, count)
|
||
|
|
||
|
for i := 0; i < count; i++ {
|
||
|
if err := lb.insertItemAt(i); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if lb.styler == nil {
|
||
|
// Update the listbox width (this sets the correct horizontal scrollbar).
|
||
|
sh := lb.idealSize()
|
||
|
lb.SendMessage(win.LB_SETHORIZONTALEXTENT, uintptr(sh.Width), 0)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) ensureVisibleItemsHeightUpToDate() error {
|
||
|
if lb.styler == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if !lb.Suspended() {
|
||
|
lb.SetSuspended(true)
|
||
|
defer lb.SetSuspended(false)
|
||
|
}
|
||
|
|
||
|
topIndex := int(lb.SendMessage(win.LB_GETTOPINDEX, 0, 0))
|
||
|
offset := maxi(0, topIndex-10)
|
||
|
count := lb.model.ItemCount()
|
||
|
var rc win.RECT
|
||
|
lb.SendMessage(win.LB_GETITEMRECT, uintptr(offset), uintptr(unsafe.Pointer(&rc)))
|
||
|
width := int(rc.Right - rc.Left)
|
||
|
offsetTop := int(rc.Top)
|
||
|
lbHeight := lb.HeightPixels()
|
||
|
|
||
|
var pastBottomCount int
|
||
|
for i := offset; i >= 0 && i < count; i++ {
|
||
|
if lb.lastWidthsMeasuredFor[i] == lb.lastWidth {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
lb.SendMessage(win.LB_GETITEMRECT, uintptr(i), uintptr(unsafe.Pointer(&rc)))
|
||
|
|
||
|
if int(rc.Top)-offsetTop > lbHeight {
|
||
|
if pastBottomCount++; pastBottomCount > 10 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
height := lb.styler.ItemHeight(i, width)
|
||
|
|
||
|
lb.SendMessage(win.LB_SETITEMHEIGHT, uintptr(i), uintptr(height))
|
||
|
|
||
|
lb.lastWidthsMeasuredFor[i] = lb.lastWidth
|
||
|
}
|
||
|
|
||
|
lb.SendMessage(win.LB_SETTOPINDEX, uintptr(topIndex), 0)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) attachModel() {
|
||
|
itemsResetHandler := func() {
|
||
|
lb.resetItems()
|
||
|
}
|
||
|
lb.itemsResetHandlerHandle = lb.model.ItemsReset().Attach(itemsResetHandler)
|
||
|
|
||
|
itemChangedHandler := func(index int) {
|
||
|
if win.CB_ERR == lb.SendMessage(win.LB_DELETESTRING, uintptr(index), 0) {
|
||
|
newError("SendMessage(CB_DELETESTRING)")
|
||
|
}
|
||
|
|
||
|
lb.insertItemAt(index)
|
||
|
|
||
|
if lb.styler != nil {
|
||
|
var rc win.RECT
|
||
|
lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc)))
|
||
|
width := int(rc.Right - rc.Left)
|
||
|
height := lb.styler.ItemHeight(index, width)
|
||
|
|
||
|
lb.SendMessage(win.LB_SETITEMHEIGHT, uintptr(index), uintptr(height))
|
||
|
|
||
|
lb.lastWidthsMeasuredFor[index] = lb.lastWidth
|
||
|
}
|
||
|
|
||
|
lb.SetCurrentIndex(lb.prevCurIndex)
|
||
|
}
|
||
|
lb.itemChangedHandlerHandle = lb.model.ItemChanged().Attach(itemChangedHandler)
|
||
|
|
||
|
lb.itemsInsertedHandlerHandle = lb.model.ItemsInserted().Attach(func(from, to int) {
|
||
|
if !lb.Suspended() {
|
||
|
lb.SetSuspended(true)
|
||
|
defer lb.SetSuspended(false)
|
||
|
}
|
||
|
|
||
|
for i := from; i <= to; i++ {
|
||
|
lb.insertItemAt(i)
|
||
|
}
|
||
|
|
||
|
lb.lastWidthsMeasuredFor = append(lb.lastWidthsMeasuredFor[:from], append(make([]int, to-from+1), lb.lastWidthsMeasuredFor[from:]...)...)
|
||
|
|
||
|
lb.ensureVisibleItemsHeightUpToDate()
|
||
|
})
|
||
|
|
||
|
lb.itemsRemovedHandlerHandle = lb.model.ItemsRemoved().Attach(func(from, to int) {
|
||
|
if !lb.Suspended() {
|
||
|
lb.SetSuspended(true)
|
||
|
defer lb.SetSuspended(false)
|
||
|
}
|
||
|
|
||
|
for i := to; i >= from; i-- {
|
||
|
lb.removeItem(i)
|
||
|
}
|
||
|
|
||
|
lb.lastWidthsMeasuredFor = append(lb.lastWidthsMeasuredFor[:from], lb.lastWidthsMeasuredFor[to:]...)
|
||
|
|
||
|
lb.ensureVisibleItemsHeightUpToDate()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) detachModel() {
|
||
|
lb.model.ItemsReset().Detach(lb.itemsResetHandlerHandle)
|
||
|
lb.model.ItemChanged().Detach(lb.itemChangedHandlerHandle)
|
||
|
lb.model.ItemsInserted().Detach(lb.itemsInsertedHandlerHandle)
|
||
|
lb.model.ItemsRemoved().Detach(lb.itemsRemovedHandlerHandle)
|
||
|
}
|
||
|
|
||
|
// Model returns the model of the ListBox.
|
||
|
func (lb *ListBox) Model() interface{} {
|
||
|
return lb.providedModel
|
||
|
}
|
||
|
|
||
|
// SetModel sets the model of the ListBox.
|
||
|
//
|
||
|
// It is required that mdl either implements walk.ListModel or
|
||
|
// walk.ReflectListModel or be a slice of pointers to struct or a []string.
|
||
|
func (lb *ListBox) SetModel(mdl interface{}) error {
|
||
|
model, ok := mdl.(ListModel)
|
||
|
if !ok && mdl != nil {
|
||
|
var err error
|
||
|
if model, err = newReflectListModel(mdl); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if _, ok := mdl.([]string); !ok {
|
||
|
if badms, ok := model.(bindingAndDisplayMemberSetter); ok {
|
||
|
badms.setDisplayMember(lb.dataMember)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
lb.providedModel = mdl
|
||
|
|
||
|
if lb.model != nil {
|
||
|
lb.detachModel()
|
||
|
}
|
||
|
|
||
|
lb.model = model
|
||
|
|
||
|
if model != nil {
|
||
|
lb.attachModel()
|
||
|
}
|
||
|
|
||
|
if err := lb.resetItems(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
return lb.ensureVisibleItemsHeightUpToDate()
|
||
|
}
|
||
|
|
||
|
// DataMember returns the member from the model of the ListBox that is displayed
|
||
|
// in the ListBox.
|
||
|
//
|
||
|
// This is only applicable to walk.ReflectListModel models and simple slices of
|
||
|
// pointers to struct.
|
||
|
func (lb *ListBox) DataMember() string {
|
||
|
return lb.dataMember
|
||
|
}
|
||
|
|
||
|
// SetDataMember sets the member from the model of the ListBox that is displayed
|
||
|
// in the ListBox.
|
||
|
//
|
||
|
// This is only applicable to walk.ReflectListModel models and simple slices of
|
||
|
// pointers to struct.
|
||
|
//
|
||
|
// For a model consisting of items of type S, the type of the specified member T
|
||
|
// and dataMember "Foo", this can be one of the following:
|
||
|
//
|
||
|
// A field Foo T
|
||
|
// A method func (s S) Foo() T
|
||
|
// A method func (s S) Foo() (T, error)
|
||
|
//
|
||
|
// If dataMember is not a simple member name like "Foo", but a path to a
|
||
|
// member like "A.B.Foo", members "A" and "B" both must be one of the options
|
||
|
// mentioned above, but with T having type pointer to struct.
|
||
|
func (lb *ListBox) SetDataMember(dataMember string) error {
|
||
|
if dataMember != "" {
|
||
|
if _, ok := lb.providedModel.([]string); ok {
|
||
|
return newError("invalid for []string model")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lb.dataMember = dataMember
|
||
|
|
||
|
if badms, ok := lb.model.(bindingAndDisplayMemberSetter); ok {
|
||
|
badms.setDisplayMember(dataMember)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) Format() string {
|
||
|
return lb.format
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SetFormat(value string) {
|
||
|
lb.format = value
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) Precision() int {
|
||
|
return lb.precision
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SetPrecision(value int) {
|
||
|
lb.precision = value
|
||
|
}
|
||
|
|
||
|
// calculateMaxItemTextWidth returns maximum item text width in native pixels.
|
||
|
func (lb *ListBox) calculateMaxItemTextWidth() int {
|
||
|
hdc := win.GetDC(lb.hWnd)
|
||
|
if hdc == 0 {
|
||
|
newError("GetDC failed")
|
||
|
return -1
|
||
|
}
|
||
|
defer win.ReleaseDC(lb.hWnd, hdc)
|
||
|
|
||
|
hFontOld := win.SelectObject(hdc, win.HGDIOBJ(lb.Font().handleForDPI(lb.DPI())))
|
||
|
defer win.SelectObject(hdc, hFontOld)
|
||
|
|
||
|
var maxWidth int
|
||
|
|
||
|
if lb.model == nil {
|
||
|
return -1
|
||
|
}
|
||
|
count := lb.model.ItemCount()
|
||
|
for i := 0; i < count; i++ {
|
||
|
item := lb.itemString(i)
|
||
|
var s win.SIZE
|
||
|
str := syscall.StringToUTF16(item)
|
||
|
|
||
|
if !win.GetTextExtentPoint32(hdc, &str[0], int32(len(str)-1), &s) {
|
||
|
newError("GetTextExtentPoint32 failed")
|
||
|
return -1
|
||
|
}
|
||
|
|
||
|
maxWidth = maxi(maxWidth, int(s.CX))
|
||
|
}
|
||
|
|
||
|
return maxWidth
|
||
|
}
|
||
|
|
||
|
// idealSize returns listbox ideal size in native pixels.
|
||
|
func (lb *ListBox) idealSize() Size {
|
||
|
defaultSize := lb.dialogBaseUnitsToPixels(Size{50, 12})
|
||
|
|
||
|
if lb.maxItemTextWidth <= 0 {
|
||
|
lb.maxItemTextWidth = lb.calculateMaxItemTextWidth()
|
||
|
}
|
||
|
|
||
|
// FIXME: Use GetThemePartSize instead of guessing
|
||
|
w := maxi(defaultSize.Width, lb.maxItemTextWidth+IntFrom96DPI(24, lb.DPI()))
|
||
|
h := defaultSize.Height + 1
|
||
|
|
||
|
return Size{w, h}
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) ItemVisible(index int) bool {
|
||
|
topIndex := int(lb.SendMessage(win.LB_GETTOPINDEX, 0, 0))
|
||
|
var rc win.RECT
|
||
|
lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc)))
|
||
|
|
||
|
return index >= topIndex && int(rc.Top) < lb.HeightPixels()
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) EnsureItemVisible(index int) {
|
||
|
lb.SendMessage(win.LB_SETTOPINDEX, uintptr(index), 0)
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) CurrentIndex() int {
|
||
|
return int(int32(lb.SendMessage(win.LB_GETCURSEL, 0, 0)))
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SetCurrentIndex(value int) error {
|
||
|
if value > -1 && win.LB_ERR == int(int32(lb.SendMessage(win.LB_SETCURSEL, uintptr(value), 0))) {
|
||
|
return newError("Invalid index or ensure lb is single-selection listbox")
|
||
|
}
|
||
|
|
||
|
if value != lb.prevCurIndex {
|
||
|
lb.prevCurIndex = value
|
||
|
lb.currentIndexChangedPublisher.Publish()
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SelectedIndexes() []int {
|
||
|
count := int(int32(lb.SendMessage(win.LB_GETCOUNT, 0, 0)))
|
||
|
if count < 1 {
|
||
|
return nil
|
||
|
}
|
||
|
index32 := make([]int32, count)
|
||
|
if n := int(int32(lb.SendMessage(win.LB_GETSELITEMS, uintptr(count), uintptr(unsafe.Pointer(&index32[0]))))); n == win.LB_ERR {
|
||
|
return nil
|
||
|
} else {
|
||
|
indexes := make([]int, n)
|
||
|
for i := 0; i < n; i++ {
|
||
|
indexes[i] = int(index32[i])
|
||
|
}
|
||
|
return indexes
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SetSelectedIndexes(indexes []int) {
|
||
|
var m int32 = -1
|
||
|
lb.SendMessage(win.LB_SETSEL, win.FALSE, uintptr(m))
|
||
|
for _, v := range indexes {
|
||
|
lb.SendMessage(win.LB_SETSEL, win.TRUE, uintptr(uint32(v)))
|
||
|
}
|
||
|
lb.selectedIndexesChangedPublisher.Publish()
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) CurrentIndexChanged() *Event {
|
||
|
return lb.currentIndexChangedPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) SelectedIndexesChanged() *Event {
|
||
|
return lb.selectedIndexesChangedPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) ItemActivated() *Event {
|
||
|
return lb.itemActivatedPublisher.Event()
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||
|
switch msg {
|
||
|
case win.WM_MEASUREITEM:
|
||
|
if lb.styler == nil {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
mis := (*win.MEASUREITEMSTRUCT)(unsafe.Pointer(lParam))
|
||
|
|
||
|
mis.ItemHeight = uint32(lb.styler.DefaultItemHeight())
|
||
|
|
||
|
return win.TRUE
|
||
|
|
||
|
case win.WM_DRAWITEM:
|
||
|
dis := (*win.DRAWITEMSTRUCT)(unsafe.Pointer(lParam))
|
||
|
|
||
|
if lb.styler == nil || dis.ItemID < 0 || dis.ItemAction != win.ODA_DRAWENTIRE {
|
||
|
return win.TRUE
|
||
|
}
|
||
|
|
||
|
lb.style.index = int(dis.ItemID)
|
||
|
lb.style.rc = dis.RcItem
|
||
|
lb.style.bounds = rectangleFromRECT(dis.RcItem)
|
||
|
lb.style.dpi = lb.DPI()
|
||
|
lb.style.state = dis.ItemState
|
||
|
lb.style.hwnd = lb.hWnd
|
||
|
lb.style.hdc = dis.HDC
|
||
|
lb.style.Font = lb.Font()
|
||
|
|
||
|
if dis.ItemAction == win.ODA_FOCUS {
|
||
|
return win.TRUE
|
||
|
}
|
||
|
|
||
|
var hTheme win.HTHEME
|
||
|
if !lb.style.highContrastActive {
|
||
|
if hTheme = win.OpenThemeData(lb.hWnd, syscall.StringToUTF16Ptr("Listview")); hTheme != 0 {
|
||
|
defer win.CloseThemeData(hTheme)
|
||
|
}
|
||
|
}
|
||
|
lb.style.hTheme = hTheme
|
||
|
|
||
|
if dis.ItemState&win.ODS_CHECKED != 0 {
|
||
|
if lb.style.highContrastActive || lb.Focused() {
|
||
|
lb.style.BackgroundColor = lb.themeSelectedBGColor
|
||
|
lb.style.TextColor = lb.themeSelectedTextColor
|
||
|
} else {
|
||
|
lb.style.BackgroundColor = lb.themeSelectedNotFocusedBGColor
|
||
|
lb.style.TextColor = lb.themeNormalTextColor
|
||
|
}
|
||
|
} else if int(dis.ItemID) == lb.style.hoverIndex {
|
||
|
if hTheme == 0 {
|
||
|
lb.style.BackgroundColor = lb.themeNormalBGColor
|
||
|
} else {
|
||
|
lb.style.BackgroundColor = lb.themeSelectedBGColor
|
||
|
}
|
||
|
lb.style.TextColor = lb.themeNormalTextColor
|
||
|
} else {
|
||
|
lb.style.BackgroundColor = lb.themeNormalBGColor
|
||
|
lb.style.TextColor = lb.themeNormalTextColor
|
||
|
}
|
||
|
if lb.themeNormalTextColor == RGB(0, 0, 0) {
|
||
|
lb.style.LineColor = RGB(0, 0, 0)
|
||
|
} else {
|
||
|
lb.style.LineColor = RGB(255, 255, 255)
|
||
|
}
|
||
|
|
||
|
lb.style.DrawBackground()
|
||
|
|
||
|
lb.styler.StyleItem(&lb.style)
|
||
|
|
||
|
defer func() {
|
||
|
lb.style.bounds = Rectangle{}
|
||
|
if lb.style.canvas != nil {
|
||
|
lb.style.canvas.Dispose()
|
||
|
lb.style.canvas = nil
|
||
|
}
|
||
|
lb.style.hdc = 0
|
||
|
}()
|
||
|
|
||
|
return win.TRUE
|
||
|
|
||
|
case win.WM_WINDOWPOSCHANGED:
|
||
|
wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))
|
||
|
|
||
|
if wp.Flags&win.SWP_NOSIZE != 0 {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
if lb.styler != nil && lb.styler.ItemHeightDependsOnWidth() {
|
||
|
width := lb.WidthPixels()
|
||
|
if width != lb.lastWidth {
|
||
|
lb.lastWidth = width
|
||
|
lb.lastWidthsMeasuredFor = make([]int, lb.model.ItemCount())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lb.ensureVisibleItemsHeightUpToDate()
|
||
|
|
||
|
case win.WM_VSCROLL:
|
||
|
lb.ensureVisibleItemsHeightUpToDate()
|
||
|
|
||
|
case win.WM_MOUSEWHEEL:
|
||
|
lb.ensureVisibleItemsHeightUpToDate()
|
||
|
|
||
|
case win.WM_LBUTTONDOWN:
|
||
|
lb.Invalidate()
|
||
|
|
||
|
case win.WM_MOUSEMOVE:
|
||
|
if !lb.trackingMouseEvent {
|
||
|
var tme win.TRACKMOUSEEVENT
|
||
|
tme.CbSize = uint32(unsafe.Sizeof(tme))
|
||
|
tme.DwFlags = win.TME_LEAVE
|
||
|
tme.HwndTrack = lb.hWnd
|
||
|
|
||
|
lb.trackingMouseEvent = win.TrackMouseEvent(&tme)
|
||
|
}
|
||
|
|
||
|
oldHoverIndex := lb.style.hoverIndex
|
||
|
|
||
|
result := uint32(lb.SendMessage(win.LB_ITEMFROMPOINT, 0, lParam))
|
||
|
if win.HIWORD(result) == 0 {
|
||
|
index := int(win.LOWORD(result))
|
||
|
|
||
|
var rc win.RECT
|
||
|
lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc)))
|
||
|
|
||
|
lp := uint32(lParam)
|
||
|
x := int32(win.LOWORD(lp))
|
||
|
y := int32(win.HIWORD(lp))
|
||
|
|
||
|
if x >= rc.Left && x <= rc.Right && y >= rc.Top && y <= rc.Bottom {
|
||
|
lb.style.hoverIndex = index
|
||
|
|
||
|
win.InvalidateRect(lb.hWnd, &rc, true)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if lb.style.hoverIndex != oldHoverIndex {
|
||
|
if wParam&win.MK_LBUTTON != 0 {
|
||
|
lb.Invalidate()
|
||
|
} else {
|
||
|
lb.invalidateItem(oldHoverIndex)
|
||
|
lb.invalidateItem(lb.style.hoverIndex)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case win.WM_MOUSELEAVE:
|
||
|
lb.trackingMouseEvent = false
|
||
|
|
||
|
index := lb.style.hoverIndex
|
||
|
|
||
|
lb.style.hoverIndex = -1
|
||
|
|
||
|
lb.invalidateItem(index)
|
||
|
|
||
|
case win.WM_COMMAND:
|
||
|
switch win.HIWORD(uint32(wParam)) {
|
||
|
case win.LBN_SELCHANGE:
|
||
|
lb.ensureVisibleItemsHeightUpToDate()
|
||
|
lb.prevCurIndex = lb.CurrentIndex()
|
||
|
lb.currentIndexChangedPublisher.Publish()
|
||
|
lb.selectedIndexesChangedPublisher.Publish()
|
||
|
|
||
|
case win.LBN_DBLCLK:
|
||
|
lb.itemActivatedPublisher.Publish()
|
||
|
}
|
||
|
|
||
|
case win.WM_GETDLGCODE:
|
||
|
if form := ancestor(lb); form != nil {
|
||
|
if dlg, ok := form.(dialogish); ok {
|
||
|
if dlg.DefaultButton() != nil {
|
||
|
// If the ListBox lives in a Dialog that has a DefaultButton,
|
||
|
// we won't swallow the return key.
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if wParam == win.VK_RETURN {
|
||
|
return win.DLGC_WANTALLKEYS
|
||
|
}
|
||
|
|
||
|
case win.WM_KEYDOWN:
|
||
|
if uint32(lParam)>>30 == 0 && Key(wParam) == KeyReturn && lb.CurrentIndex() > -1 {
|
||
|
lb.itemActivatedPublisher.Publish()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return lb.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) invalidateItem(index int) {
|
||
|
var rc win.RECT
|
||
|
lb.SendMessage(win.LB_GETITEMRECT, uintptr(index), uintptr(unsafe.Pointer(&rc)))
|
||
|
|
||
|
win.InvalidateRect(lb.hWnd, &rc, true)
|
||
|
}
|
||
|
|
||
|
func (lb *ListBox) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
|
||
|
return NewGreedyLayoutItem()
|
||
|
}
|