go - 如何使用 fyne 避免 GUI 应用程序中的循环依赖?

标签 go model-view-controller circular-dependency fyne

我想向我用 Go 编写的命令行应用程序添加 GUI,但我遇到了 fyne 和循环依赖项的问题。

考虑这个简单的例子来说明我面临的问题:假设一个按钮触发我的模型类上的一个耗时的方法(比如获取数据等),并且我希望 View 在任务完成时更新。

我首先实现了一个非常幼稚且完全不解耦的解决方案,这显然会遇到 go 编译器引发的循环依赖错误。考虑以下代码:

ma​​in.go

package main

import (
    "my-gui/gui"
)

func main() {
    gui.Init()
}

gui/gui.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))
    }
}

model/model.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 数据绑定(bind):这应该适用于简单的内容,例如上例中的标签文本。但是,如果我必须根据模型的状态以非常自定义的方式更新更多内容该怎么办?假设我必须根据模型的条件更新按钮的启用状态。这如何与数据绑定(bind)?这可能吗?

  • 按照标准 MVC 设计模式使用接口(interface):我也尝试过这一点,但无法真正理解它。我创建了一个单独的模块,该模块将提供一个接口(interface),然后可以由模型类导入该接口(interface)。然后,我将注册一个 View ,该 View (隐式)使用模型实现该接口(interface)。但我无法让它发挥作用。我认为目前我对 go 接口(interface)的理解还不够。

  • 对模型进行简短的轮询:这只是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)
})

channel

也许您还可以将 channel 传递给模型函数。但通过上述方法,这似乎不是必需的。如果您有多个计数器值,则有可能。

func DoTimeConsumingStuff(counterChan chan int) {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Second)
        counter++
        counterChan <- counter
    }
    close(counterChan)
}

在 GUI 中,您可以再次在 Goroutine 中从 channel 接收信息,以免阻塞 UI。

counterButton := widget.NewButton("Increment", func() {
    go func() {
        counterChan := make(chan int)
        go model.DoTimeConsumingStuff(counterChan)
        for counter := range counterChan {
            UpdateCounterLabel(counter)
        }
    }()      
})

当然,您也可以再次使用在每次迭代时调用的回调。

关于go - 如何使用 fyne 避免 GUI 应用程序中的循环依赖?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71546756/

相关文章:

postgresql - 连接 : connection timed out

c - 为什么我得到的是 Make : Circular Dependency Dropped warning?

c++ - 命名空间内具有函数的循环包含

javascript - AngularJS 绑定(bind)到 WebGL/Canvas

node.js - Strapi 适合服务 html 还是用作传统的 mvc Web 框架?

c# - 在多项目中引发自定义事件

arrays - Go 中数组的处理

go - 编译器是否优化变量声明?

go - 在MacOS上将 `shirou/gopsutil` Go软件包用作 vendor 软件包失败(10.14.6)

java - 一个 Controller 用于不同的 View ?(MVC)