Go红队开发—图形化界面
好久没做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() //进度条测试
}
本文来自博客园,作者:竹等寒,转载请注明原文链接。

浙公网安备 33010602011771号