我想在Go中编写的命令行应用程序中添加一个GUI,但遇到了fyne和循环依赖关系的问题。
考虑这个简单的例子来说明我面临的问题:假设一个按钮在我的模型类上触发了一个耗时的方法(比如获取数据(,我希望在任务完成时更新视图。
我首先实现了一个非常天真且完全不解耦的解决方案,它显然会遇到go编译器引发的循环依赖性错误。考虑以下代码:
main.go
package main
import (
"my-gui/gui"
)
func main() {
gui.Init()
}
gui/gi.go
package gui
import (
"my-gui/model"
//[...] fyne imports
)
var counterLabel *widget.Label
func Init() {
myApp := app.New()
myWindow := myApp.NewWindow("Test")
counterLabel = widget.NewLabel("0")
counterButton := widget.NewButton("Increment", func() {
go model.DoTimeConsumingStuff()
})
content := container.NewVBox(counterLabel, counterButton)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
func UpdateCounterLabel(value int) {
if counterLabel != nil {
counterLabel.SetText(strconv.Itoa(value))
}
}
型号/型号.go
package model
import (
"my-gui/gui" // <-- this dependency is where it obviously hits the fan
//[...]
)
var counter = 0
func DoTimeConsumingStuff() {
time.Sleep(1 * time.Second)
counter++
fmt.Println("Counter: " + strconv.Itoa(counter))
gui.UpdateCounterLabel(counter)
}
所以我想知道如何正确地解耦这个简单的应用程序,使其工作。我的想法:
使用fyne数据绑定:这应该适用于简单的东西,比如上面例子中的标签文本。但是,如果我必须根据模型的状态以非常自定义的方式更新更多,该怎么办。假设我必须根据模型的条件更新按钮的启用状态。如何将其绑定到数据?这可能吗?
使用标准MVC设计模式中的接口:我也尝试过,但无法真正理解。我创建了一个单独的模块,该模块将提供一个接口,然后可以由模型类导入。然后,我将注册一个视图,该视图(隐式地(实现与模型的接口。但我无法让它发挥作用。在这一点上,我认为我对go接口的理解还不够。
短期轮询模型:这只是meh,当然不是Go和/或fyne的开发者想要的:-(
有人能给我指一个解决这个问题的惯用方法吗?我可能错过了一些非常非常基本的东西。。。
返回值
您可以返回值。
func DoTimeConsumingStuff() int {
time.Sleep(1 * time.Second)
counter++
return counter
}
然后点击按钮,生成一个匿名goroutine,以避免阻塞UI。
counterButton := widget.NewButton("Increment", func() {
go func() {
counter := model.DoTimeConsumingStuff(counterChan)
UpdateCounterLabel(counter)
}()
})
回调
您可以将UpdateCounterLabel
函数传递给您的模型函数,也就是回调函数。
func DoTimeConsumingStuff(callback func(int)) {
time.Sleep(1 * time.Second)
counter++
callback(counter)
}
counterButton := widget.NewButton("Increment", func() {
go model.DoTimeConsumingStuff(UpdateCounterLabel)
})
通道
也许你也可以把一个通道传给你的模型函数。但采用上述方法,似乎不需要这样做。如果您有多个计数器值,则可能会出现这种情况。
func DoTimeConsumingStuff(counterChan chan int) {
for i := 0; i < 10; i++ {
time.Sleep(1 * time.Second)
counter++
counterChan <- counter
}
close(counterChan)
}
在GUI中,您可以再次在goroutine中从通道接收,以避免阻塞UI。
counterButton := widget.NewButton("Increment", func() {
go func() {
counterChan := make(chan int)
go model.DoTimeConsumingStuff(counterChan)
for counter := range counterChan {
UpdateCounterLabel(counter)
}
}()
})
当然,您也可以再次使用在每次迭代中调用的回调。