lxn / walk

A Windows GUI toolkit for the Go Programming Language
Other
6.79k stars 885 forks source link

Is there any way to use DataBinder with ListBox? #714

Open dzcpy opened 3 years ago

dzcpy commented 3 years ago

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

lxn commented 3 years ago

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 commented 3 years ago

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

roffe commented 3 years ago

@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,
            },
        },
    }
)
lxn commented 3 years ago

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
}
...
roffe commented 3 years ago

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