Lists, structs and preferences
Bound lists
A list binding holds a slice and notifies its listeners when items are
added, removed or changed. Pass it to
widget.NewListWithData:
tasks := binding.NewStringList()
_ = tasks.Set([]string{"Write the report", "Book the room"})
list := widget.NewListWithData(tasks,
func() core.CanvasObject {
return widget.NewLabel("template")
},
func(item binding.DataItem, row core.CanvasObject) {
row.(*widget.Label).Bind(item.(binding.String))
},
)The list asks for a row template once per visible row, then calls the
update function with the binding for one item and the row to fill. Rows are
reused as the list scrolls. Calling Bind on the row’s label replaces
whatever it was bound to before, so a reused row always shows the right
item.
Change the list through the binding, and the widget follows:
n := 0
add := widget.NewButton("Add", func() {
n++
_ = tasks.Append(fmt.Sprintf("New task %d", n))
})
removeFirst := widget.NewButton("Remove first", func() {
if tasks.Length() == 0 {
return
}
first, _ := tasks.GetValue(0)
_ = tasks.Remove(first)
})A list binding has Append, Prepend, Remove, Set (replace all items),
SetValue (replace one item), Get, GetValue and Length. Remove
takes a value, not an index, and removes one item equal to it.
There are list bindings for every value type (NewStringList,
NewFloatList, NewIntList and so on), and
binding.NewList
for any type. Trees work the same way with NewStringTree and
widget.NewTreeWithData, and widget.NewGridWrapWithData shows a list as a
wrapping grid.
Bind to your own variable
The Bind… functions bind to a variable you already have instead of
holding their own value:
count := 0
bound := binding.BindInt(&count) // reads and writes count
label := widget.NewLabelWithData(binding.IntToString(bound))
viaBinding := widget.NewButton("Add via binding", func() {
v, _ := bound.Get()
_ = bound.Set(v + 1) // updates count and the label
})
direct := widget.NewButton("Add directly", func() {
count++
_ = bound.Reload() // tell the binding that count changed
})Setting the binding writes to the variable. The reverse is not automatic:
when your code changes the variable directly, call Reload so the binding
notices and tells its listeners. BindString, BindFloat, BindBool and
the other Bind… functions, as well as BindStringList and the other list
forms, all work this way.
Bind struct fields
binding.BindStruct
takes a pointer to a struct and gives one binding per exported field:
type contact struct {
Name string
Email string
}person := &contact{Name: "Ada", Email: "ada@example.com"}
fields := binding.BindStruct(person)
nameItem, _ := fields.GetItem("Name")
emailItem, _ := fields.GetItem("Email")
form := widget.NewForm(
widget.NewFormItem("Name", widget.NewEntryWithData(nameItem.(binding.String))),
widget.NewFormItem("Email", widget.NewEntryWithData(emailItem.(binding.String))),
)GetItem returns the field’s binding as a binding.DataItem. Bool, int,
float and string fields come back as binding.Bool, binding.Int,
binding.Float and binding.String, so assert to the type you need. Editing
the entries changes person. After you change person in code, call
Reload on the struct binding.
For a map[string]any, use binding.NewUntypedMap or
binding.BindUntypedMap.
Bind a preference
Preference bindings read their value from the app’s preferences and save every change back:
nickname := binding.BindPreferenceString("nickname", a.Preferences())
nickEntry := widget.NewEntryWithData(nickname) // saved as you typeThe value is restored the next time the app starts. Preferences need an app
ID, so create the app with app.NewWithID. Bindings for the same key share
one underlying value, and binding one key with two different types logs an
error.
There are BindPreferenceBool, …Float, …Int and …String, and list
forms such as BindPreferenceStringList.
The full programs:
// A bound list shown in a widget.List, with items added and removed at runtime.
package main
import (
"fmt"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/core"
"github.com/belluikit/bellui/data/binding"
"github.com/belluikit/bellui/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Bound list")
tasks := binding.NewStringList()
_ = tasks.Set([]string{"Write the report", "Book the room"})
list := widget.NewListWithData(tasks,
func() core.CanvasObject {
return widget.NewLabel("template")
},
func(item binding.DataItem, row core.CanvasObject) {
row.(*widget.Label).Bind(item.(binding.String))
},
)
n := 0
add := widget.NewButton("Add", func() {
n++
_ = tasks.Append(fmt.Sprintf("New task %d", n))
})
removeFirst := widget.NewButton("Remove first", func() {
if tasks.Length() == 0 {
return
}
first, _ := tasks.GetValue(0)
_ = tasks.Remove(first)
})
w.SetContent(container.NewBorder(container.NewHBox(add, removeFirst), nil, nil, nil, list))
bellui.ShowAndRun(w)
}// Binding to variables you own, to struct fields and to preferences.
package main
import (
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/data/binding"
"github.com/belluikit/bellui/widget"
)
type contact struct {
Name string
Email string
}
func main() {
a := app.NewWithID("com.example.binding")
w := a.NewWindow("External data")
count := 0
bound := binding.BindInt(&count) // reads and writes count
label := widget.NewLabelWithData(binding.IntToString(bound))
viaBinding := widget.NewButton("Add via binding", func() {
v, _ := bound.Get()
_ = bound.Set(v + 1) // updates count and the label
})
direct := widget.NewButton("Add directly", func() {
count++
_ = bound.Reload() // tell the binding that count changed
})
person := &contact{Name: "Ada", Email: "ada@example.com"}
fields := binding.BindStruct(person)
nameItem, _ := fields.GetItem("Name")
emailItem, _ := fields.GetItem("Email")
form := widget.NewForm(
widget.NewFormItem("Name", widget.NewEntryWithData(nameItem.(binding.String))),
widget.NewFormItem("Email", widget.NewEntryWithData(emailItem.(binding.String))),
)
nickname := binding.BindPreferenceString("nickname", a.Preferences())
nickEntry := widget.NewEntryWithData(nickname) // saved as you type
w.SetContent(container.NewVBox(label, viaBinding, direct, form, nickEntry))
bellui.ShowAndRun(w)
}