- GUi 圖形化
- 配置
- 第一個GUI
- 常用 widget 組件
- Layout 佈局
- 絕對佈局
- dialog彈框
- 注意事項
- 類別
- 案例demo所有代碼
好久沒做golang開發了,之前的文章一直在做cli的安全工具開發,這裏瞭解一下gui圖形化的開發,後續目前還不知道能發什麼了,主要是cli和gui這些無非都是將之前學過的集成在一起而已,我個人是感覺這個合集已經差不多完成了,若是還有在看我這個合集的師傅覺得還想看什麼的可以給一些意見。
GUi 圖形化
這裏只使用 fyne 庫,其他庫不討論。
配置
- 配置GCC:我下載的Gcc版本
拿這個來配置的好處就是不用安裝,直接下載解壓,然後配置環境變量即可
- 運⾏時,需要開啓CGO_ENABLED=1
go env -w CGO_ENABLED=1
# 解釋
Fyne 的相關源碼⽂件帶有 cgo 構建標籤。你如果把 CGO_ENABLED=0 關掉了,帶該標籤的源碼會被排除,編譯器要麼找不到實現,要麼⾛到不兼容的路徑,從⽽出現“build constraints exclude all Go files” 之類的錯誤。
反正跟着來就行了
- 看你使用的是什麼環境,我使用的windows,我要切換編譯平台回來
go env -w GOOS=windows
# 若是linux就修改linux
- 編寫一個 “Hello Fyne” 簡單窗口
- 當然創建go項目的時候記得:
go mod init 項目名; go mod tidy
- 當然創建go項目的時候記得:
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()
}
- 運行代碼之前一定要重新打開一下你的vscode或者你的代碼編輯器或者終端,為了讓之前設置的環境變量生效
# 再次運行代碼
go run main.go
下圖中我的fyne沒有用v2,我後面換了,我上面代碼是正確的,只是圖片中我忘記切換新版本的庫了
第一個GUI
func first_gui() {
myApp := app.New() // 創建一個app
myWin := myApp.NewWindow("Hello") // 創建一個窗口,之後就要放內容進這個窗口了,同時給這個窗口命名
myWin.SetContent(widget.NewLabel("Hello Fyne!")) //簡單設置一個標籤內容,然後用窗口的SetContent設置內容
myWin.Resize(fyne.NewSize(200, 200)) // 設置窗口大小
myWin.ShowAndRun() // Show顯示和Run運行,這裏其實是可以分開用兩個函數執行,ShowAndRun就是一個命令執行了兩個
}
沒什麼好説,直接看代碼來的直接
常用 widget 組件
widget.NewLabel
標籤組件,這個就是標籤文本widget.NewButton
按鈕,第二個參數傳入函數,表示這個按鈕被點擊後的action動作widget.NewEntry
組件實體,控件用於給用户輸入簡單的文本內容SetReadOnly(true/false)設置是否只讀SetPlaceHolder設置佔位字符widget.NewEntry().MultiLine = true這樣設置可以多行文本
widget.NewPasswordEntry
密碼輸入框,這個和widget.NewEntry一樣,只不過這裏是密碼的方式輸入,所以輸入的內容看不到NewMultiLineEntry
多行文本輸入,但其實上面也可以通過MultiLine = true的方式進行多行輸入widget.NewCheck
複選框widget.NewRadioGroup
單選框,舊版本好像是widget.NewRadio,v2的就用widget.NewRadioGroupwidget.NewSelect
下拉框widget.NewSlider
滑塊widget.NewProgressBar
進度條,通過SetValue來控制進度條的滑動widget.NewProgressBarInfinite
無限進度條widget.NewSeparator
分割線widget.NewVBox
簡單的水平或垂直的容器,Box可以對放入box的控件採用佈局widget.NewCard
卡片,給標題和這個卡片的內容
目前可以簡單的通過container來new一個box進行整合控件在某個容器裏
看下面的代碼案例中最後return即可(在basicWidgets函數中)
穿插下container:container.NewVBox和 【container.NewScroll、container.NewHScroll】
container.NewVBox
這個很重要,因為我們可以打包空間在這個box裏面,然後這個box就可以作為某個模塊插入到你想要的功能看模塊中去了,當然這裏使用的是VBox,V表示垂直的佈局,後面會學到HBox,表示水平佈局container.NewScroll
這個是整合box的時候,container作為上下滑動還是左右滑動,通過傳入VBox還是HBox來判斷左右還是上下滑動,一般都是VBox上下滑動,因為我們習慣就是這樣,比較好看。
widget.NewSelectEntry
可輸入的下拉框widget.NewAccordion
父:摺疊面板
子:通過widget.NewAccordionItem來創建展開後的面板項目widget.NewForm
表單,這裏看代碼吧,涉及到提交和取消函數
// 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
標籤容器,這個可以理解為像瀏覽器不同窗口之間切換的樣子
這個顯示的標籤可以修改位置:平時瀏覽器的窗口都是顯示在上方,這裏修改的位置就是窗口的那個位置TabLocationBottom:顯示在底部TabLocationLeading:顯示在頂部左邊TabLocationTrailing:顯示在頂部右邊
直接看一段簡單的偽代碼:
tabs := widget.NewTabContainer(
widget.NewTabItem("Profile", profile),
widget.NewTabItem("Setting", setting),
)
myWindow.SetContent(tabs)
widget.NewToolbar
工具欄,很簡單的用法,就是NewToolbarAction創建然後第一個參數給圖標,第二個參數給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 佈局
首先我們的控件都是要放進box或者一個容器中,所以我們佈局要學的東西在上面可能有看到過,只是沒注意到這個是什麼佈局罷了
container.New
沒有佈局,要v2版本的fyne,舊版本記得是沒有的container.NewVBox
V表示垂直佈局container.NewHBox
H表示水平佈局container.NewBorder
邊框佈局layout.NewGridLayout
固定列數⽹格,這個要用一個空的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
指定列數的⽹格container.NewGridWithRows
指定⾏數的⽹格container.NewCenter
居中佈局container.NewMax
最大化佈局container.NewStack
堆疊佈局container.NewPadded
帶內邊距的佈局container.NewScroll
這個是垂直的滾動
絕對佈局
container.NewWithoutLayout
創建一個沒有佈局的容器,然後自己來一個個放組件進去,定位也自己寫
直接看代碼:
// 絕對定位容器
absolute := container.NewWithoutLayout(
widget.NewButton("按鈕1", nil),
widget.NewButton("按鈕2", nil),
widget.NewLabel("標籤"),
)
// ⼿動設置位置和⼤⼩
// 容器裏面的組件就按照你當初放的那樣,進行一個數組訪問即可
// 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彈框
這個就是一些遇到錯誤就彈框出來,或者刪除保存彈出來讓你確認之類的等等的一些彈框
注意事項
- 你這個彈框對應的父窗口一定要看清楚了
比如你是在A窗口彈框的,那就傳A的這個窗口進去作為參數給到該彈框,告訴他應該在A窗口中進行彈框
否則可能會出現看不到或者被覆蓋等等未知情況
類別
- 信息彈框
這個就是簡單一個彈框提示
dialog.ShowInformation(title, message, parentWindow)
- 錯誤彈框
需要傳入錯誤類型,將你這個錯誤信息彈出來
dialog.ShowError(err, parentWindow)
- 確認彈框
“確定/取消”,回調接收布爾值,使用該bool來判斷用户是確定還是取消接着下一步操作
dialog.ShowConfirm(title, message, func(confirmed bool), parentWindow)
- 自定義彈框
這個其實就是一套娃,點擊某個功能後,你希望彈出什麼內容都可以,嵌入該對話框中,比如你點擊後彈出的框是另外一個功能更多的程序都可以,但是這樣你的這個彈框就有點大了,這個還是看具體情況具體分析。
NewCustom與NewCustomConfirm的區別:NewCustom:這個只有一個關閉按鈕,NewCustomConfirm:會讓你去“確認/取消”,然後拿到用户的確認或取消的結果進行一下步操作
dialog.NewCustom(title, dismissText, content, parentWindow)
dialog.NewCustomConfirm(title, confirmText, dismissText, content, func(confirmed bool), parentWindow)
- 打開文件/文件夾窗口
- 能夠對打開文件夾後做的一些限制:
SetFilter(storage.NewExtensionFileFilter([]string{".txt"}))限制可選可看到的文件後綴類型SetLocation(storage.NewFileURI(path))設置初始位置
- 回調參數為 nil 表示用户取消
- 能夠對打開文件夾後做的一些限制:
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"}))
//設置初始位置
SetLocation(storage.NewFileURI(path))
- 進度彈框
舉例:點擊開始掃描,然後彈進度條框/旋轉等待任務完成,這種就很常見- 需要配合
fyne.Do去執行
- 需要配合
//通過SetValue設置進度
dialog.NewProgress(title, message, parentWindow)
//顯示的是旋轉,這裏就不用setvalue了,沒有進度大小
dialog.NewProgressInfinite(title, message, parentWindow)
在 Fyne v2.6.0 及以上版本中,如果你在 非主線程(例如在 goroutine 中)更新UI組件,必須使用 fyne.Do 或 fyne.DoAndWait 來包裝這些操作
fyne.Do 與 fyne.DoAndWait:fyne.Do 會異步地將函數調度到主線程執行,不會阻塞你當前的 goroutine,適用於像更新進度條這樣的場景。fyne.DoAndWait 則會同步等待函數在主線程執行完畢
案例demo所有代碼
如下圖所示:很多測試單元函數被我註釋了,想要運行哪個就自己解開,不能運行多個,只能一個一個的函數去運行,因為我沒有單獨把窗口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" // 導⼊擴展包
"github.com/flopp/go-findfont"
)
func fyne_test() {
myApp := app.New()
myWin := myApp.NewWindow("窗口標題")
myWin.SetContent(widget.NewLabel("label內容"))
myWin.Resize(fyne.NewSize(200, 200)) // 設置窗口大小
myWin.CenterOnScreen() // 窗口居中顯示
// ---------------------------------
// 全屏
// myWin.SetFullScreen(true)
// 判斷是否全屏
// isFullScrenn := myWin.FullScreen()
// fmt.Println(isFullScrenn)
// ---------------------------------
// ---------------------------------
// 主窗口設置
myWin.SetMaster()
mainMenu := fyne.NewMainMenu(
fyne.NewMenu(
"文件",
fyne.NewMenuItem("新建", func() { fmt.Println("點擊了新建") }),
fyne.NewMenuItem("打開", func() { fmt.Println("點擊了打開") }),
fyne.NewMenuItem("推出", func() { myApp.Quit() }),
),
)
myWin.SetMainMenu(mainMenu)
// ---------------------------------
// 攔截關閉事件
myWin.SetCloseIntercept(func() {
dialog.ShowConfirm("確認", "確定要退出嗎?", func(ok bool) {
if ok {
myApp.Quit()
}
}, myWin)
})
myWin.ShowAndRun() //運行
}
// 更新時間
func time_clock() {
updateTime := func(clock *widget.Label) {
formatted := time.Now().Format("Time: 03:04:05") //獲取格式化時間
clock.SetText(formatted)
}
app := app.New() // 創建應用程序實例
window := app.NewWindow("Hello world") // 創建窗口,標題為"Hello Wolrd"
clock := widget.NewLabel("")
updateTime(clock)
go func() {
for range time.Tick(time.Second) {
updateTime(clock) // 每秒更新一次時間
}
}()
window.SetContent(clock) // 往窗口中放入一個內容為"Hello world!"的標籤控件
window.ShowAndRun() //展示並運行程序
}
// 基礎控件
func basicWidgets() fyne.CanvasObject {
// 1. Label - ⽂本標籤
label := widget.NewLabel("這是⼀個標籤")
// 2. Button - 按鈕
button := widget.NewButton("點擊我", func() {
fmt.Println("按鈕被點擊")
})
// 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 - 複選框
check := widget.NewCheck("同意條款", func(checked bool) {
fmt.Println("複選框狀態:", checked)
})
// 7. RadioGroup - 單選按鈕組
radio := widget.NewRadioGroup([]string{"選項1", "選項2", "選項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 - 進度條
progress := widget.NewProgressBar()
progress.SetValue(0.5) // 50%
// 11. ProgressBarInfinite - ⽆限進度條
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("基礎控件", "", container.NewVBox(
label,
button,
entry,
passwordEntry,
multiEntry,
check,
radio,
selectWidget,
slider,
progress,
infiniteProgress,
hyperlink,
separator,
)),
)
}
func basicwidget_test() {
myapp := app.New()
w := myapp.NewWindow("基礎控件⽰例")
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. 日期選擇器 - 自定義實現
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{"選項1", "選項2", "選項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("⾼級設置",
container.NewVBox(
widget.NewLabel("這是⾼級設置⾯板"),
widget.NewCheck("啓⽤⾼級功能", nil),
)),
widget.NewAccordionItem("其他選項",
container.NewVBox(
widget.NewLabel("這是其他選項⾯板"),
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()
}
// 假設你使用的是舊版本,不是v2以上的,那若你有中文的話,就需要進行中文字體設置
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)
}
// 畫布練習
func fyne_canvas() {
myApp := app.New()
myWin := myApp.NewWindow("畫布測試")
myWin.Resize(fyne.NewSize(400, 300))
myWin.ShowAndRun()
}
// 工具欄練習
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()
}
// 相對佈局練習
func fyne_Layouts() {
myApp := app.New()
myWin := myApp.NewWindow("Layouts")
// 設置窗⼝⼤⼩
myWin.Resize(fyne.NewSize(600, 700))
// 1. BoxLayout - 垂直佈局
vboxLayout := container.NewVBox(
widget.NewLabel("垂直佈局 - 項⽬1"),
widget.NewLabel("垂直佈局 - 項⽬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("左側"), // left
widget.NewLabel("右側"), // right
widget.NewLabel("中⼼內容"), // center
)
// 4. GridLayout - 固定列數⽹格
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 - 指定列數的⽹格
gridWithColumns := container.NewGridWithColumns(2,
widget.NewLabel("單元格 1"),
widget.NewLabel("單元格 2"),
widget.NewLabel("單元格 3"),
widget.NewLabel("單元格 4"),
)
// 6. GridWithRows - 指定⾏數的⽹格
gridWithRows := container.NewGridWithRows(3,
widget.NewLabel("⾏ 1"),
widget.NewLabel("⾏ 2"),
widget.NewLabel("⾏ 3"),
)
// 7. CenterLayout - 居中佈局
centerLayout := container.NewCenter(
widget.NewLabel("居中的內容"),
)
// 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("堆疊內容")),
)
// 10. PaddedLayout - 帶內邊距的佈局
paddedLayout := container.NewPadded(
widget.NewButton("有內邊距的按鈕", 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 內邊距佈局", "", paddedLayout),
))
myWin.SetContent(w)
myWin.ShowAndRun()
}
// 絕對佈局練習
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("標籤"),
)
// ⼿動設置位置和⼤⼩
// 容器裏面的組件就按照你當初放的那樣,進行一個數組訪問即可
// 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("準備開始...")
button := widget.NewButton("開始任務", func() {
// 創建進度對話框,初始進度0%
progDialog := dialog.NewProgress("處理中", "正在進行一項耗時的確定性任務...", myWindow)
progDialog.Show()
totalSteps := 100
// 使用 fyne.Do 確保初始設置在主線程執行
fyne.Do(func() {
progDialog.SetValue(0) // 確保從0開始
})
// 模擬任務進度更新
go func() {
for i := 0; i <= totalSteps; i++ {
currentI := i // 創建局部變量避免閉包問題
currentProgress := float64(currentI) / float64(totalSteps)
if currentI == totalSteps {
time.Sleep(500 * time.Millisecond) // 最後稍作停頓
// 使用 fyne.Do 包裝UI更新
fyne.Do(func() {
progDialog.SetValue(1.0) // 完成時設置為1.0 (100%)
progressInfo.SetText("任務完成!")
})
time.Sleep(500 * time.Millisecond)
fyne.Do(func() {
progDialog.Hide() // 完成任務後隱藏
})
return
}
// 使用 fyne.Do 包裝所有UI更新操作
fyne.Do(func() {
progDialog.SetValue(currentProgress) // 更新進度 (0.0 到 1.0)
progressInfo.SetText(fmt.Sprintf("進度: %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("開始不確定任務", func() {
// 創建無限進度對話框
infiniteDialog := dialog.NewProgressInfinite("請等待", "正在進行一項不確定時間的任務...", myWindow)
infiniteDialog.Show()
fyne.Do(func() {
statusLabel.SetText("任務進行中...")
})
// 模擬一個耗時不確定的任務
go func() {
time.Sleep(5 * time.Second) // 模擬工作,比如網絡請求
// 使用 fyne.Do 包裝UI更新
fyne.Do(func() {
infiniteDialog.Hide() // 任務完成後隱藏對話框
statusLabel.SetText("不確定任務已完成!")
})
}()
})
content := container.NewVBox(statusLabel, startButton)
myWindow.SetContent(content)
myWindow.Resize(fyne.NewSize(400, 200))
myWindow.ShowAndRun()
}
func test_Progess() {
//這裏是手動切換調用哪個的函數方法, 我在main中調用他這個函數即可
// fyne_dialog_for_NewProgess()
fyne_dialog_for_NewProgessInfinite()
}
func main() {
// packet_myfont() //設置中文字體,不亂碼
// fyne_test()
// basicwidget_test()
// advancedWidgets_test()
// time_clock()
// fyne_canvas() //畫布測試
// fyne_toolbar() // 工具欄
// fyne_Layouts() // 相對佈局測試
// fyne_absolute_layout() // 絕對佈局測試
test_Progess() //進度條測試
}