Go红队开发—图形化界面

好久没做golang开发了,之前的文章一直在做cli的安全工具开发,这里了解一下gui图形化的开发,后续目前还不知道能发什么了,主要是cli和gui这些无非都是将之前学过的集成在一起而已,我个人是感觉这个合集已经差不多完成了,若是还有在看我这个合集的师傅觉得还想看什么的可以给一些意见。

GUi 图形化

这里只使用 fyne 库,其他库不讨论。

配置

拿这个来配置的好处就是不用安装,直接下载解压,然后配置环境变量即可

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() //进度条测试
}
posted @ 2025-11-05 17:04  竹等寒  阅读(29)  评论(0)    收藏  举报