一个 Go 实现的跨平台 GUI 框架 Fyne

news2024/12/24 3:15:20

今天,推荐一个 Go 实现的 GUI 库 - fyne。

Go 官方也没有提供标准的 GUI 框架,在 Go 实现的几个 GUI 库中,Fyne 算是最出色的,它有着简洁的API、支持跨平台能力,且高度可扩展。这也就是说,Fyne 是可以开发 App。

本文将尝试介绍下 Fyne,希望对大家快速上手这个 GUI 框架有所帮助。我最近产生了不少想法,其中有些是对 GUI 有要求的,就想着折腾用 Go 实现,而不是用那些已经很流行和成熟的 GUI 框架。

在写这篇文章时,顺手搞了下它的中文版文档,文档查看 www.poloxue.com/gofyne,希望能帮助那些想继续深入这个框架的朋友。

安装 fyne

开始前,确保已成功安装 Go,如果是 MacOS X 系统,要确认安装了 xcode。

如下使用 go get 命令安装 Fyne。

$ mkdir hellofyne
$ cd helloyfyne
$ go mod init hellofyne
$ go get fyne.io/fyne/v2@latest
$ go install fyne.io/fyne/v2/cmd/fyne@latest

如果想立刻查看 fyne 提供的演示案例,通过命令检查:

$ go run fyne.io/fyne/v2/cmd/fyne_demo@latest

看起来,这里面的案例还是不够丰富的。

安装工作到此就完成了。Fyne 对不同系统有不同依赖,如果安装过程中遇到问题,细节可查看官方提供的安装文档。

创建第一个应用

由于 Go 简洁的语法和 fyne 的设计,使用 fyne 创建一个 GUI 应用异常简单。

以下是一个创建基础窗口的例子:

package main

import (
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/widget"
)

func main() {
    a := app.New()
    w := a.NewWindow("Hello Fyne")
    w.SetContent(widget.NewLabel("Welcome to Fyne!"))
    w.ShowAndRun()
}

这段代码创建了一个包含标签的窗口。

通过 app.New() 初始化一个 Fyne 应用实例。然后,创建一个标题为 “Hello Fyne” 的窗口,并设置内容为包含 “Welcome to Fyne!” 文本标签。最后,通过w.ShowAndRun()显示窗口并启动应用的事件循环。

fyne 中窗口的默认大小是其包含的内容决定,也可预置大小。

如在创建窗口后,提供 Resize 重新调整大小。

w.Resize(fyne.NewSize(100, 100))

还有,一个 app 下可以有多个窗口的,示例代码:

btn := widget.NewButton("Create a widnow", func() {
	w2 := a.NewWindow("Window 02")
	w2.Resize(fyne.NewSize(200, 200))
	w2.Show()
})

w.SetContent(btn)

我们创建一个按钮,它的点击事件是创建一个新的窗口并显示。

布局和控件

布局和控件是 GUI 应用程序设计中必不可少的两类组件。Fyne 提供了多种布局管理器和标准的 UI 控件,支持创建更复杂的界面。

布局管理

Fyne 中的布局的实现位于 container 包中。它提供了多种不同布局方式安排窗口中的元素。最基本的布局方式有 HBox 水平布局和 VBox 垂直布局。

通过 HBox 创建水平布局的代码如下:

w.SetContent(container.NewHBox(widget.NewButton("Left", func() {
	fmt.Println("Left button clicked")
}), widget.NewButton("Right", func() {
	fmt.Println("Right button clicked")
})))

显示效果:

通过 VBox 创建垂直布局的例子。

w.SetContent(container.NewVBox(widget.NewButton("Top", func() {
	fmt.Println("Top button clicked")
}), widget.NewButton("Bottom", func() {
	fmt.Println("Bottom button clicked")
})))

显示效果:

Fyne 除了基础的水平(HBoxLayout)和垂直(VBoxLayout)布局,还提供了GridLayoutFormLayout 甚至是混合布局 CombinedLayout 等高级布局方式。

GridLayout可以将元素均匀地分布在网格中,而FormLayout适用于创建表单,自动排列标签和字段。这些灵活的布局选项支持创建更复杂和功能丰富的GUI界面。

官方文档中的布局列表查看:Layout List。

更多控件

前面的演示案例中,用到了两个控件:Label 和 Button,Fyne 还支持其他多种控件,它们都为于 widget 包中。

我尝试在一份代码中展示出来,如下是常见控件一览:

// 标签 Label
label := widget.NewLabel("Label")
// 按钮 Button
button := widget.NewButton("Button", func() {})
// 输入框 Entry
entry := widget.NewEntry()
entry.SetPlaceHolder("Entry")
// 复选框 Check
check := widget.NewCheck("Check", func(bool) {})
// 单选框 Check
radio := widget.NewRadioGroup([]string{"Option 1", "Option 2"}, func(string) {})
// 选择框
selectEntry := widget.NewSelectEntry([]string{"Option A", "Option B"}
// 进度条
progressBar := widget.NewProgressBar()
// 滑块
slider := widget.NewSlider(0, 100)
// 组合框
combo := widget.NewSelect([]string{"Option A", "Option B", "Option C"}, func(string) {})
// 表单项
formItem := widget.NewFormItem("FormItem", widget.NewEntry())
form := widget.NewForm(formItem)
// 手风琴
accordion := widget.NewAccordion(widget.NewAccordionItem("Accordion", widget.NewLabel("Content")))
// Tab 选择
tabs := container.NewAppTabs(
	container.NewTabItem("Tab 1", widget.NewLabel("Content 1")),
	container.NewTabItem("Tab 2", widget.NewLabel("Content 2")),
)

// 弹出对话框示例按钮
dialogButton := widget.NewButton("Show Dialog", func() {
	dialog.ShowInformation("Dialog", "Dialog Content", w)
})

// 滚动布局
content := container.NewVScroll(container.NewVBox(
	label, button, entry, check, radio, selectEntry, progressBar, slider,
	combo, form, accordion, tabs, dialogButton,
))
w.SetContent(content)

演示效果:

Fyne 中的自定义

如果在实际项目中使用 Fyne,基本上是要使用 Fyne 的自定义能力。Fyne 提供了自定义控件、布局和主题等。

自定义控件

fyne 是支持实现自定义控件的,这涉及定义控件的绘制方法和布局逻辑。我们主要是实现两个接口:fyne.Widgetfyne.WidgetRenderer

fyne.Widget 的定义如下所示:

type Widget interface {
	CanvasObject
	CreateRenderer() WidgetRenderer
}

CreateRenderer 方法返回的就是 WiddgetRenderer,用于定义控件渲染和布局的逻辑。

type WidgetRenderer interface {
	Destroy()
	Layout(Size)
	MinSize() Size
	Objects() []CanvasObject
	Refresh()
}

这样拆分的目标是为将了控件的逻辑和 UI 绘制分离开来,在 Widget 中专注于逻辑,而 WidgetRenderer 中专注于渲染布局。

假设实现一个类似 Label 的控件,类型定义:

type CustomLabel struct {
    widget.BaseWidget
    Text string
}

它继承了 wiget.BaseWidget 基本控件实现,Text 就是要 Label 显示的文本。还要给给 CustomLabel 实现 CreateRenderer 方法。

定义 CustomLabel 创建函数:

func NewCustomLabel(text string) *CustomLabel {
	label := &CustomLabel{Text: text}
	label.ExtendBaseWidget(label)
	return label
}

customWidgetRenderer 类型定义如下:

type customWidgetRenderer struct {
    text  *canvas.Text // 使用canvas.Text来绘制文本
    label *CustomLabel
}

实现 CustomLabelCreateRenderer 方法。

func (label *CustomLabel) CreateRenderer() fyne.WidgetRenderer {
	text := canvas.NewText(label.Text, theme.ForegroundColor())
	text.Alignment = fyne.TextAlignCenter

	return &customLabelRenderer{
		text:  text,
		label: label,
	}
}

构建 Renderer 变量,使用 canvas 创建 Text 文本框,为适配主题使用主题配置前景色。还有,设置文本居中显示。

customLabelRenderer 要实现 WidgetRender 接口定义的所有方法。

func (r *customLabelRenderer) MinSize() fyne.Size {
	return r.text.MinSize()
}

func (r *customLabelRenderer) Layout(size fyne.Size) {
	r.text.Resize(size)
}

func (r *customLabelRenderer) Refresh() {
	r.text.Text = r.label.Text
	r.text.Color = theme.ForegroundColor() // 确保文本颜色更新
	r.text.Refresh()
}

func (r *customLabelRenderer) BackgroundColor() color.Color {
	return theme.BackgroundColor()
}

func (r *customLabelRenderer) Objects() []fyne.CanvasObject {
	return []fyne.CanvasObject{r.text}
}

func (r *customLabelRenderer) Destroy() {}

在 main 函数中,尝试使用这个控件。

a := app.New()
w := a.NewWindow("Custom Label")

w.SetContent(NewCustomLabel("Hello"))
w.ShowAndRun()

显示的效果和 Label 控件是类似的。

其他自定义

其他自定义能力,如 Layout、Theme,我就不展开介绍。如果有机会,写点实际应用案例。如果基于案例介绍,会更有体悟吧。

还有,Fyne 的官方文档写的挺易读的,可直接看它的文档。

数据绑定

Fyne 从 v2.0.0 开始支持数据绑定。它让控件和与数据实时连接,数据更改会自动反映在UI上,反之亦然。

控件为了支持数据绑定能力,一般会提供如 NewXXXWithData 的接口。直接通过场景说明,场景:编辑输入框的内容,可直接实现到 Label 上。

核心代码如下所示:

// 创建一个字符串绑定
textBind := binding.NewString()

// 创建一个 Entry,将其内容绑定到 textBind
entry := widget.NewEntryWithData(textBind)

// 创建一个 Label,也将其内容绑定到同一个 textBind
label := widget.NewLabelWithData(textBind)

// 使用容器放置 Entry 和 Label,以便它们都显示在窗口中
content := container.NewVBox(entry, label)

显示效果,如下所示:

尝试让前面自定义的 CustomLabel 支持数据绑定能力。只要创建一个 NewCustomLabelWithData 构造函数。

如下所示:

func NewCustomLabelWithData(data binding.String) *CustomLabel {
	label := &CustomLabel{}
	label.ExtendBaseWidget(label)
	data.AddListener(binding.NewDataListener(func() {
		text, _ := data.Get()
		label.Text = text
		label.Refresh()
	}))
	return label
}

通过 data 监听数据变化后,立刻刷新组件。

如上所示,我们这个自定义 Label 中的文本是居中显示的。

数据绑定的核心是监听器模式(Observer pattern)。每个绑定对象内部维护了一个监听器列表,当数据变化时,这些监听器会被通知更新。

在 Fyne 中,通过 data.AddListner() 将 UI 组件与数据绑定对象绑定时,实际上是在数据对象上注册了一个监听器,这个监听器会在数据变化时更新 UI 组件的状态。

结语

Fyne 是简单、强大和跨平台的 GUI 工具,使得用 GO 开发现代 GUI 应用多了一个优秀选择。随着对 Fyne 的深入,它能够更加灵活地构建出符合需求的应用。

我喜欢用 Go 的原因,很重要的原因就是它的简洁性,很容易看到本质的东西,但又无需理解太复杂的编程概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1692832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如果直升机一直在空中悬停,24小时后能否绕行地球一圈?

直升机悬停在空中,似乎给了我们一种静止的错觉。但如果直升机一直保持这种状态,24小时后,它是否能够神奇地绕地球一圈? 地球自转:直升机悬停的无形锁链 问题的答案并非像表面上看起来那样简单。要解答这个问题&#…

Win10玩红警黑屏有声音的解决方法

Win10玩红警黑屏怎么解决?红色警戒,可以说是一款深受青少年朋友喜爱的网游,但是,当我们在使用win10电脑运行红色警戒的时候免不了会出现不兼容、权限不足等问题。相信玩过红警的小伙伴都有遇到过黑屏的问题,那该怎么解…

弱密码系统登录之后强制修改密码

在你登录的时候,获取到弱密码,然后将他存到vuex里面,在登录进去之后,index页面再去取,思路是这样的 一、vuex里面定义密码字段 我是直接在user.js里面写的 import { login, logout, getInfo } from /api/login impo…

SQLmap学习以及题解运用

1.简介 SQLmap是一款开源的SQL注入工具,用于检测和利用Web应用程序的SQL注入漏洞。SQLmap支持多种数据库管理系统,包括MySQL、Oracle、PostgreSQL、Microsoft SQL Server、SQLite等,并支持各种不同的操作系统和平台。 这里主要分为四大部分…

计网(部分在session学习章)

TCP/UDP TCP:面向连接,先三次握手建立连接,可靠传输。 UDP:无连接,不可靠,传递的快。 TCP可靠传输 1.分块编号传输; 2.校验和,校验首部和数据的检验和,检测数据在传输中的变化; 3.丢弃重复数据; 4.流量控制,TCP 利⽤滑动窗⼝实现流量控制。TCP的拥塞控制采⽤…

一年前的Java作业,模拟游戏玩家战斗

说明:一年前写的作业,感觉挺有意思的,将源码分享给大家。 刚开始看题也觉得很难,不过写着写着思路更加清晰,发现也没有想象中的那么难。 一、作业题目描述: 题目:模拟游戏玩家战斗 1.1 基础功…

台灯的功能作用有哪些?护眼台灯真的护眼吗?

现在的学生会长时间使用平板、手机、电脑等,这些设备的屏幕会产生一定的频闪和蓝光辐射,也就会影响视力健康,而护眼养眼也成了家长关注的问题,视力疲劳和眼部疾病不仅影响个体的生活质量,还可能导致长期的健康问题。当…

IPIDEA与您分享:代理IP究竟是如何保护用户隐私的?

在信息化、网络化的今天,互联网已成为人们生活中不可或缺的一部分。无论是日常沟通、学习工作,还是娱乐休闲,网络都扮演着举足轻重的角色。然而,随着网络活动的增加,网络安全问题也日益凸显,为了保护个人隐…

Mybatis源码剖析---第二讲

Mybatis源码剖析—第二讲 那我们在讲完了mappedstatement这个类,它的一个核心作用之后呢?那下面我有一个问题想问问各位。作为mappedstatement来讲,它封装的是一个select标签或者insert标签。但是呢,我们需要大家注意的是什么&am…

LeetCode --- 398周赛

题目列表 3151. 特殊数组 I 3152. 特殊数组 II 3153. 所有数对中数位不同之和 3154. 到达第 K 级台阶的方案数 一、特殊数组I 遍历数组&#xff0c;看相邻元素的奇偶性是否相同即可&#xff0c;代码如下 class Solution { public:bool isArraySpecial(vector<int>&…

LangChain - 为何我们选择 ClickHouse 作为 LangSmith 的动力

本文字数&#xff1a;4742&#xff1b;估计阅读时间&#xff1a;12 分钟 作者&#xff1a;Ankush Gola 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 “我们在使用 ClickHouse 方面有着良好的经历。它使我们能够将 LangSmith 扩展到…

centos7.9用docker运行一个nginx容器

首先你的linux 系统里面已经安装好了docker&#xff0c;docker的安装教程看这个 1&#xff0c;下载nginx镜像 有很多文章会把镜像下载说成是拉取镜像&#xff0c; 我觉得就是下载的意思啊&#xff0c;搞不懂为什么要说拉取&#xff1f; docker pull nginx 下载最新版 Nginx …

PostgreSQL基础(二):PostgreSQL的安装与配置

文章目录 PostgreSQL的安装与配置 一、PostgreSQL的安装 二、PostgreSQL的配置 1、远程连接配置

Springboot+Vue项目-基于Java+MySQL的酒店管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

知识分享:大数据信用花导致的评分不足多久能恢复

随着金融风控领域越来越科技化&#xff0c;基于大数据技术的金融风控成为了贷前风控不可或缺的重要环节&#xff0c;相信很多人在申贷的时候都听说过大数据信用和综合评分等词语&#xff0c;那大数据信用花导致的评分不足多久能恢复呢?本文带大家一起去了解一下。 首先&#x…

公认最好用的随身wifi品牌推荐!欧本VS闪鱼VS格行随身wifi哪款更好用?欧本随身wifi靠谱吗?

最近小编后台私信快被问爆了&#xff0c;都想让测评一下自己相中的随身wifi到底行不行。该说不说随身wifi品牌真是越来越多了&#xff0c;今天就挑选咨询量最多的三款&#xff1a;格行&#xff0c;欧本&#xff0c;闪鱼&#xff01;咱们各方面都实测一下&#xff0c;看看哪款更…

WORD、PPT技巧

WORD技巧 编辑设置 word标题导航窗口怎么调出word2016&#xff0c;缩小了页面&#xff0c;可是怎么是竖着的一页一页排列啊&#xff1f;以前不是好几页横排着的么&#xff1f;怎么设置&#xff0c;求救&#xff1a;在Word标题栏那一行找到“视图”&#xff0c;点击“显示比例…

Android Compose 九:常用组件列表 简单使用

遇事不决 先看官方文档 列表和网格 如果不需要任何滚动&#xff0c;通过Column 或 Row可以使用verticalScroll() 使Column滚动 Column(modifier Modifier.verticalScroll(rememberScrollState())) {for (i in 0..50){Text(text "条目>>${i}")}}显示大量列表…

音视频学习规划

文章目录 概述闲聊点 小结 概述 最近在学习音视频&#xff0c;觉得还是要先写个提纲&#xff0c;给自己制定下学习路线及目标。先写下我的个人流程及思路。 ffmpeg的命令ffmpeg api播放器流媒体RTMP&#xff0c;HLS 闲聊点 先说下学习命令行吧&#xff0c;学习命令行是为了…

FreeRTOS任务间通信“IPC”

---------------信号量--------------- 信号量的定义&#xff1a; 操作系统中一种解决问题的机制&#xff0c;可以实现 “共享资源的访问” 信号&#xff1a;起通知作用量&#xff1a;还可以用来表示资源的数量当"量"没有限制时&#xff0c;它就是"计数型信…