Go紅隊(duì)開發(fā)—圖形化界面
好久沒做golang開發(fā)了,之前的文章一直在做cli的安全工具開發(fā),這里了解一下gui圖形化的開發(fā),后續(xù)目前還不知道能發(fā)什么了,主要是cli和gui這些無非都是將之前學(xué)過的集成在一起而已,我個人是感覺這個合集已經(jīng)差不多完成了,若是還有在看我這個合集的師傅覺得還想看什么的可以給一些意見。
GUi 圖形化
這里只使用 fyne 庫,其他庫不討論。
配置
- 配置GCC:我下載的Gcc版本
拿這個來配置的好處就是不用安裝,直接下載解壓,然后配置環(huán)境變量即可

- 運(yùn)?時,需要開啟CGO_ENABLED=1
go env -w CGO_ENABLED=1
# 解釋
Fyne 的相關(guān)源碼?件帶有 cgo 構(gòu)建標(biāo)簽。你如果把 CGO_ENABLED=0 關(guān)掉了,帶該標(biāo)簽的源碼會被排除,編譯器要么找不到實(shí)現(xiàn),要么?到不兼容的路徑,從?出現(xiàn)“build constraints exclude all Go files” 之類的錯誤。
反正跟著來就行了
- 看你使用的是什么環(huán)境,我使用的windows,我要切換編譯平臺回來
go env -w GOOS=windows
# 若是linux就修改linux
- 編寫一個 “Hello Fyne” 簡單窗口
- 當(dāng)然創(chuàng)建go項(xiàng)目的時候記得:
go mod init 項(xiàng)目名; go mod tidy
- 當(dāng)然創(chuàng)建go項(xiàng)目的時候記得:
package main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
)
func main() {
myApp := app.New()
myWin := myApp.NewWindow("Hello")
myWin.SetContent(widget.NewLabel("Hello Fyne!"))
myWin.Resize(fyne.NewSize(200, 200))
myWin.ShowAndRun()
}
- 運(yùn)行代碼之前一定要重新打開一下你的vscode或者你的代碼編輯器或者終端,為了讓之前設(shè)置的環(huán)境變量生效
# 再次運(yùn)行代碼
go run main.go
下圖中我的fyne沒有用v2,我后面換了,我上面代碼是正確的,只是圖片中我忘記切換新版本的庫了

第一個GUI
func first_gui() {
myApp := app.New() // 創(chuàng)建一個app
myWin := myApp.NewWindow("Hello") // 創(chuàng)建一個窗口,之后就要放內(nèi)容進(jìn)這個窗口了,同時給這個窗口命名
myWin.SetContent(widget.NewLabel("Hello Fyne!")) //簡單設(shè)置一個標(biāo)簽內(nèi)容,然后用窗口的SetContent設(shè)置內(nèi)容
myWin.Resize(fyne.NewSize(200, 200)) // 設(shè)置窗口大小
myWin.ShowAndRun() // Show顯示和Run運(yùn)行,這里其實(shí)是可以分開用兩個函數(shù)執(zhí)行,ShowAndRun就是一個命令執(zhí)行了兩個
}
沒什么好說,直接看代碼來的直接
常用 widget 組件
widget.NewLabel
標(biāo)簽組件,這個就是標(biāo)簽文本widget.NewButton
按鈕,第二個參數(shù)傳入函數(shù),表示這個按鈕被點(diǎn)擊后的action動作widget.NewEntry
組件實(shí)體,控件用于給用戶輸入簡單的文本內(nèi)容SetReadOnly(true/false)設(shè)置是否只讀SetPlaceHolder設(shè)置占位字符widget.NewEntry().MultiLine = true這樣設(shè)置可以多行文本
widget.NewPasswordEntry
密碼輸入框,這個和widget.NewEntry一樣,只不過這里是密碼的方式輸入,所以輸入的內(nèi)容看不到NewMultiLineEntry
多行文本輸入,但其實(shí)上面也可以通過MultiLine = true的方式進(jìn)行多行輸入widget.NewCheck
復(fù)選框widget.NewRadioGroup
單選框,舊版本好像是widget.NewRadio,v2的就用widget.NewRadioGroupwidget.NewSelect
下拉框widget.NewSlider
滑塊widget.NewProgressBar
進(jìn)度條,通過SetValue來控制進(jìn)度條的滑動widget.NewProgressBarInfinite
無限進(jìn)度條widget.NewSeparator
分割線widget.NewVBox
簡單的水平或垂直的容器,Box可以對放入box的控件采用布局widget.NewCard
卡片,給標(biāo)題和這個卡片的內(nèi)容
目前可以簡單的通過container來new一個box進(jìn)行整合控件在某個容器里
看下面的代碼案例中最后return即可(在basicWidgets函數(shù)中)
穿插下container:container.NewVBox和 【container.NewScroll、container.NewHScroll】
container.NewVBox
這個很重要,因?yàn)槲覀兛梢源虬臻g在這個box里面,然后這個box就可以作為某個模塊插入到你想要的功能看模塊中去了,當(dāng)然這里使用的是VBox,V表示垂直的布局,后面會學(xué)到HBox,表示水平布局container.NewScroll
這個是整合box的時候,container作為上下滑動還是左右滑動,通過傳入VBox還是HBox來判斷左右還是上下滑動,一般都是VBox上下滑動,因?yàn)槲覀兞?xí)慣就是這樣,比較好看。
widget.NewSelectEntry
可輸入的下拉框widget.NewAccordion
父:折疊面板
子:通過widget.NewAccordionItem來創(chuàng)建展開后的面板項(xiàng)目widget.NewForm
表單,這里看代碼吧,涉及到提交和取消函數(shù)
// Form - 表單
nameEntry := widget.NewEntry()
ageEntry := widget.NewEntry()
genderRadio := widget.NewRadioGroup([]string{"男", "?"}, nil)
form := widget.NewForm(
widget.NewFormItem("姓名", nameEntry),
widget.NewFormItem("年齡", ageEntry),
widget.NewFormItem("性別", genderRadio),
)
form.OnSubmit = func() {
fmt.Printf("表單提交 - 姓名: %s, 年齡: %s, 性別: %s\n",
nameEntry.Text, ageEntry.Text, genderRadio.Selected)
}
form.OnCancel = func() {
fmt.Println("表單取消")
}
widget.NewTabContainer
標(biāo)簽容器,這個可以理解為像瀏覽器不同窗口之間切換的樣子
這個顯示的標(biāo)簽可以修改位置:平時瀏覽器的窗口都是顯示在上方,這里修改的位置就是窗口的那個位置TabLocationBottom:顯示在底部TabLocationLeading:顯示在頂部左邊TabLocationTrailing:顯示在頂部右邊
直接看一段簡單的偽代碼:
tabs := widget.NewTabContainer(
widget.NewTabItem("Profile", profile),
widget.NewTabItem("Setting", setting),
)
myWindow.SetContent(tabs)
widget.NewToolbar
工具欄,很簡單的用法,就是NewToolbarAction創(chuàng)建然后第一個參數(shù)給圖標(biāo),第二個參數(shù)給action動作
直接看下面的代碼:

func fyne_toolbar() {
myApp := app.New()
myWindow := myApp.NewWindow("Toolbar")
toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.DocumentCreateIcon(), func() {
fmt.Println("New document")
}),
widget.NewToolbarSeparator(),
widget.NewToolbarAction(theme.ContentCutIcon(), func() {
fmt.Println("Cut")
}),
widget.NewToolbarAction(theme.ContentCopyIcon(), func() {
fmt.Println("Copy")
}),
widget.NewToolbarAction(theme.ContentPasteIcon(), func() {
fmt.Println("Paste")
}),
widget.NewToolbarSpacer(),
widget.NewToolbarAction(theme.HelpIcon(), func() {
log.Println("Display help")
}),
)
content := fyne.NewContainerWithLayout(
layout.NewBorderLayout(toolbar, nil, nil, nil),
toolbar, widget.NewLabel(`Lorem ipsum dolor,
sit amet consectetur adipisicing elit.
Quidem consectetur ipsam nesciunt,
quasi sint expedita minus aut,
porro iusto magnam ducimus voluptates cum vitae.
Vero adipisci earum iure consequatur quidem.`),
)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
Layout 布局
首先我們的控件都是要放進(jìn)box或者一個容器中,所以我們布局要學(xué)的東西在上面可能有看到過,只是沒注意到這個是什么布局罷了
container.New
沒有布局,要v2版本的fyne,舊版本記得是沒有的container.NewVBox
V表示垂直布局container.NewHBox
H表示水平布局container.NewBorder
邊框布局layout.NewGridLayout
固定列數(shù)?格,這個要用一個空的container然后再傳入布局
gridLayout := container.New(
layout.NewGridLayout(3), // 3列
widget.NewButton("1", nil),
widget.NewButton("2", nil),
widget.NewButton("3", nil),
widget.NewButton("4", nil),
widget.NewButton("5", nil),
widget.NewButton("6", nil),
)
container.NewGridWithColumns
指定列數(shù)的?格container.NewGridWithRows
指定?數(shù)的?格container.NewCenter
居中布局container.NewMax
最大化布局container.NewStack
堆疊布局container.NewPadded
帶內(nèi)邊距的布局container.NewScroll
這個是垂直的滾動
絕對布局
container.NewWithoutLayout
創(chuàng)建一個沒有布局的容器,然后自己來一個個放組件進(jìn)去,定位也自己寫
直接看代碼:
// 絕對定位容器
absolute := container.NewWithoutLayout(
widget.NewButton("按鈕1", nil),
widget.NewButton("按鈕2", nil),
widget.NewLabel("標(biāo)簽"),
)
// ?動設(shè)置位置和??
// 容器里面的組件就按照你當(dāng)初放的那樣,進(jìn)行一個數(shù)組訪問即可
// Move移動,Resize重置大小
if len(absolute.Objects) >= 3 {
absolute.Objects[0].Move(fyne.NewPos(10, 10))
absolute.Objects[0].Resize(fyne.NewSize(100, 30))
absolute.Objects[1].Move(fyne.NewPos(120, 10))
absolute.Objects[1].Resize(fyne.NewSize(100, 30))
absolute.Objects[2].Move(fyne.NewPos(10, 50))
absolute.Objects[2].Resize(fyne.NewSize(210, 30))
}
return widget.NewCard("絕對定位布局", "", absolute)
dialog彈框
這個就是一些遇到錯誤就彈框出來,或者刪除保存彈出來讓你確認(rèn)之類的等等的一些彈框
注意事項(xiàng)
- 你這個彈框?qū)?yīng)的父窗口一定要看清楚了
比如你是在A窗口彈框的,那就傳A的這個窗口進(jìn)去作為參數(shù)給到該彈框,告訴他應(yīng)該在A窗口中進(jìn)行彈框
否則可能會出現(xiàn)看不到或者被覆蓋等等未知情況
類別
- 信息彈框
這個就是簡單一個彈框提示
dialog.ShowInformation(title, message, parentWindow)
- 錯誤彈框
需要傳入錯誤類型,將你這個錯誤信息彈出來
dialog.ShowError(err, parentWindow)
- 確認(rèn)彈框
“確定/取消”,回調(diào)接收布爾值,使用該bool來判斷用戶是確定還是取消接著下一步操作
dialog.ShowConfirm(title, message, func(confirmed bool), parentWindow)
- 自定義彈框
這個其實(shí)就是一套娃,點(diǎn)擊某個功能后,你希望彈出什么內(nèi)容都可以,嵌入該對話框中,比如你點(diǎn)擊后彈出的框是另外一個功能更多的程序都可以,但是這樣你的這個彈框就有點(diǎn)大了,這個還是看具體情況具體分析。
NewCustom與NewCustomConfirm的區(qū)別:NewCustom:這個只有一個關(guān)閉按鈕,NewCustomConfirm:會讓你去“確認(rèn)/取消”,然后拿到用戶的確認(rèn)或取消的結(jié)果進(jìn)行一下步操作
dialog.NewCustom(title, dismissText, content, parentWindow)
dialog.NewCustomConfirm(title, confirmText, dismissText, content, func(confirmed bool), parentWindow)
- 打開文件/文件夾窗口
- 能夠?qū)Υ蜷_文件夾后做的一些限制:
SetFilter(storage.NewExtensionFileFilter([]string{".txt"}))限制可選可看到的文件后綴類型SetLocation(storage.NewFileURI(path))設(shè)置初始位置
- 回調(diào)參數(shù)為 nil 表示用戶取消
- 能夠?qū)Υ蜷_文件夾后做的一些限制:
dialog.NewFileOpen(func(fyne.URIReadCloser, error), parentWindow)
dialog.NewFileSave(func(fyne.URIWriteCloser, error), parentWindow)
dialog.NewFolderOpen(func(fyne.ListableURI, error), parentWindow)
//限制可選可看到的文件后綴類型
SetFilter(storage.NewExtensionFileFilter([]string{".txt"}))
//設(shè)置初始位置
SetLocation(storage.NewFileURI(path))
- 進(jìn)度彈框
舉例:點(diǎn)擊開始掃描,然后彈進(jìn)度條框/旋轉(zhuǎn)等待任務(wù)完成,這種就很常見- 需要配合
fyne.Do去執(zhí)行
- 需要配合
//通過SetValue設(shè)置進(jìn)度
dialog.NewProgress(title, message, parentWindow)
//顯示的是旋轉(zhuǎn),這里就不用setvalue了,沒有進(jìn)度大小
dialog.NewProgressInfinite(title, message, parentWindow)
在 Fyne v2.6.0 及以上版本中,如果你在 非主線程(例如在 goroutine 中)更新UI組件,必須使用 fyne.Do 或 fyne.DoAndWait 來包裝這些操作
fyne.Do 與 fyne.DoAndWait:fyne.Do 會異步地將函數(shù)調(diào)度到主線程執(zhí)行,不會阻塞你當(dāng)前的 goroutine,適用于像更新進(jìn)度條這樣的場景。fyne.DoAndWait 則會同步等待函數(shù)在主線程執(zhí)行完畢
案例demo所有代碼
如下圖所示:很多測試單元函數(shù)被我注釋了,想要運(yùn)行哪個就自己解開,不能運(yùn)行多個,只能一個一個的函數(shù)去運(yùn)行,因?yàn)槲覜]有單獨(dú)把窗口app拎出來

- 源代碼如下
package main
import (
"fmt"
"image/color"
"log"
"net/url"
"os"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" // 導(dǎo)?擴(kuò)展包
"github.com/flopp/go-findfont"
)
func fyne_test() {
myApp := app.New()
myWin := myApp.NewWindow("窗口標(biāo)題")
myWin.SetContent(widget.NewLabel("label內(nèi)容"))
myWin.Resize(fyne.NewSize(200, 200)) // 設(shè)置窗口大小
myWin.CenterOnScreen() // 窗口居中顯示
// ---------------------------------
// 全屏
// myWin.SetFullScreen(true)
// 判斷是否全屏
// isFullScrenn := myWin.FullScreen()
// fmt.Println(isFullScrenn)
// ---------------------------------
// ---------------------------------
// 主窗口設(shè)置
myWin.SetMaster()
mainMenu := fyne.NewMainMenu(
fyne.NewMenu(
"文件",
fyne.NewMenuItem("新建", func() { fmt.Println("點(diǎn)擊了新建") }),
fyne.NewMenuItem("打開", func() { fmt.Println("點(diǎn)擊了打開") }),
fyne.NewMenuItem("推出", func() { myApp.Quit() }),
),
)
myWin.SetMainMenu(mainMenu)
// ---------------------------------
// 攔截關(guān)閉事件
myWin.SetCloseIntercept(func() {
dialog.ShowConfirm("確認(rèn)", "確定要退出嗎?", func(ok bool) {
if ok {
myApp.Quit()
}
}, myWin)
})
myWin.ShowAndRun() //運(yùn)行
}
// 更新時間
func time_clock() {
updateTime := func(clock *widget.Label) {
formatted := time.Now().Format("Time: 03:04:05") //獲取格式化時間
clock.SetText(formatted)
}
app := app.New() // 創(chuàng)建應(yīng)用程序?qū)嵗? window := app.NewWindow("Hello world") // 創(chuàng)建窗口,標(biāo)題為"Hello Wolrd"
clock := widget.NewLabel("")
updateTime(clock)
go func() {
for range time.Tick(time.Second) {
updateTime(clock) // 每秒更新一次時間
}
}()
window.SetContent(clock) // 往窗口中放入一個內(nèi)容為"Hello world!"的標(biāo)簽控件
window.ShowAndRun() //展示并運(yùn)行程序
}
// 基礎(chǔ)控件
func basicWidgets() fyne.CanvasObject {
// 1. Label - ?本標(biāo)簽
label := widget.NewLabel("這是?個標(biāo)簽")
// 2. Button - 按鈕
button := widget.NewButton("點(diǎn)擊我", func() {
fmt.Println("按鈕被點(diǎn)擊")
})
// 3. Entry - 單?輸?框
entry := widget.NewEntry()
entry.SetPlaceHolder("請輸??本..")
// 4. PasswordEntry - 密碼輸?框
passwordEntry := widget.NewPasswordEntry()
passwordEntry.SetPlaceHolder("輸?密碼...")
// 5. MultiLineEntry - 多??本輸?
multiEntry := widget.NewMultiLineEntry()
multiEntry.SetPlaceHolder("多??本...")
multiEntry.Resize(fyne.NewSize(300, 100))
// 6. Check - 復(fù)選框
check := widget.NewCheck("同意條款", func(checked bool) {
fmt.Println("復(fù)選框狀態(tài):", checked)
})
// 7. RadioGroup - 單選按鈕組
radio := widget.NewRadioGroup([]string{"選項(xiàng)1", "選項(xiàng)2", "選項(xiàng)3"}, func(value string) {
fmt.Println("選中:", value)
})
// 8. Select - 下拉選擇框
selectWidget := widget.NewSelect([]string{"蘋果", "?蕉", "橙?"}, func(value string) {
fmt.Println("選擇了:", value)
})
// 9. Slider - 滑塊
slider := widget.NewSlider(0, 100)
slider.OnChanged = func(value float64) {
fmt.Printf("滑塊值: %.2f", value)
}
// 10. ProgressBar - 進(jìn)度條
progress := widget.NewProgressBar()
progress.SetValue(0.5) // 50%
// 11. ProgressBarInfinite - ?限進(jìn)度條
infiniteProgress := widget.NewProgressBarInfinite()
// 12. Hyperlink - 超鏈接
link, _ := url.Parse("https://fyne.io")
hyperlink := widget.NewHyperlink("訪問 Fyne 官?", link)
// 13. Separator - 分隔線
separator := widget.NewSeparator()
return container.NewVBox(
widget.NewCard("基礎(chǔ)控件", "", container.NewVBox(
label,
button,
entry,
passwordEntry,
multiEntry,
check,
radio,
selectWidget,
slider,
progress,
infiniteProgress,
hyperlink,
separator,
)),
)
}
func basicwidget_test() {
myapp := app.New()
w := myapp.NewWindow("基礎(chǔ)控件?例")
widgets := basicWidgets()
w.SetContent(widgets)
w.ShowAndRun()
}
// 高級控件
func advancedWidgets() fyne.CanvasObject {
// 1. Form - 表單
nameEntry := widget.NewEntry()
ageEntry := widget.NewEntry()
genderRadio := widget.NewRadioGroup([]string{"男", "?"}, nil)
form := widget.NewForm(
widget.NewFormItem("姓名", nameEntry),
widget.NewFormItem("年齡", ageEntry),
widget.NewFormItem("性別", genderRadio),
)
form.OnSubmit = func() {
fmt.Printf("表單提交 - 姓名: %s, 年齡: %s, 性別: %s\n",
nameEntry.Text, ageEntry.Text, genderRadio.Selected)
}
form.OnCancel = func() {
fmt.Println("表單取消")
}
// 2. 日期選擇器 - 自定義實(shí)現(xiàn)
selectedDateLabel := widget.NewLabel("未選擇日期")
dateEntry := widget.NewEntry()
dateEntry.SetPlaceHolder("YYYY-MM-DD")
dateButton := widget.NewButton("選擇今日", func() {
today := time.Now().Format("2006-01-02")
dateEntry.SetText(today)
selectedDateLabel.SetText("選中日期: " + today)
fmt.Println("選擇的日期:", today)
})
dateContainer := container.NewVBox(
selectedDateLabel,
dateEntry,
dateButton,
)
// 3. SelectEntry - 可輸?的下拉框
selectEntry := widget.NewSelectEntry([]string{"選項(xiàng)1", "選項(xiàng)2", "選項(xiàng)3"})
selectEntry.PlaceHolder = "輸?或選擇..."
selectEntry.OnChanged = func(value string) {
fmt.Println("選擇或輸?:", value)
}
// 4. Accordion - 折疊?板
accordion := widget.NewAccordion(
widget.NewAccordionItem("基本信息",
container.NewVBox(
widget.NewLabel("這是基本信息?板"),
widget.NewEntry(),
)),
widget.NewAccordionItem("?級設(shè)置",
container.NewVBox(
widget.NewLabel("這是?級設(shè)置?板"),
widget.NewCheck("啟??級功能", nil),
)),
widget.NewAccordionItem("其他選項(xiàng)",
container.NewVBox(
widget.NewLabel("這是其他選項(xiàng)?板"),
widget.NewSlider(0, 100),
)),
)
// //左右滑動的container
// scrollContainer := container.NewHScroll(container.NewVBox(
// widget.NewLabel("這是左滑的"),
// widget.NewLabel("這是右滑的"),
// ))
// 組合所有控件 上下滑動
return container.NewScroll(container.NewVBox(
widget.NewCard("表單控件", "", form),
widget.NewCard("?期選擇", "", container.NewVBox(
dateContainer,
widget.NewSeparator(),
)),
widget.NewCard("可輸?下拉框", "", selectEntry),
widget.NewCard("折疊?板", "", accordion),
))
}
func advancedWidgets_test() {
myapp := app.New()
w := myapp.NewWindow("高級控件?例")
widgets := advancedWidgets()
w.SetContent(widgets)
w.ShowAndRun()
}
// 假設(shè)你使用的是舊版本,不是v2以上的,那若你有中文的話,就需要進(jìn)行中文字體設(shè)置
func packet_myfont() {
fontPath, err := findfont.Find("FontLibrary/MSYHBD.TTC") // 這個字體文件直接找自己喜歡的即可,能成功加載路徑即可
if err != nil {
panic(err)
}
// load the font with the freetype library
// fontData, err := os.ReadFile(fontPath)
// if err != nil {
// panic(err)
// }
// _, err = truetype.Parse(fontData)
// if err != nil {
// panic(err)
// }
os.Setenv("FYNE_FONT", fontPath)
}
// 畫布練習(xí)
func fyne_canvas() {
myApp := app.New()
myWin := myApp.NewWindow("畫布測試")
myWin.Resize(fyne.NewSize(400, 300))
myWin.ShowAndRun()
}
// 工具欄練習(xí)
func fyne_toolbar() {
myApp := app.New()
myWindow := myApp.NewWindow("Toolbar")
toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.DocumentCreateIcon(), func() {
fmt.Println("New document")
}),
widget.NewToolbarSeparator(),
widget.NewToolbarAction(theme.ContentCutIcon(), func() {
fmt.Println("Cut")
}),
widget.NewToolbarAction(theme.ContentCopyIcon(), func() {
fmt.Println("Copy")
}),
widget.NewToolbarAction(theme.ContentPasteIcon(), func() {
fmt.Println("Paste")
}),
widget.NewToolbarSpacer(),
widget.NewToolbarAction(theme.HelpIcon(), func() {
log.Println("Display help")
}),
)
content := fyne.NewContainerWithLayout(
layout.NewBorderLayout(toolbar, nil, nil, nil),
toolbar, widget.NewLabel(`Lorem ipsum dolor,
sit amet consectetur adipisicing elit.
Quidem consectetur ipsam nesciunt,
quasi sint expedita minus aut,
porro iusto magnam ducimus voluptates cum vitae.
Vero adipisci earum iure consequatur quidem.`),
)
myWindow.SetContent(content)
myWindow.ShowAndRun()
}
// 相對布局練習(xí)
func fyne_Layouts() {
myApp := app.New()
myWin := myApp.NewWindow("Layouts")
// 設(shè)置窗???
myWin.Resize(fyne.NewSize(600, 700))
// 1. BoxLayout - 垂直布局
vboxLayout := container.NewVBox(
widget.NewLabel("垂直布局 - 項(xiàng)?1"),
widget.NewLabel("垂直布局 - 項(xiàng)?2"),
widget.NewButton("按鈕", nil),
)
// 2. BoxLayout - ?平布局
hboxLayout := container.NewHBox(
widget.NewLabel("?平1"),
widget.NewLabel("?平2"),
widget.NewButton("按鈕", nil),
)
// 3. BorderLayout - 邊框布局
borderLayout := container.NewBorder(
widget.NewLabel("頂部"), // top
widget.NewLabel("底部"), // bottom
widget.NewLabel("左側(cè)"), // left
widget.NewLabel("右側(cè)"), // right
widget.NewLabel("中?內(nèi)容"), // center
)
// 4. GridLayout - 固定列數(shù)?格
gridLayout := container.New(
layout.NewGridLayout(3), // 3列
widget.NewButton("1", nil),
widget.NewButton("2", nil),
widget.NewButton("3", nil),
widget.NewButton("4", nil),
widget.NewButton("5", nil),
widget.NewButton("6", nil),
)
// 5. GridWithColumns - 指定列數(shù)的?格
gridWithColumns := container.NewGridWithColumns(2,
widget.NewLabel("單元格 1"),
widget.NewLabel("單元格 2"),
widget.NewLabel("單元格 3"),
widget.NewLabel("單元格 4"),
)
// 6. GridWithRows - 指定?數(shù)的?格
gridWithRows := container.NewGridWithRows(3,
widget.NewLabel("? 1"),
widget.NewLabel("? 2"),
widget.NewLabel("? 3"),
)
// 7. CenterLayout - 居中布局
centerLayout := container.NewCenter(
widget.NewLabel("居中的內(nèi)容"),
)
// 8. MaxLayout - 最?化布局(重疊)
maxLayout := container.NewMax(
canvas.NewRectangle(color.RGBA{R: 100, G: 100, B: 100, A: 255}),
container.NewCenter(widget.NewLabel("覆蓋在矩形上")),
)
// 9. StackLayout - 堆疊布局
stackLayout := container.NewStack(
canvas.NewRectangle(color.RGBA{R: 200, G: 0, B: 0, A: 100}),
container.NewCenter(widget.NewLabel("堆疊內(nèi)容")),
)
// 10. PaddedLayout - 帶內(nèi)邊距的布局
paddedLayout := container.NewPadded(
widget.NewButton("有內(nèi)邊距的按鈕", nil),
)
w := container.NewScroll(container.NewVBox(
widget.NewCard("VBox 垂直布局", "", vboxLayout),
widget.NewCard("HBox ?平布局", "", hboxLayout),
widget.NewCard("Border 邊框布局", "", borderLayout),
widget.NewCard("Grid ?格布局", "", gridLayout),
widget.NewCard("GridWithColumns", "", gridWithColumns),
widget.NewCard("GridWithRows", "", gridWithRows),
widget.NewCard("Center 居中布局", "", centerLayout),
widget.NewCard("Max 最?化布局", "", maxLayout),
widget.NewCard("Stack 堆疊布局", "", stackLayout),
widget.NewCard("Padded 內(nèi)邊距布局", "", paddedLayout),
))
myWin.SetContent(w)
myWin.ShowAndRun()
}
// 絕對布局練習(xí)
func fyne_absolute_layout() {
myApp := app.New()
myWin := myApp.NewWindow("絕對定位布局")
myWin.Resize(fyne.NewSize(300, 200))
// 絕對定位容器
absolute := container.NewWithoutLayout(
widget.NewButton("按鈕1", nil),
widget.NewButton("按鈕2", nil),
widget.NewLabel("標(biāo)簽"),
)
// ?動設(shè)置位置和??
// 容器里面的組件就按照你當(dāng)初放的那樣,進(jìn)行一個數(shù)組訪問即可
// Move移動,Resize重置大小
if len(absolute.Objects) >= 3 {
absolute.Objects[0].Move(fyne.NewPos(10, 10))
absolute.Objects[0].Resize(fyne.NewSize(100, 30))
absolute.Objects[1].Move(fyne.NewPos(120, 10))
absolute.Objects[1].Resize(fyne.NewSize(100, 30))
absolute.Objects[2].Move(fyne.NewPos(10, 50))
absolute.Objects[2].Resize(fyne.NewSize(210, 30))
}
w := widget.NewCard("絕對定位布局", "", absolute)
myWin.SetContent(w)
myWin.ShowAndRun()
}
func fyne_dialog_for_NewProgess() {
myApp := app.New()
myWindow := myApp.NewWindow("Determinate Progress Example")
progressInfo := widget.NewLabel("準(zhǔn)備開始...")
button := widget.NewButton("開始任務(wù)", func() {
// 創(chuàng)建進(jìn)度對話框,初始進(jìn)度0%
progDialog := dialog.NewProgress("處理中", "正在進(jìn)行一項(xiàng)耗時的確定性任務(wù)...", myWindow)
progDialog.Show()
totalSteps := 100
// 使用 fyne.Do 確保初始設(shè)置在主線程執(zhí)行
fyne.Do(func() {
progDialog.SetValue(0) // 確保從0開始
})
// 模擬任務(wù)進(jìn)度更新
go func() {
for i := 0; i <= totalSteps; i++ {
currentI := i // 創(chuàng)建局部變量避免閉包問題
currentProgress := float64(currentI) / float64(totalSteps)
if currentI == totalSteps {
time.Sleep(500 * time.Millisecond) // 最后稍作停頓
// 使用 fyne.Do 包裝UI更新
fyne.Do(func() {
progDialog.SetValue(1.0) // 完成時設(shè)置為1.0 (100%)
progressInfo.SetText("任務(wù)完成!")
})
time.Sleep(500 * time.Millisecond)
fyne.Do(func() {
progDialog.Hide() // 完成任務(wù)后隱藏
})
return
}
// 使用 fyne.Do 包裝所有UI更新操作
fyne.Do(func() {
progDialog.SetValue(currentProgress) // 更新進(jìn)度 (0.0 到 1.0)
progressInfo.SetText(fmt.Sprintf("進(jìn)度: %d/%d", currentI, totalSteps))
})
time.Sleep(50 * time.Millisecond) // 模擬工作
}
}()
})
content := container.NewVBox(progressInfo, button)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(400, 200))
myWindow.ShowAndRun()
}
func fyne_dialog_for_NewProgessInfinite() {
myApp := app.New()
myWindow := myApp.NewWindow("Infinite Progress Example")
statusLabel := widget.NewLabel("等待操作...")
startButton := widget.NewButton("開始不確定任務(wù)", func() {
// 創(chuàng)建無限進(jìn)度對話框
infiniteDialog := dialog.NewProgressInfinite("請等待", "正在進(jìn)行一項(xiàng)不確定時間的任務(wù)...", myWindow)
infiniteDialog.Show()
fyne.Do(func() {
statusLabel.SetText("任務(wù)進(jìn)行中...")
})
// 模擬一個耗時不確定的任務(wù)
go func() {
time.Sleep(5 * time.Second) // 模擬工作,比如網(wǎng)絡(luò)請求
// 使用 fyne.Do 包裝UI更新
fyne.Do(func() {
infiniteDialog.Hide() // 任務(wù)完成后隱藏對話框
statusLabel.SetText("不確定任務(wù)已完成!")
})
}()
})
content := container.NewVBox(statusLabel, startButton)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(400, 200))
myWindow.ShowAndRun()
}
func test_Progess() {
//這里是手動切換調(diào)用哪個的函數(shù)方法, 我在main中調(diào)用他這個函數(shù)即可
// fyne_dialog_for_NewProgess()
fyne_dialog_for_NewProgessInfinite()
}
func main() {
// packet_myfont() //設(shè)置中文字體,不亂碼
// fyne_test()
// basicwidget_test()
// advancedWidgets_test()
// time_clock()
// fyne_canvas() //畫布測試
// fyne_toolbar() // 工具欄
// fyne_Layouts() // 相對布局測試
// fyne_absolute_layout() // 絕對布局測試
test_Progess() //進(jìn)度條測試
}
本文來自博客園,作者:竹等寒,轉(zhuǎn)載請注明原文鏈接。

浙公網(wǎng)安備 33010602011771號