walk icon indicating copy to clipboard operation
walk copied to clipboard

Is there any way to use DataBinder with ListBox?

Open yf-hk opened this issue 5 years ago • 5 comments

What I really like to do is to use a single DataBinder to manage all the input fields within a form. Is it doable?

yf-hk avatar Aug 27 '20 08:08 yf-hk

Thanks for bringing this up. It looks like it is not yet possible. If time permits, I will try to do something about it next week.

lxn avatar Aug 27 '20 17:08 lxn

Implemented in https://github.com/lxn/walk/commit/f6c18dbf6bd964bcd544fa602acb832567c215ad, please test!

lxn avatar Aug 31 '20 13:08 lxn

@lxn Thanks for a great package. I've just gotten into Windows GUI dev in Go thanks to this! <3

I'm trying to get the DataBinder on a ListBox and could really need a guiding nudge to what i am doing wrong.

With the radio buttons it seems to be working fine but i can't seem to get the value of the ListBox selection to propagate into my DataBinder

Thanks again for a awesome package!

This is a standalone code i wrote from all my parts to give you the whole picture:

package main

import (
	"fmt"
	"log"
	"sort"
	"time"

	"github.com/lxn/walk"
	dec "github.com/lxn/walk/declarative"
	"k8s.io/klog/v2"
)

type Raids []*Raid

type Raid struct {
	Date   time.Time     `json:"date"`
	Length time.Duration `json:"length"`
	Title  string        `json:"title"`
	Desc   string        `json:"desc"`
}

type signWindow struct {
	*walk.MainWindow
	clb        *walk.ListBox
	speclb     *walk.ListBox
	dataBinder *walk.DataBinder
}

type signupState struct {
	Status string
	Class  string
	Spec   string
	Role   string
}

func main() {
	raid := &Raid{
		Date:   time.Now().Add(time.Hour * 24 * 10),
		Length: time.Duration(2 * time.Hour),
		Title:  "Mythic Reclear",
		Desc:   "Clearing to hungering destroyer then going lady",
	}

	mw := new(signWindow)
	state := &signupState{}

	classListBox := dec.ListBox{
		AssignTo:      &mw.clb,
		Name:          "classListBox",
		Visible:       dec.Bind("attending.Checked"),
		BindingMember: "Class",
		Model:         ClassesNew,
		OnCurrentIndexChanged: func() {
			i := mw.clb.CurrentIndex()
			if i < 0 {
				return
			}
			if name, ok := ClassesNew.Value(i).(string); ok {
				c := Classes[name]
				var specs []string
				for _, cp := range c.Specs {
					specs = append(specs, cp.Name)
				}
				sortStringSlice(specs)
				mw.speclb.SetModel(specs)
				return
			}
		},
	}

	specListBox := dec.ListBox{
		AssignTo:      &mw.speclb,
		BindingMember: "Role",
		Visible:       dec.Bind("classListBox.CurrentIndex >= 0"),
	}

	window := dec.MainWindow{
		AssignTo: &mw.MainWindow,
		Name:     "signWindow",
		Title:    fmt.Sprintf("%s - %s", raid.Title, raid.Date),
		Layout:   dec.VBox{},
		DataBinder: dec.DataBinder{
			AssignTo:   &mw.dataBinder,
			Name:       "state",
			DataSource: state,
			AutoSubmit: true,
			OnSubmitted: func() {
				klog.Info(state)
				if state.Status == "Bench" {
					mw.clb.SetCurrentIndex(-1)
					mw.speclb.SetCurrentIndex(-1)
					return
				}
			},
		},
		MinSize: dec.Size{Width: 600, Height: 300},
		Size:    dec.Size{Width: 600, Height: 300},
		Children: []dec.Widget{
			dec.VSplitter{
				Children: []dec.Widget{
					dec.RadioButtonGroup{
						DataMember: "Status",
						Buttons: []dec.RadioButton{
							{
								Name:  "attending",
								Text:  "Attending",
								Value: "Attending",
							},
							{
								Name:  "bench",
								Text:  "Bench",
								Value: "Bench",
							},
						},
					},
					classListBox,
					specListBox,
					dec.PushButton{
						Text: "Sign",
					},
				},
			},
		},
	}
	if err := window.Create(); err != nil {
		klog.Error(err)
	}

	mw.MainWindow.Run()
}

func sortStringSlice(s []string) {
	sort.Slice(s, func(i, j int) bool {
		return s[i] < s[j]
	})
}

// Models

type Class struct {
	ID    int
	Name  string
	Specs []Spec
}

func (c Class) String() string {
	return c.Name
}

type Spec struct {
	ID   int
	Name string
	Role Role
	Type Type
}

type Role int
type Type int

func (r Role) String() string {
	return [...]string{"Tank", "Healer", "DPS"}[r]
}

const (
	Tank = iota
	Healer
	DPS

	Melee = iota
	Range
)

type ClassList struct {
	walk.ListModelBase
	items []Class
}

var ClassesNew = &ClassList{
	items: []Class{DeathKnight, DemonHunter, Druid, Hunter, Mage, Monk, Paladin, Priest, Rogue, Shaman, Warlock, Warrior},
}

func (c *ClassList) ItemCount() int {
	return len(c.items)
}

func (c *ClassList) Value(index int) interface{} {
	return c.items[index].Name
}

var (
	Classes = map[string]Class{
		"Death Knight": DeathKnight, // 6
		"Demon Hunter": DemonHunter, // 12
		"Druid":        Druid,       // 11
		"Hunter":       Hunter,      // 3
		"Mage":         Mage,        // 8
		"Monk":         Monk,        // 10
		"Paladin":      Paladin,     // 2
		"Priest":       Priest,      // 5
		"Rogue":        Rogue,       // 4
		"Shaman":       Shaman,      // 7
		"Warlock":      Warlock,     // 9
		"Warrior":      Warrior,     // 1
	}

	Warrior = Class{
		ID:   1,
		Name: "Warrior",
		Specs: []Spec{
			{
				ID:   71,
				Name: "Arms",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   72,
				Name: "Fury",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   73,
				Name: "Protection",
				Role: Tank,
				Type: Melee,
			},
		},
	}

	Paladin = Class{
		ID:   2,
		Name: "Paladin",
		Specs: []Spec{
			{
				ID:   65,
				Name: "Holy",
				Role: Healer,
				Type: Melee,
			},
			{
				ID:   66,
				Name: "Protection",
				Role: Tank,
				Type: Melee,
			},
			{
				ID:   70,
				Name: "Retribution",
				Role: Healer,
				Type: Melee,
			},
		},
	}

	Hunter = Class{
		ID:   3,
		Name: "Hunter",
		Specs: []Spec{
			{
				ID:   253,
				Name: "Beast Mastery",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   254,
				Name: "Marksmanship",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   254,
				Name: "Survival",
				Role: DPS,
				Type: Range,
			},
		},
	}

	Rogue = Class{
		ID:   4,
		Name: "Rogue",
		Specs: []Spec{
			{
				ID:   259,
				Name: "Assassination",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   260,
				Name: "Outlaw",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   261,
				Name: "Subtlety",
				Role: DPS,
				Type: Melee,
			},
		},
	}

	Priest = Class{
		ID:   5,
		Name: "Priest",
		Specs: []Spec{
			{
				ID:   256,
				Name: "Discipline",
				Role: Healer,
				Type: Range,
			},
			{
				ID:   257,
				Name: "Holy",
				Role: Healer,
				Type: Range,
			},
			{
				ID:   258,
				Name: "Shadow",
				Role: DPS,
				Type: Range,
			},
		},
	}

	DeathKnight = Class{
		ID:   6,
		Name: "Death Knight",
		Specs: []Spec{
			{
				ID:   250,
				Name: "Blood",
				Role: Tank,
				Type: Melee,
			},
			{
				ID:   251,
				Name: "Frost",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   252,
				Name: "Unholy",
				Role: DPS,
				Type: Melee,
			},
		},
	}

	Shaman = Class{
		ID:   7,
		Name: "Shaman",
		Specs: []Spec{
			{
				ID:   262,
				Name: "Elemental",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   263,
				Name: "Enhancement",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   264,
				Name: "Restoration",
				Role: Healer,
				Type: Range,
			},
		},
	}

	Mage = Class{
		ID:   8,
		Name: "Mage",
		Specs: []Spec{
			{
				ID:   62,
				Name: "Arcane",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   63,
				Name: "Fire",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   64,
				Name: "Frost",
				Role: DPS,
				Type: Range,
			},
		},
	}

	Warlock = Class{
		ID:   9,
		Name: "Warlock",
		Specs: []Spec{
			{
				ID:   265,
				Name: "Affliction",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   266,
				Name: "Demonology",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   267,
				Name: "Destruction",
				Role: DPS,
				Type: Range,
			},
		},
	}

	Monk = Class{
		ID:   10,
		Name: "Monk",
		Specs: []Spec{
			{
				ID:   268,
				Name: "Brewmaster",
				Role: Tank,
				Type: Melee,
			},
			{
				ID:   269,
				Name: "Windwalker",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   270,
				Name: "Mistweaver",
				Role: Healer,
				Type: Melee,
			},
		},
	}

	Druid = Class{
		ID:   11,
		Name: "Druid",
		Specs: []Spec{
			{
				ID:   102,
				Name: "Balance",
				Role: DPS,
				Type: Range,
			},
			{
				ID:   103,
				Name: "Feral",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   104,
				Name: "Guardian",
				Role: Tank,
				Type: Melee,
			},
			{
				ID:   105,
				Name: "Restoration",
				Role: Healer,
				Type: Range,
			},
		},
	}

	DemonHunter = Class{
		ID:   12,
		Name: "Demon Hunter",
		Specs: []Spec{
			{
				ID:   577,
				Name: "Havoc",
				Role: DPS,
				Type: Melee,
			},
			{
				ID:   581,
				Name: "Vengeance",
				Role: Tank,
				Type: Melee,
			},
		},
	}
)

roffe avatar Apr 04 '21 17:04 roffe

Thanks for the kind words, @roffe.

Please try something like this:

...
	// You may actually want to bind to `Class.ID`, instead of `Class.Name`.
	// In that case, you would need `ClassID` in `signupState` and use 
	// `BindingMember: "ID"` together with `Value: dec.Bind("ClassID")`.
	classListBox := dec.ListBox{
		AssignTo:      &mw.clb,
		Name:          "classListBox",
		Visible:       dec.Bind("attending.Checked"),
		BindingMember: "Name",
		DisplayMember: "Name",
		Value:         dec.Bind("Class"),
		Model:         ClassesNew,
		OnCurrentIndexChanged: func() {
...
type ClassList struct {
	walk.ReflectListModelBase
	items []Class
}

var ClassesNew = &ClassList{
	items: []Class{DeathKnight, DemonHunter, Druid, Hunter, Mage, Monk, Paladin, Priest, Rogue, Shaman, Warlock, Warrior},
}

func (c *ClassList) Items() interface{} {
	return c.items
}
...

lxn avatar Apr 05 '21 07:04 lxn

Thank you very much. this works as a charm and I now have a much better understanding of how the api for DataBinder works!

roffe avatar Apr 05 '21 10:04 roffe