英文:
Making a widget using multiple widgets
问题
我正在尝试创建一个文件选择小部件(我几乎在每个开发任务中都需要用到)。该小部件由一个标签、一个输入框和一个按钮(按照这个顺序)组成,它们都水平地显示在同一行上。
如果可能的话,我希望避免创建一个全新的自定义小部件,因为标准的fyne小部件已经具备了100%的功能。所以我的想法是创建一个自定义布局,并将我的小部件放在其中。
问题是,由于我的构造函数返回的是一个fyne.Container,我无法访问我需要访问的Entry.Text字段。
当然,我可以每次需要访问它时都执行container.Objects[1].(widgets.Entry).Text
,但这似乎不直观。
所以我想,我可以扩展fyne.Container,在其中通过一个方法将Text返回给我,但是似乎container没有类似于ExtendBaseWidget()
的东西来获取容器的所有功能。
以下是我目前的工作代码:
package fyne_custom
import (
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/sqweek/dialog"
)
type FileChoiceLayout struct {
Label, Entry, Button fyne.CanvasObject
}
func (f FileChoiceLayout) MinSize(_ []fyne.CanvasObject) fyne.Size {
total := fyne.NewSize(0, 0)
total = total.Add(f.Label.MinSize())
total = total.Add(f.Entry.MinSize())
total = total.Add(f.Button.MinSize())
return total
}
func (f FileChoiceLayout) Layout(items []fyne.CanvasObject, size fyne.Size) {
topLeft := fyne.NewPos(0, 0)
for _, item := range items {
if item == f.Label {
item.Move(topLeft)
item.Resize(item.MinSize())
topLeft = topLeft.Add(fyne.NewPos(item.MinSize().Width, 0))
} else if item == f.Entry {
item.Move(topLeft)
width := size.Width - f.Label.MinSize().Width - f.Button.MinSize().Width
item.Resize(fyne.NewSize(width, item.MinSize().Height))
topLeft = topLeft.Add(fyne.NewPos(width, 0))
} else if item == f.Button {
item.Move(topLeft)
item.Resize(item.MinSize())
}
}
}
func NewFileChoice(labelText, placeHolder, buttonText string) (*fyne.Container, *widget.Entry) {
label := widget.NewLabel(labelText)
entry := widget.NewEntry()
entry.PlaceHolder = placeHolder
button := widget.NewButton(buttonText, func() {
cwd, _ := os.Getwd()
file, err := dialog.File().Load()
if err != nil {
file = ""
}
if file != "" {
entry.SetText(file)
}
os.Chdir(cwd)
})
container := container.New(&FileChoiceLayout{Label: label, Entry: entry, Button: button}, label, entry, button)
return container, entry
}
我目前找到的临时解决方法是不仅返回容器,还返回输入框小部件以访问其文本字段。
我想肯定有更好的方法,但我找不到。
如果有任何想法,将不胜感激!
英文:
I am trying to make a file choosing widget (which I need on almost every development I am tasked with). The widget is composed of a label, an entry and a button (in that order) and it all appears horizontally on the same line.
I wish to avoid (if possible) making a brand new custom widget as 100% of the functionnality already exists in the sandard fyne widgets. So my idea was to make a custom layout and place my widgets in there.
The problem is that as my constructor function is returning a fyne.Container, I do not have access to the Entry.Text field which I need access to.
I could of course do container.Objects[1].(widgets.Entry).Text
every time I need to access it but that just seems unintuitive.
So then I thought that I would extend the fyne.Container to return the Text to me through a method however it seems container doesn't have something similar to ExtendBaseWidget()
to get all the functionnality of a container.
Here is the working code I have so far:
package fyne_custom
import (
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/sqweek/dialog"
)
type FileChoiceLayout struct {
Label, Entry, Button fyne.CanvasObject
}
func (f FileChoiceLayout) MinSize(_ []fyne.CanvasObject) fyne.Size {
total := fyne.NewSize(0, 0)
total = total.Add(f.Label.MinSize())
total = total.Add(f.Entry.MinSize())
total = total.Add(f.Button.MinSize())
return total
}
func (f FileChoiceLayout) Layout(items []fyne.CanvasObject, size fyne.Size) {
topLeft := fyne.NewPos(0, 0)
for _, item := range items {
if item == f.Label {
item.Move(topLeft)
item.Resize(item.MinSize())
topLeft = topLeft.Add(fyne.NewPos(item.MinSize().Width, 0))
} else if item == f.Entry {
item.Move(topLeft)
width := size.Width - f.Label.MinSize().Width - f.Button.MinSize().Width
item.Resize(fyne.NewSize(width, item.MinSize().Height))
topLeft = topLeft.Add(fyne.NewPos(width, 0))
} else if item == f.Button {
item.Move(topLeft)
item.Resize(item.MinSize())
}
}
}
func NewFileChoice(labelText, placeHolder, buttonText string) (*fyne.Container, *widget.Entry) {
label := widget.NewLabel(labelText)
entry := widget.NewEntry()
entry.PlaceHolder = placeHolder
button := widget.NewButton(buttonText, func() {
cwd, _ := os.Getwd()
file, err := dialog.File().Load()
if err != nil {
file = ""
}
if file != "" {
entry.SetText(file)
}
os.Chdir(cwd)
})
container := container.New(&FileChoiceLayout{Label: label, Entry: entry, Button: button}, label, entry, button)
return container, entry
}
The temporary workaround I found so far is to return not only the container but also the entry widget to access its Text Field.
I'm thinking there must be a better way to do it but I can't seem to find it.
Any idea would be very much appreciated!
答案1
得分: 1
我最终做出了自定义小部件。最后的代码量并不多,而且它按照我的意图工作。唯一的问题是我不确定自己是否做得正确。我的Destroy()和Update()方法是空的,我不确定是否应该这样。而且我不得不使用绑定字符串来在小部件结构体和其渲染器之间同步数据。如果有更好的了解的人有意见,我完全愿意接受!无论如何,如果有人需要这个小部件,这里是代码:
package fyne_custom
import (
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
"github.com/sqweek/dialog"
)
type FileChoice struct {
widget.BaseWidget
path binding.String
labelText, placeHolder, buttonText string
}
func (fc FileChoice) Text() string {
text, _ := fc.path.Get()
return text
}
func NewFileChoice(labelText, placeHolder, buttonText string) *FileChoice {
f := &FileChoice{}
f.path = binding.NewString()
f.labelText = labelText
f.placeHolder = placeHolder
f.buttonText = buttonText
f.ExtendBaseWidget(f)
return f
}
type fileChoiceRender struct {
fc *FileChoice
label *widget.Label
entry *widget.Entry
button *widget.Button
}
func (fcr fileChoiceRender) Objects() []fyne.CanvasObject {
return []fyne.CanvasObject{fcr.label, fcr.entry, fcr.button}
}
func (fcr fileChoiceRender) MinSize() fyne.Size {
var minHeight float32
var totalWidth float32
for _, item := range fcr.Objects() {
if item.MinSize().Height > minHeight {
minHeight = item.MinSize().Height
}
totalWidth += item.MinSize().Width
}
return fyne.NewSize(totalWidth, minHeight)
}
func (fcr fileChoiceRender) Layout(size fyne.Size) {
topLeft := fyne.NewPos(0, 0)
fcr.label.Move(topLeft)
fcr.label.Resize(fyne.NewSize(fcr.label.MinSize().Width, size.Height))
topLeft = topLeft.Add(fyne.NewPos(fcr.label.MinSize().Width, 0))
fcr.entry.Move(topLeft)
fcr.entry.Resize(fyne.NewSize(size.Width-fcr.label.MinSize().Width-fcr.button.MinSize().Width, size.Height))
topLeft = topLeft.Add(fyne.NewPos(size.Width-fcr.label.MinSize().Width-fcr.button.MinSize().Width, 0))
fcr.button.Move(topLeft)
fcr.button.Resize(fyne.NewSize(fcr.button.MinSize().Width, size.Height))
}
func (fcr fileChoiceRender) Destroy() {}
func (fcr fileChoiceRender) Refresh() {}
func (fc FileChoice) CreateRenderer() fyne.WidgetRenderer {
label := widget.NewLabel(fc.labelText)
entry := widget.NewEntryWithData(fc.path)
entry.PlaceHolder = fc.placeHolder
button := widget.NewButton(fc.buttonText, func() {
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
file, err := dialog.File().Load()
if err != nil {
file = ""
}
if file != "" {
entry.SetText(file)
}
})
return &fileChoiceRender{label: label, entry: entry, button: button}
}
英文:
I ended up making the custom widget.
It wasn't that much more code in the end and it works as I intended it. The only thing is that I'm not sure I did it correctly. My Destroy() and Update() methods are empty. I'm not sure if that's how it should be. And I had to use a bind string to get the data to sync between the widget struct and its renderer.
If anyone who knows better has an opinion, I am entirely open to it!
In any case, here is the code if anyone needs this widget:
package fyne_custom
import (
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
"github.com/sqweek/dialog"
)
type FileChoice struct {
widget.BaseWidget
path binding.String
labelText, placeHolder, buttonText string
}
func (fc FileChoice) Text() string {
text, _ := fc.path.Get()
return text
}
func NewFileChoice(labelText, placeHolder, buttonText string) *FileChoice {
f := &FileChoice{}
f.path = binding.NewString()
f.labelText = labelText
f.placeHolder = placeHolder
f.buttonText = buttonText
f.ExtendBaseWidget(f)
return f
}
type fileChoiceRender struct {
fc *FileChoice
label *widget.Label
entry *widget.Entry
button *widget.Button
}
func (fcr fileChoiceRender) Objects() []fyne.CanvasObject {
return []fyne.CanvasObject{fcr.label, fcr.entry, fcr.button}
}
func (fcr fileChoiceRender) MinSize() fyne.Size {
var minHeight float32
var totalWidth float32
for _, item := range fcr.Objects() {
if item.MinSize().Height > minHeight {
minHeight = item.MinSize().Height
}
totalWidth += item.MinSize().Width
}
return fyne.NewSize(totalWidth, minHeight)
}
func (fcr fileChoiceRender) Layout(size fyne.Size) {
topLeft := fyne.NewPos(0, 0)
fcr.label.Move(topLeft)
fcr.label.Resize(fyne.NewSize(fcr.label.MinSize().Width, size.Height))
topLeft = topLeft.Add(fyne.NewPos(fcr.label.MinSize().Width, 0))
fcr.entry.Move(topLeft)
fcr.entry.Resize(fyne.NewSize(size.Width-fcr.label.MinSize().Width-fcr.button.MinSize().Width, size.Height))
topLeft = topLeft.Add(fyne.NewPos(size.Width-fcr.label.MinSize().Width-fcr.button.MinSize().Width, 0))
fcr.button.Move(topLeft)
fcr.button.Resize(fyne.NewSize(fcr.button.MinSize().Width, size.Height))
}
func (fcr fileChoiceRender) Destroy() {}
func (fcr fileChoiceRender) Refresh() {}
func (fc FileChoice) CreateRenderer() fyne.WidgetRenderer {
label := widget.NewLabel(fc.labelText)
entry := widget.NewEntryWithData(fc.path)
entry.PlaceHolder = fc.placeHolder
button := widget.NewButton(fc.buttonText, func() {
cwd, _ := os.Getwd()
defer os.Chdir(cwd)
file, err := dialog.File().Load()
if err != nil {
file = ""
}
if file != "" {
entry.SetText(file)
}
})
return &fileChoiceRender{label: label, entry: entry, button: button}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论