fyne-x icon indicating copy to clipboard operation
fyne-x copied to clipboard

CompletionEntry cannot type word when SetText is called in widget.List

Open BabySid opened this issue 2 years ago • 6 comments

As the following code shows, the text in CompletionEntry cannot be edited.

Screenshots

图片

Steps to reproduce the behaviour:

  • Run the code below
package main

import (
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/widget"
	x "fyne.io/x/fyne/widget"
)

var data = []string{"a", "string", "list"}
var option = []string{"option1", "option2", "option3"}

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("List Widget")

	list := widget.NewList(
		func() int {
			return len(data)
		},
		func() fyne.CanvasObject {
			return x.NewCompletionEntry(option)
		},
		func(i widget.ListItemID, o fyne.CanvasObject) {
			e := o.(*x.CompletionEntry)
			e.SetText(data[i])
			e.OnChanged = func(s string) {
				e.ShowCompletion()
			}
		})

	myWindow.SetContent(list)
	myWindow.ShowAndRun()
}

BabySid avatar Apr 02 '22 03:04 BabySid

After debugging, I found that whenever I input in the input box, the widget.List.UpdateItem is also called, so the completionentry.text is set back. It is not clear for me why UpdateItem is called now.

BabySid avatar Apr 02 '22 06:04 BabySid

It is not clear for me why UpdateItem is called now.

It will be called at least whenever a cell may need to be refreshed (usually due to re-use, for example if list scrolls). Additionally it will be called if something calls List.Refresh().

Essentially the problem is that you are mutating data that is being re-loaded from static data. If however you updated your data source on edit (by setting the variable, or by using data binding) the issue would be resolved.

andydotxyz avatar Apr 03 '22 21:04 andydotxyz

Referring to your suggestion, I re implemented the code. But when I specified the method OnChange, the program crashed.

I debuged the code, and found:

func (c *CompletionEntry) maxSize() fyne.Size {
	cnv := fyne.CurrentApp().Driver().CanvasForObject(c) // cnv is nil
        ...
}

The demo code is as beblow:

import (
	"fmt"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/data/binding"
	"fyne.io/fyne/v2/widget"
	x "fyne.io/x/fyne/widget"
)

var option = []string{"option1", "option2", "option3"}

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("List Data")

	data := binding.BindStringList(
		&[]string{"Item 1", "Item 2", "Item 3"},
	)

	list := widget.NewListWithData(
		data,
		func() fyne.CanvasObject {
			return x.NewCompletionEntry(option)
		},
		func(i binding.DataItem, o fyne.CanvasObject) {
			e := o.(*x.CompletionEntry)
			e.Bind(i.(binding.String))
			e.OnChanged = func(s string) {
				e.ShowCompletion()
			}
		})

	add := widget.NewButton("Append", func() {
		val := fmt.Sprintf("Item %d", data.Length()+1)
		data.Append(val)
	})

	myWindow.SetContent(container.NewBorder(nil, add, nil, nil, list))
	myWindow.CenterOnScreen()
	myWindow.Resize(fyne.NewSize(600, 400))
	myWindow.ShowAndRun()
}

BabySid avatar Apr 04 '22 03:04 BabySid

Essentially the problem is that you are mutating data that is being re-loaded from static data. If however you updated your data source on edit (by setting the variable, or by using data binding) the issue would be resolved.

I think the problem should still be on the CompletionEntry component. Because I replace with widget.SelectEntry, the issue is resolved.

BabySid avatar Apr 04 '22 06:04 BabySid

That it works this way with a SelectEntry is more luck than anything. You are applying a static data source to a dynamic item of data, this mismatch may or may not cause problems but either way it is a race condition in your code.

You mentioned that the updated code crashes - can you provide the crash log and the replication steps that led to it please?

andydotxyz avatar Apr 04 '22 11:04 andydotxyz

You mentioned that the updated code crashes - can you provide the crash log and the replication steps that led to it please?

To Reproduce:

Steps to reproduce the behaviour:

  • Run the code below:

Crash Log

GOROOT=D:\ProgramFiles\go1.17.5 #gosetup
GOPATH=C:\Users\sid\go #gosetup
D:\ProgramFiles\go1.17.5\bin\go.exe build -o D:\projects\go-fyne\output\go_build_go_fyne.exe . #gosetup
D:\projects\go-fyne\output\go_build_go_fyne.exe
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0xa8 pc=0xc03f25]

goroutine 9 [running]:
fyne.io/x/fyne/widget.(*CompletionEntry).maxSize(0xc000332400)
        C:/Users/sid/go/pkg/mod/fyne.io/x/[email protected]/widget/completionentry.go:91 +0xe5 
fyne.io/x/fyne/widget.(*CompletionEntry).ShowCompletion(0xc000332400)
        C:/Users/sid/go/pkg/mod/fyne.io/x/[email protected]/widget/completionentry.go:76 +0x1be
main.main.func2.1({0xc000332400, 0xc00008fe60})
        D:/projects/go-fyne/main.go:32 +0x1d
fyne.io/fyne/v2/widget.(*Entry).updateText(0xc000332400, {0xfb0a80, 0x6})
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/widget/entry.go:1179 +0x9f
fyne.io/fyne/v2/widget.(*Entry).SetText(0xc000332400, {0xfb0a80, 0xc0000520a0})
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/widget/entry.go:457 +0x25
fyne.io/fyne/v2/widget.(*Entry).updateFromData(0xc000332400, {0x1068178, 0xc0000520a0})
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/widget/entry.go:1124 +0xc5
fyne.io/fyne/v2/widget.(*basicBinder).Bind.func1()
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/widget/bind_helper.go:25 +0xa2
fyne.io/fyne/v2/data/binding.(*listener).DataChanged(0x1)
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/data/binding/binding.go:55 +0x1a
fyne.io/fyne/v2/data/binding.queueItem.func1.1()
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/data/binding/queue.go:19 +0x42
created by fyne.io/fyne/v2/data/binding.queueItem.func1
        C:/Users/sid/go/pkg/mod/fyne.io/fyne/[email protected]/data/binding/queue.go:17 +0x45

Process finished with the exit code 2

Example Code

package main

import (
	"fmt"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/data/binding"
	"fyne.io/fyne/v2/widget"
	x "fyne.io/x/fyne/widget"
)

var option = []string{"option1", "option2", "option3"}

func main() {
	myApp := app.New()
	myWindow := myApp.NewWindow("List Data")

	data := binding.BindStringList(
		&[]string{"Item 1", "Item 2", "Item 3"},
	)

	list := widget.NewListWithData(
		data,
		func() fyne.CanvasObject {
			return x.NewCompletionEntry(option)
		},
		func(i binding.DataItem, o fyne.CanvasObject) {
			e := o.(*x.CompletionEntry)
			e.Bind(i.(binding.String))
			e.OnChanged = func(s string) {
				e.ShowCompletion()
			}
		})

	add := widget.NewButton("Append", func() {
		val := fmt.Sprintf("Item %d", data.Length()+1)
		data.Append(val)
	})

	myWindow.SetContent(container.NewBorder(nil, add, nil, nil, list))
	myWindow.CenterOnScreen()
	myWindow.Resize(fyne.NewSize(600, 400))
	myWindow.ShowAndRun()
}

BabySid avatar Apr 04 '22 13:04 BabySid