golang fyne dialog doesn't update on user choice

45 views Asked by At

EDIT


Working solution found. See NEW SECTION below.


Original Question:

Trying to make a classic function to call a file, read a csv, ask the user which columns to keep, and then return the resulting minimized dataframe.

The outcome is that the dialog works, but it doesn't update the result of the table. It just outputs the whole table as if the user did not do anything.

I've tried using goroutines to block execution, but then the dialog wouldn't work either. In fact, the whole program froze permanently.

If you have done something like this before, don't bother reading what I did and trying to correct it, feel free to just tell me how you might handle it.

(Related: I've checked main () not waiting for update of file dialog in golang using fyne for file dialog but I don't know how I could do a direct Set() here given the more complex return types.)

Here's what I've got:

func callFile(path string, w2 fyne.Window) (*widget.Table, *dataframe.DataFrame) {
    file, err := os.Open(path)
    if err != nil {
        fmt.Println(err)
        return nil, nil
    }
    csvDf := dataframe.ReadCSV(file)
    NewDF := csvDf.Select(csvDf.Names())
    table := widget.NewTable(
        //[REMOVED TO SHORTEN THE POST]
    )
    //THIS NEXT SECTION IS ONLY RELEVANT TO MAKING THE DIALOG UI TEXT. FEEL FREE TO SKIP
    ---------------
    coln := csvDf.Names()
    typn := []string{}
    for _, colName := range coln {
        currcol := csvDf.Col(colName)
        // Get the data type of the column
        typn = append(typn, fmt.Sprint(currcol.Type()))
    }
    ----------------
    checks := []*widget.FormItem{}
    boundChecks := make(map[string]bool)
    for i, v := range coln {
        //making the specific key for the column
        staticref := v + " (" + fmt.Sprint(typn[i]) + ")"
        boundChecks[staticref] = true
        tempFI := widget.NewFormItem(staticref,
                                     widget.NewCheck("", func(value bool) {
                                     boundChecks[staticref] = value
                                     }))
        checks = append(checks, tempFI)
    }
    columnsToKeep := []string{}
    dialog.ShowForm("Smart Select", "That should do it", "Close", checks, func(b bool) {
        if b {
            for i, v := range coln {
                staticref := v + " (" + fmt.Sprint(typn[i]) + ")"
                keep := boundChecks[staticref]
                if keep {
               // if the user checked this box 
                        columnsToKeep = append(columnsToKeep, v)
                            }
                                    }
            NewDF = csvDf.Select(columnsToKeep)
            table = ... // make the table again with NewDF instead of csvDf
        } else {
           // if the user doesn't check any boxes then all the columns will be selected
        }
    }, w2)

    return table, &NewDF
}

NEW SECTION


A working solution using data bindings worked here, but that might not be the best solution. It takes in an untyped list data binding. That gets initialized as:

BT := binding.NewUntypedList()
var BTinit []interface{}
BT.Set(BTinit)

Then the callFile function is handled as:

// add a listener to respond to do whatever you want with the dataframe that 
// the callFile function will return
nbt := binding.NewDataListener(func() {
currBT, emptyError := BT.Get()
    if emptyError == nil && len(currBT) > 0 {
     switch dfpInter := currBT[0].(type) {
            case *widget.Table:
            switch DFInter := currBT[1].(type) {
            case dataframe.DataFrame:
        //do whatever you want with your dataframe DFInter
                        }
                    default:
    fmt.Println("for some reason it didn't return a dataframe")
                    }
                }
            })
            BT.AddListener(nbt)
            callFile(FPATH, w2, BT)

Now actually make the dialog that lets the user open the file and pick their data:

func callFile(path string, w2 fyne.Window, BT binding.UntypedList) {
    // Open the file at the given path
    file, err := os.Open(path)
    if err != nil {
        fmt.Println(err)
        return
    }

    // Read the CSV file into a dataframe
    csvDf := dataframe.ReadCSV(file)

    // Determine the data types of each column in the dataframe
    coln := csvDf.Names()
    typn := []string{}
    for _, colName := range coln {
        currcol := csvDf.Col(colName)
        typn = append(typn, fmt.Sprint(currcol.Type()))
    }

    // Create checkboxes for each column to allow the user to select which columns to keep
    checks := []*widget.FormItem{}
    boundChecks := make(map[string]bool)
    for i, v := range coln {
        staticref := v + " (" + fmt.Sprint(typn[i]) + ")"
        boundChecks[staticref] = false
        this_check := widget.NewCheck("", func(value bool) {
            boundChecks[staticref] = value
        })
        if i == 0 {
            this_check.SetChecked(true)
        }
        tempFI := widget.NewFormItem(staticref, this_check)
        checks = append(checks, tempFI)
    }

    // Show a dialog to the user to select which columns to keep
    dialog.ShowForm("Select the columns you are interested in", "That should do it", "Close", checks, func(b bool) {
        if b {
            columnsToKeep := []string{}
            for i, v := range coln {
                staticref := v + " (" + fmt.Sprint(typn[i]) + ")"
                keep := boundChecks[staticref]
                if keep {
                    columnsToKeep = append(columnsToKeep, v)
                }
            }
            NewDF := csvDf.Select(columnsToKeep)

            // Create a table widget to display the selected columns
            table := widget.NewTable(
                func() (int, int) {
                    return NewDF.Nrow() + 1, NewDF.Ncol()
                },
                func() fyne.CanvasObject {
                    return widget.NewLabel("December 15")
                },
                func(i widget.TableCellID, o fyne.CanvasObject) {
                    if i.Row == 0 {
                        o.(*widget.Label).SetText(NewDF.Names()[i.Col])
                        o.(*widget.Label).Alignment = fyne.TextAlignCenter
                    } else {
                        colName := NewDF.Names()[i.Col]
                        txt_0 := NewDF.Col(colName).Subset(i.Row - 1).String()
                        txt_0 = txt_0[1:]
                        txt_0 = txt_0[:len(txt_0)-1]
                        o.(*widget.Label).SetText(txt_0)
                    }
                },
            )

            // Update the binding with the table and the new dataframe
            BT.Set([]interface{}{table, NewDF})
        }
    }, w2)
}

This is different than the original solution in that it doesn't recreate the table but instead waits for the user to pick the columns of data that they want before creating it.

1

There are 1 answers

1
andy.xyz On

You should not make a new table if the data changes - you should call table.Refresh() and it will read the new data. If that does not work then the other posters are correct your data update code is not functioning as you think.