golang原生gui框架

2023-05-05  本文已影响0人  Yellowtail

概述

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

以下是找到的框架列表

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()
}

上一篇下一篇

猜你喜欢

热点阅读