動態

詳情 返回 返回

Go红队开发—图形化界面 - 動態 詳情

目錄
  • GUi 圖形化
    • 配置
    • 第一個GUI
    • 常用 widget 組件
    • Layout 佈局
    • 絕對佈局
    • dialog彈框
      • 注意事項
      • 類別
    • 案例demo所有代碼

好久沒做golang開發了,之前的文章一直在做cli的安全工具開發,這裏瞭解一下gui圖形化的開發,後續目前還不知道能發什麼了,主要是cli和gui這些無非都是將之前學過的集成在一起而已,我個人是感覺這個合集已經差不多完成了,若是還有在看我這個合集的師傅覺得還想看什麼的可以給一些意見。

GUi 圖形化

這裏只使用 fyne 庫,其他庫不討論。

配置

  • 配置GCC:我下載的Gcc版本
拿這個來配置的好處就是不用安裝,直接下載解壓,然後配置環境變量即可

500

  • 運⾏時,需要開啓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
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,我後面換了,我上面代碼是正確的,只是圖片中我忘記切換新版本的庫了

630

第一個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.NewRadioGroup
  • widget.NewSelect
    下拉框
  • widget.NewSlider
    滑塊
  • widget.NewProgressBar
    進度條,通過SetValue來控制進度條的滑動
  • widget.NewProgressBarInfinite
    無限進度條
  • widget.NewSeparator
    分割線
  • widget.NewVBox
    簡單的水平或垂直的容器,Box可以對放入box的控件採用佈局
  • widget.NewCard
    卡片,給標題和這個卡片的內容
    目前可以簡單的通過containernew一個box進行整合控件在某個容器裏
    看下面的代碼案例中最後return即可(在basicWidgets函數中)

穿插下container:container.NewVBox和 【container.NewScrollcontainer.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動作
    直接看下面的代碼:
    500
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)
  • 自定義彈框
    這個其實就是一套娃,點擊某個功能後,你希望彈出什麼內容都可以,嵌入該對話框中,比如你點擊後彈出的框是另外一個功能更多的程序都可以,但是這樣你的這個彈框就有點大了,這個還是看具體情況具體分析。
    NewCustomNewCustomConfirm 的區別:
    • 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.DoAndWaitfyne.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() //進度條測試
}

Add a new 評論

Some HTML is okay.