golang原生gui框架

概述

最近想動(dòng)手做一些小工具,命令行輸入有諸多麻煩,沒(méi)有可視化工具直觀,于是想寫(xiě)gui程序
語(yǔ)言的話,選來(lái)選去,還是想用golang寫(xiě),鍛煉一下自己
于是找了找開(kāi)源的 golang gui框架
發(fā)現(xiàn)大多需要CGO編譯,嫌麻煩,找了幾個(gè)go 實(shí)現(xiàn)的gui框架,無(wú)需c++編譯器

以下是找到的框架列表

  • gio
  • goey
  • walk
  • govcl
  • winc

gio

這個(gè)框架的代碼沒(méi)有在github

key value
官網(wǎng) https://gioui.org/
代碼 https://git.sr.ht/~eliasnaur/gio/tree
官方例子 https://git.sr.ht/~eliasnaur/gio-example
煮蛋器詳細(xì)教程 https://jonegil.github.io/gui-with-gio/
優(yōu)點(diǎn) 可以很細(xì)膩的調(diào)整樣式
缺點(diǎn) 調(diào)樣式的時(shí)候,需要一層一層的寫(xiě)func(return x.Layout),感覺(jué)非常啰嗦;輸入框不支持中文,調(diào)了好久,不是很熟悉,無(wú)果,放棄了
個(gè)人評(píng)價(jià) 文檔太少了,官網(wǎng)也很簡(jiǎn)略,寫(xiě)起來(lái)感覺(jué)很啰嗦、別扭,只想寫(xiě)一個(gè)小工具,不想整那些啰里啰嗦的樣式;倉(cāng)庫(kù)網(wǎng)站不是很熟悉,代碼看起來(lái)也很別扭

以下是在界面上展現(xiàn)一個(gè)輸入框、一個(gè)按鈕的代碼
(不要在意界面是否美觀,樣式我瞎寫(xiě)的)


不要在意界面是否美觀,我沒(méi)認(rèn)真調(diào)整
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
官網(wǎng) https://bitbucket.org/rj/goey/src/v0.9.0/
官方例子 https://bitbucket.org/rj/goey/src/v0.9.0/example/
優(yōu)點(diǎn) 寫(xiě)起來(lái)還算舒服,默認(rèn)樣式夠用,不用怎么調(diào)整
缺點(diǎn) 控件的value的更新,需要重新繪制整個(gè)窗口,感覺(jué)很麻煩、耦合
個(gè)人評(píng)價(jià) 官方例子豐富,上手簡(jiǎn)單,支持minifest,就是重新繪制窗口這個(gè)很別扭

[圖片上傳失敗...(image-77c39f-1683305015690)]

代碼寫(xiě)起來(lái)還算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
官網(wǎng) https://github.com/lxn/walk
官方例子 https://github.com/lxn/walk/tree/master/examples
優(yōu)點(diǎn) 寫(xiě)起來(lái)還算舒服,支持控件內(nèi)容單獨(dú)刷新,控件還算豐富
缺點(diǎn) 樣式有一些bug,包括調(diào)整不生效,顯示空白等 ;必須配合 manifest 才能顯示界面;上次更新還是2021年
個(gè)人評(píng)價(jià) 頁(yè)面有一些小bug,不過(guò)作為小工具無(wú)傷大雅;功能完善:包括控件的種類(lèi)、動(dòng)態(tài)創(chuàng)建控件等;我用的就是這個(gè)
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:  "漫畫(huà)下載器",
        Size:   Size{Width: 400, Height: 600},
        Layout: VBox{},
        MenuItems: []MenuItem{
            Menu{
                Text: "&Help",
                Items: []MenuItem{
                    Action{
                        Text:        "About",
                        OnTriggered: func() { aboutAction_Triggered() },
                    },
                    Action{
                        Text:        "頁(yè)面空白",
                        OnTriggered: func() { tips() },
                    },
                },
            },
        },
        Children: []Widget{
            Label{Text: "頁(yè)面空白的話,切換一下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: "網(wǎng)址", 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, "錯(cuò)誤", "網(wǎng)址為空", walk.MsgBoxIconError)
                                        return
                                    }
                                    if path == "" {
                                        walk.MsgBox(nil, "錯(cuò)誤", "網(wǎng)址為空", walk.MsgBoxIconError)
                                        return
                                    }

                                    outTE.SetText("開(kāi)始下載\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,
        "提示",
        "頁(yè)面空白的話,切換一下tab",
        walk.MsgBoxOK|walk.MsgBoxIconInformation)
}

govcl

key value
官網(wǎng) https://z-kit.cc/
官方例子 https://github.com/ying32/govcl/tree/master/samples
優(yōu)點(diǎn) 界面設(shè)計(jì)用的是可視化工具Lazarus,再轉(zhuǎn)go代碼,無(wú)需寫(xiě)界面代碼,專心寫(xiě)邏輯就行;作者是國(guó)人,更新還算及時(shí)
缺點(diǎn) 上手的時(shí)候,安裝配套工具需要花一定時(shí)間,裝好了就還好;界面代碼生成完成之后很難用代碼修改
個(gè)人評(píng)價(jià) 開(kāi)發(fā)過(guò)程和c# winform 類(lèi)似,有界面設(shè)計(jì)工具

winc

key value
官網(wǎng) https://github.com/tadvi/winc
官方例子 https://github.com/tadvi/winc/tree/master/examples
優(yōu)點(diǎn) 寫(xiě)起來(lái)還算舒服
缺點(diǎn) 控件有點(diǎn)少(也可能是我不熟悉,沒(méi)找到grid),每個(gè)控件都需要手動(dòng)設(shè)置 position(也可能是我不熟悉),有點(diǎn)麻煩
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()
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容