golang原生gui框架

概述

最近想动手做一些小工具,命令行输入有诸多麻烦,没有可视化工具直观,于是想写gui程序
语言的话,选来选去,还是想用golang写,锻炼一下自己
于是找了找开源的 golang gui框架
发现大多需要CGO编译,嫌麻烦,找了几个go 实现的gui框架,无需c++编译器

以下是找到的框架列表

  • gio
  • goey
  • walk
  • govcl
  • winc

gio

这个框架的代码没有在github

key value
官网 https://gioui.org/
代码 https://git.sr.ht/~eliasnaur/gio/tree
官方例子 https://git.sr.ht/~eliasnaur/gio-example
煮蛋器详细教程 https://jonegil.github.io/gui-with-gio/
优点 可以很细腻的调整样式
缺点 调样式的时候,需要一层一层的写func(return x.Layout),感觉非常啰嗦;输入框不支持中文,调了好久,不是很熟悉,无果,放弃了
个人评价 文档太少了,官网也很简略,写起来感觉很啰嗦、别扭,只想写一个小工具,不想整那些啰里啰嗦的样式;仓库网站不是很熟悉,代码看起来也很别扭

以下是在界面上展现一个输入框、一个按钮的代码
(不要在意界面是否美观,样式我瞎写的)


不要在意界面是否美观,我没认真调整
package main

import (
    "image/color"
    "log"
    "os"

    "gioui.org/app"
    "gioui.org/font/gofont"
    "gioui.org/io/system"
    "gioui.org/layout"
    "gioui.org/op"
    "gioui.org/widget/material"

    "gioui.org/unit"
    "gioui.org/widget"
)

func main() {
    go func() {
        w := app.NewWindow()
        err := run(w)
        if err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }()
    app.Main()
}

func run(w *app.Window) error {

    for {
        e := <-w.Events()
        switch e := e.(type) {
        case system.DestroyEvent:
            return e.Err
        case system.FrameEvent:

            myWindow(e)
        }
    }
}

func myWindow(e system.FrameEvent) {

    var ops op.Ops

    th := material.NewTheme(gofont.Collection())

    gtx := layout.NewContext(&ops, e)

    var startButton widget.Clickable
    var input1 widget.Editor

    // layout.Dimensions

    layout.Flex{
        // Vertical alignment, from top to bottom
        Axis: layout.Vertical,
        // Empty space is left at the start, i.e. at the top
        Spacing: layout.SpaceStart,
    }.Layout(gtx,
        // We insert two rigid elements:
        // First a button ...
        layout.Rigid(
            func(gtx layout.Context) layout.Dimensions {
                margins := layout.Inset{
                    Top:    unit.Dp(25),
                    Right:  unit.Dp(25),
                    Bottom: unit.Dp(25),
                    Left:   unit.Dp(25),
                }

                return margins.Layout(gtx, func(gtx layout.Context) layout.Dimensions {

                    // ... and borders ...
                    border := widget.Border{
                        Color:        color.NRGBA{R: 204, G: 204, B: 204, A: 255},
                        CornerRadius: unit.Dp(3),
                        Width:        unit.Dp(2),
                    }

                    input := material.Editor(th, &input1, "ha")
                    return border.Layout(gtx, input.Layout)
                })

            },
        ),
        layout.Rigid(
            func(gtx layout.Context) layout.Dimensions {
                margins := layout.Inset{
                    Top:    unit.Dp(25),
                    Bottom: unit.Dp(25),
                    Right:  unit.Dp(35),
                    Left:   unit.Dp(35),
                }
                return margins.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
                    btn := material.Button(th, &startButton, "Start")
                    return btn.Layout(gtx)
                })

            },
        ),
    )

    e.Frame(gtx.Ops)
}

goey

代码在 bitbucket

key value
官网 https://bitbucket.org/rj/goey/src/v0.9.0/
官方例子 https://bitbucket.org/rj/goey/src/v0.9.0/example/
优点 写起来还算舒服,默认样式够用,不用怎么调整
缺点 控件的value的更新,需要重新绘制整个窗口,感觉很麻烦、耦合
个人评价 官方例子丰富,上手简单,支持minifest,就是重新绘制窗口这个很别扭

[图片上传失败...(image-77c39f-1683305015690)]

代码写起来还算ok

package main

import (
    "fmt"
    "strconv"

    "bitbucket.org/rj/goey"
    "bitbucket.org/rj/goey/base"
    "bitbucket.org/rj/goey/loop"
)

var (
    mainWindow *goey.Window

    feetValue  string
    meterValue string
)

func main() {
    err := loop.Run(createWindow)
    if err != nil {
        fmt.Println("Error: ", err.Error())
    }
}

func createWindow() error {
    // Add the controls
    mw, err := goey.NewWindow("Feet to Meters", render())
    if err != nil {
        return err
    }
    mainWindow = mw

    return nil
}

func update() {
    err := mainWindow.SetChild(render())
    if err != nil {
        fmt.Println("Error: ", err.Error())
    }
}

func render() base.Widget {
    return &goey.Padding{
        Insets: goey.DefaultInsets(),
        Child: &goey.Align{Child: &MinSizedBox{Child: &goey.VBox{
            AlignMain: goey.MainCenter,
            Children: []base.Widget{
                &goey.HBox{
                    AlignMain:  goey.Homogeneous,
                    AlignCross: goey.CrossCenter,
                    Children: []base.Widget{
                        &goey.Empty{},
                        &goey.TextInput{Value: feetValue, OnChange: func(v string) { feetValue = v }, OnEnterKey: func(v string) { feetValue = v; calculate() }},
                        &goey.Label{Text: "feet"},
                    },
                }, &goey.HBox{
                    AlignMain:  goey.Homogeneous,
                    AlignCross: goey.CrossCenter,
                    Children: []base.Widget{
                        &goey.Label{Text: "is equivalent to"},
                        &goey.Label{Text: meterValue},
                        &goey.Label{Text: "meters"},
                    },
                }, &goey.HBox{
                    AlignMain:  goey.Homogeneous,
                    AlignCross: goey.CrossCenter,
                    Children: []base.Widget{
                        &goey.Empty{},
                        &goey.Empty{},
                        &goey.Button{Text: "Calculate", Default: true, OnClick: calculate},
                    },
                },
            },
        }}},
    }
}

func calculate() {
    feet, err := strconv.ParseFloat(feetValue, 64)
    if err != nil {
        meterValue = "(error)"
    } else {
        meterValue = fmt.Sprintf("%f", feet*0.3048)
    }
    update()
}

walk

代码在 github

key value
官网 https://github.com/lxn/walk
官方例子 https://github.com/lxn/walk/tree/master/examples
优点 写起来还算舒服,支持控件内容单独刷新,控件还算丰富
缺点 样式有一些bug,包括调整不生效,显示空白等 ;必须配合 manifest 才能显示界面;上次更新还是2021年
个人评价 页面有一些小bug,不过作为小工具无伤大雅;功能完善:包括控件的种类、动态创建控件等;我用的就是这个
image.png

代码

package main

import (
    "github.com/lxn/walk"
    . "github.com/lxn/walk/declarative"
)

func main() {
    var outTE *walk.TextEdit

    var urlInput *walk.LineEdit
    var pathInput *walk.LineEdit
    var proxyInput *walk.LineEdit

    var startBtn, stopBtn *walk.PushButton

    var mw *MainWindow

    mw = &MainWindow{
        Title:  "漫画下载器",
        Size:   Size{Width: 400, Height: 600},
        Layout: VBox{},
        MenuItems: []MenuItem{
            Menu{
                Text: "&Help",
                Items: []MenuItem{
                    Action{
                        Text:        "About",
                        OnTriggered: func() { aboutAction_Triggered() },
                    },
                    Action{
                        Text:        "页面空白",
                        OnTriggered: func() { tips() },
                    },
                },
            },
        },
        Children: []Widget{
            Label{Text: "页面空白的话,切换一下tab", TextColor: walk.RGB(102, 178, 255)},
            TabWidget{
                Pages: []TabPage{

                    {
                        Title: "下载",
                        // Layout: Grid{Columns: 2},
                        Visible: true,
                        Layout:  VBox{},
                        Children: []Widget{
                            Composite{
                                Layout: Grid{Columns: 2},
                                Children: []Widget{
                                    Label{Text: "网址", TextColor: walk.RGB(255, 0, 127)},
                                    LineEdit{
                                        Text:     "",
                                        AssignTo: &urlInput,
                                    },
                                    Label{Text: "保存路径"},
                                    LineEdit{
                                        Text:     ``,
                                        AssignTo: &pathInput,
                                    },
                                    Label{Text: "代理"},
                                    LineEdit{
                                        Text:     "",
                                        AssignTo: &proxyInput, ToolTipText: "比如 socks5://127.0.0.1:1080",
                                    },
                                },
                            },
                            PushButton{
                                Text:       "下载",
                                AssignTo:   &startBtn,
                                MaxSize:    Size{Width: 400, Height: 100},
                                MinSize:    Size{Width: 300, Height: 30},
                                Background: SolidColorBrush{Color: walk.RGB(0x5F, 0x69, 0x8E)},
                                OnClicked: func() {
                                    url := urlInput.Text()
                                    path := pathInput.Text()

                                    if url == "" {
                                        walk.MsgBox(nil, "错误", "网址为空", walk.MsgBoxIconError)
                                        return
                                    }
                                    if path == "" {
                                        walk.MsgBox(nil, "错误", "网址为空", walk.MsgBoxIconError)
                                        return
                                    }

                                    outTE.SetText("开始下载\r\n")

                                },
                            },
                            PushButton{
                                Text:       "停止",
                                AssignTo:   &stopBtn,
                                MaxSize:    Size{Width: 400, Height: 100},
                                MinSize:    Size{Width: 300, Height: 30},
                                Background: SolidColorBrush{Color: walk.RGB(0x5F, 0x69, 0x8E)},
                                Enabled:    false,
                                OnClicked: func() {

                                },
                            },

                            Composite{
                                Layout: Grid{Columns: 2},
                                Children: []Widget{
                                    Label{Text: "log", TextColor: walk.RGB(255, 0, 127)},
                                    TextEdit{AssignTo: &outTE, ReadOnly: true, VScroll: true},
                                },
                            },
                        },
                    },
                    {
                        Title: "About",
                        // Layout: Grid{Columns: 2},
                        Layout:  VBox{},
                        Visible: true,
                        Children: []Widget{
                            TextEdit{Text: "哈哈哈",
                                ReadOnly: true},
                        },
                    },
                },
            },
        },
    }

    mw.Run()
}

func aboutAction_Triggered() {
    walk.MsgBox(nil,
        "提示",
        "An example that demonstrates a main window that supports multiple pages.",
        walk.MsgBoxOK|walk.MsgBoxIconInformation)
}
func tips() {
    walk.MsgBox(nil,
        "提示",
        "页面空白的话,切换一下tab",
        walk.MsgBoxOK|walk.MsgBoxIconInformation)
}

govcl

key value
官网 https://z-kit.cc/
官方例子 https://github.com/ying32/govcl/tree/master/samples
优点 界面设计用的是可视化工具Lazarus,再转go代码,无需写界面代码,专心写逻辑就行;作者是国人,更新还算及时
缺点 上手的时候,安装配套工具需要花一定时间,装好了就还好;界面代码生成完成之后很难用代码修改
个人评价 开发过程和c# winform 类似,有界面设计工具

winc

key value
官网 https://github.com/tadvi/winc
官方例子 https://github.com/tadvi/winc/tree/master/examples
优点 写起来还算舒服
缺点 控件有点少(也可能是我不熟悉,没找到grid),每个控件都需要手动设置 position(也可能是我不熟悉),有点麻烦
image.png
package main

import (
    "github.com/tadvi/winc"
)

func main() {
    mainWindow := winc.NewForm(nil)
    mainWindow.SetSize(400, 300) // (width, height)
    mainWindow.SetText("Yellow Demo")

    label := winc.NewLabel(mainWindow)
    label.SetText("hei")
    label.SetPos(10, 10)

    edt := winc.NewEdit(mainWindow)
    edt.SetPos(50, 20)
    // Most Controls have default size unless SetSize is called.
    edt.SetText("edit text")

    btn := winc.NewPushButton(mainWindow)
    btn.SetText("Show or Hide")
    btn.SetPos(40, 50)   // (x, y)
    btn.SetSize(100, 40) // (width, height)
    btn.OnClick().Bind(func(e *winc.Event) {
        if edt.Visible() {
            edt.Hide()
        } else {
            edt.Show()

            edt.SetText(edt.Text() + "0,")
        }
    })

    mainWindow.Center()
    mainWindow.Show()
    mainWindow.OnClose().Bind(wndOnClose)

    winc.RunMainLoop() // Must call to start event loop.
}

func wndOnClose(arg *winc.Event) {
    winc.Exit()
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容