Go GUI项目实战:基于fyne开发简易MarkDown

fyne是一个用go编写的GUI框架,它上手简单,语法清晰,界面美观。 教程地址:

https://pkg.go.dev/fyne.io/fyne/v2#section-readmehttps://www.topgoer.cn/docs/goday/goday-1crdp17nj4v6p

项目地址:https://gitee.com/Zifasdfa/ziyiMarkDown

1 基础框架搭建

首先需要有go的环境【我的本地环境是1.17】

go环境搭建过程:https://editor.csdn.net/md/?articleId=130175889

package main

import (

"fyne.io/fyne/v2"

"fyne.io/fyne/v2/app"

"fyne.io/fyne/v2/container"

_ "fyne.io/fyne/v2/dialog"

_ "fyne.io/fyne/v2/storage"

"fyne.io/fyne/v2/widget"

"github.com/flopp/go-findfont"

_ "io/ioutil"

"os"

"strings"

)

/*

用fyne实现简易版typora

*/

func init() {

//设置中文字体:解决中文乱码问题

fontPaths := findfont.List()

for _, path := range fontPaths {

if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {

os.Setenv("FYNE_FONT", path)

break

}

}

}

type config struct {

EditWidget *widget.Entry //编辑框

PreviewWidget *widget.RichText //预览框【富文本】

CurrentFile fyne.URI

SaveMenuItem *fyne.MenuItem

}

var cfg config

func main() {

a := app.New()

win := a.NewWindow("Markdown")

edit, preview := cfg.makeUI()

cfg.createMenuItems(win)

//布局选择左右布局,一边是编辑,一边是预览

win.SetContent(container.NewHSplit(edit, preview))

win.Resize(fyne.Size{Width: 800, Height: 500})

win.CenterOnScreen()

win.ShowAndRun()

}

//初始化界面

func (app *config) makeUI() (*widget.Entry, *widget.RichText) {

edit := widget.NewMultiLineEntry()

preview := widget.NewRichTextFromMarkdown("")

app.EditWidget = edit

app.PreviewWidget = preview

edit.OnChanged = preview.ParseMarkdown

return edit, preview

}

添加过滤器,只能打开.md结尾文件

//var filter = storage.NewExtensionFileFilter([]string{".md", ".MD"})

//

//创建主菜单

func (app *config) createMenuItems(win fyne.Window) {

openMenuItem := fyne.NewMenuItem("open...", func() {})

saveMenuItem := fyne.NewMenuItem("Save...", func() {})

app.SaveMenuItem = saveMenuItem

app.SaveMenuItem.Disabled = true

saveAsMenuItem := fyne.NewMenuItem("Save as...", func() {})

fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem, saveAsMenuItem)

menu := fyne.NewMainMenu(fileMenu)

win.SetMainMenu(menu)

}

2 实现另存为功能

添加过滤器实现另存为

package main

import (

"fyne.io/fyne/v2"

"fyne.io/fyne/v2/app"

"fyne.io/fyne/v2/container"

"fyne.io/fyne/v2/dialog"

"fyne.io/fyne/v2/storage"

"fyne.io/fyne/v2/widget"

"github.com/flopp/go-findfont"

_ "io/ioutil"

"os"

"strings"

)

/*

用fyne实现简易版typora

*/

func init() {

//设置中文字体:解决中文乱码问题

fontPaths := findfont.List()

for _, path := range fontPaths {

if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {

os.Setenv("FYNE_FONT", path)

break

}

}

}

type config struct {

EditWidget *widget.Entry //编辑框

PreviewWidget *widget.RichText //预览框【富文本】

CurrentFile fyne.URI

SaveMenuItem *fyne.MenuItem

}

var cfg config

func main() {

a := app.New()

win := a.NewWindow("Markdown")

edit, preview := cfg.makeUI()

cfg.createMenuItems(win)

//布局选择左右布局,一边是编辑,一边是预览

win.SetContent(container.NewHSplit(edit, preview))

win.Resize(fyne.Size{Width: 800, Height: 500})

win.CenterOnScreen()

win.ShowAndRun()

}

//初始化界面

func (app *config) makeUI() (*widget.Entry, *widget.RichText) {

edit := widget.NewMultiLineEntry()

preview := widget.NewRichTextFromMarkdown("")

app.EditWidget = edit

app.PreviewWidget = preview

edit.OnChanged = preview.ParseMarkdown

return edit, preview

}

//添加过滤器,只能打开.md结尾文件

var filter = storage.NewExtensionFileFilter([]string{".md", ".MD"})

//创建主菜单

func (app *config) createMenuItems(win fyne.Window) {

openMenuItem := fyne.NewMenuItem("open...", func() {})

//openMenuItem := fyne.NewMenuItem("open...", app.openFunc(win))

saveMenuItem := fyne.NewMenuItem("Save...", func() {})

//saveMenuItem := fyne.NewMenuItem("Save...", app.saveFunc(win))

app.SaveMenuItem = saveMenuItem

app.SaveMenuItem.Disabled = true

//另存为

saveAsMenuItem := fyne.NewMenuItem("Save as...", app.saveAsFunc(win))

fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem, saveAsMenuItem)

menu := fyne.NewMainMenu(fileMenu)

win.SetMainMenu(menu)

}

//【另存为】功能实现

func (app *config) saveAsFunc(win fyne.Window) func() {

return func() {

saveDialog := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if write == nil {

//user canceled

return

}

if !strings.HasPrefix(strings.ToLower(write.URI().String()), ".md") {

dialog.ShowInformation("Error", "Please name your file with a .md extension!", win)

return

}

//save file

write.Write([]byte(app.EditWidget.Text))

app.CurrentFile = write.URI()

defer write.Close()

win.SetTitle(win.Title() + "-" + write.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

saveDialog.SetFileName("untitled.md")

saveDialog.SetFilter(filter)

saveDialog.Show()

}

}

3 实现打开文件功能

package main

import (

"fyne.io/fyne/v2"

"fyne.io/fyne/v2/app"

"fyne.io/fyne/v2/container"

"fyne.io/fyne/v2/dialog"

"fyne.io/fyne/v2/storage"

"fyne.io/fyne/v2/widget"

"github.com/flopp/go-findfont"

"io/ioutil"

"os"

"strings"

)

/*

用fyne实现简易版typora

*/

func init() {

//设置中文字体:解决中文乱码问题

fontPaths := findfont.List()

for _, path := range fontPaths {

if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {

os.Setenv("FYNE_FONT", path)

break

}

}

}

type config struct {

EditWidget *widget.Entry //编辑框

PreviewWidget *widget.RichText //预览框【富文本】

CurrentFile fyne.URI

SaveMenuItem *fyne.MenuItem

}

var cfg config

func main() {

a := app.New()

win := a.NewWindow("Markdown")

edit, preview := cfg.makeUI()

cfg.createMenuItems(win)

//布局选择左右布局,一边是编辑,一边是预览

win.SetContent(container.NewHSplit(edit, preview))

win.Resize(fyne.Size{Width: 800, Height: 500})

win.CenterOnScreen()

win.ShowAndRun()

}

//初始化界面

func (app *config) makeUI() (*widget.Entry, *widget.RichText) {

edit := widget.NewMultiLineEntry()

preview := widget.NewRichTextFromMarkdown("")

app.EditWidget = edit

app.PreviewWidget = preview

edit.OnChanged = preview.ParseMarkdown

return edit, preview

}

//添加过滤器,只能打开.md结尾文件

var filter = storage.NewExtensionFileFilter([]string{".md", ".MD"})

//创建主菜单

func (app *config) createMenuItems(win fyne.Window) {

//打开文件

openMenuItem := fyne.NewMenuItem("open...", app.openFunc(win))

saveMenuItem := fyne.NewMenuItem("Save...", func() {})

//saveMenuItem := fyne.NewMenuItem("Save...", app.saveFunc(win))

app.SaveMenuItem = saveMenuItem

app.SaveMenuItem.Disabled = true

//另存为

saveAsMenuItem := fyne.NewMenuItem("Save as...", app.saveAsFunc(win))

fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem, saveAsMenuItem)

menu := fyne.NewMainMenu(fileMenu)

win.SetMainMenu(menu)

}

//【打开文件】功能

func (app *config) openFunc(win fyne.Window) func() {

return func() {

openDialog := dialog.NewFileOpen(func(read fyne.URIReadCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if read == nil {

return

}

defer read.Close()

data, err := ioutil.ReadAll(read)

if err != nil {

dialog.ShowError(err, win)

return

}

app.EditWidget.SetText(string(data))

app.CurrentFile = read.URI()

win.SetTitle(win.Title() + "-" + read.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

//添加.md过滤器

openDialog.SetFilter(filter)

openDialog.Show()

}

}

//【另存为】功能实现

func (app *config) saveAsFunc(win fyne.Window) func() {

return func() {

saveDialog := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if write == nil {

//user canceled

return

}

if !strings.HasPrefix(strings.ToLower(write.URI().String()), ".md") {

dialog.ShowInformation("Error", "Please name your file with a .md extension!", win)

return

}

//save file

write.Write([]byte(app.EditWidget.Text))

app.CurrentFile = write.URI()

defer write.Close()

win.SetTitle(win.Title() + "-" + write.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

saveDialog.SetFileName("untitled.md")

saveDialog.SetFilter(filter)

saveDialog.Show()

}

}

4 实现保存功能

package main

import (

"fyne.io/fyne/v2"

"fyne.io/fyne/v2/app"

"fyne.io/fyne/v2/container"

"fyne.io/fyne/v2/dialog"

"fyne.io/fyne/v2/storage"

"fyne.io/fyne/v2/widget"

"github.com/flopp/go-findfont"

"io/ioutil"

"os"

"strings"

)

/*

用fyne实现简易版typora

*/

func init() {

//设置中文字体:解决中文乱码问题

fontPaths := findfont.List()

for _, path := range fontPaths {

if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {

os.Setenv("FYNE_FONT", path)

break

}

}

}

type config struct {

EditWidget *widget.Entry //编辑框

PreviewWidget *widget.RichText //预览框【富文本】

CurrentFile fyne.URI

SaveMenuItem *fyne.MenuItem

}

var cfg config

func main() {

a := app.New()

win := a.NewWindow("Markdown")

edit, preview := cfg.makeUI()

cfg.createMenuItems(win)

//布局选择左右布局,一边是编辑,一边是预览

win.SetContent(container.NewHSplit(edit, preview))

win.Resize(fyne.Size{Width: 800, Height: 500})

win.CenterOnScreen()

win.ShowAndRun()

}

//初始化界面

func (app *config) makeUI() (*widget.Entry, *widget.RichText) {

edit := widget.NewMultiLineEntry()

preview := widget.NewRichTextFromMarkdown("")

app.EditWidget = edit

app.PreviewWidget = preview

edit.OnChanged = preview.ParseMarkdown

return edit, preview

}

//添加过滤器,只能打开.md结尾文件

var filter = storage.NewExtensionFileFilter([]string{".md", ".MD"})

//创建主菜单

func (app *config) createMenuItems(win fyne.Window) {

//打开文件

openMenuItem := fyne.NewMenuItem("open...", app.openFunc(win))

//保存文件

saveMenuItem := fyne.NewMenuItem("Save...", app.saveFunc(win))

app.SaveMenuItem = saveMenuItem

app.SaveMenuItem.Disabled = true

//另存为

saveAsMenuItem := fyne.NewMenuItem("Save as...", app.saveAsFunc(win))

fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem, saveAsMenuItem)

menu := fyne.NewMainMenu(fileMenu)

win.SetMainMenu(menu)

}

//【保存文件】功能

func (app *config) saveFunc(win fyne.Window) func() {

return func() {

if app.CurrentFile != nil {

write, err := storage.Writer(app.CurrentFile)

if err != nil {

dialog.ShowError(err, win)

return

}

write.Write([]byte(app.EditWidget.Text))

defer write.Close()

}

}

}

//【打开文件】功能

func (app *config) openFunc(win fyne.Window) func() {

return func() {

openDialog := dialog.NewFileOpen(func(read fyne.URIReadCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if read == nil {

return

}

defer read.Close()

data, err := ioutil.ReadAll(read)

if err != nil {

dialog.ShowError(err, win)

return

}

app.EditWidget.SetText(string(data))

app.CurrentFile = read.URI()

win.SetTitle(win.Title() + "-" + read.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

//添加.md过滤器

openDialog.SetFilter(filter)

openDialog.Show()

}

}

//【另存为】功能实现

func (app *config) saveAsFunc(win fyne.Window) func() {

return func() {

saveDialog := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if write == nil {

//user canceled

return

}

if !strings.HasPrefix(strings.ToLower(write.URI().String()), ".md") {

dialog.ShowInformation("Error", "Please name your file with a .md extension!", win)

return

}

//save file

write.Write([]byte(app.EditWidget.Text))

app.CurrentFile = write.URI()

defer write.Close()

win.SetTitle(win.Title() + "-" + write.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

saveDialog.SetFileName("untitled.md")

saveDialog.SetFilter(filter)

saveDialog.Show()

}

}

//安装打包程序

//go install fyne.io/fyne/v2/cmd/fyne@latest

//fyne package -appVersion 1.0.0 --name MarkDown -appID=.41234 -releas

5 全部代码

package main

import (

"fyne.io/fyne/v2"

"fyne.io/fyne/v2/app"

"fyne.io/fyne/v2/container"

"fyne.io/fyne/v2/dialog"

"fyne.io/fyne/v2/storage"

"fyne.io/fyne/v2/widget"

"github.com/flopp/go-findfont"

"io/ioutil"

"os"

"strings"

)

/*

用fyne实现简易版typora

*/

func init() {

//设置中文字体:解决中文乱码问题

fontPaths := findfont.List()

for _, path := range fontPaths {

if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {

os.Setenv("FYNE_FONT", path)

break

}

}

}

type config struct {

EditWidget *widget.Entry //编辑框

PreviewWidget *widget.RichText //预览框【富文本】

CurrentFile fyne.URI

SaveMenuItem *fyne.MenuItem

}

var cfg config

func main() {

a := app.New()

win := a.NewWindow("Markdown")

edit, preview := cfg.makeUI()

cfg.createMenuItems(win)

//布局选择左右布局,一边是编辑,一边是预览

win.SetContent(container.NewHSplit(edit, preview))

win.Resize(fyne.Size{Width: 800, Height: 500})

win.CenterOnScreen()

win.ShowAndRun()

}

//初始化界面

func (app *config) makeUI() (*widget.Entry, *widget.RichText) {

edit := widget.NewMultiLineEntry()

preview := widget.NewRichTextFromMarkdown("")

app.EditWidget = edit

app.PreviewWidget = preview

edit.OnChanged = preview.ParseMarkdown

return edit, preview

}

//添加过滤器,只能打开.md结尾文件

var filter = storage.NewExtensionFileFilter([]string{".md", ".MD"})

//创建主菜单

func (app *config) createMenuItems(win fyne.Window) {

openMenuItem := fyne.NewMenuItem("open...", app.openFunc(win))

saveMenuItem := fyne.NewMenuItem("Save...", app.saveFunc(win))

app.SaveMenuItem = saveMenuItem

app.SaveMenuItem.Disabled = true

saveAsMenuItem := fyne.NewMenuItem("Save as...", app.saveAsFunc(win))

fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem, saveAsMenuItem)

menu := fyne.NewMainMenu(fileMenu)

win.SetMainMenu(menu)

}

//【保存文件】功能

func (app *config) saveFunc(win fyne.Window) func() {

return func() {

if app.CurrentFile != nil {

write, err := storage.Writer(app.CurrentFile)

if err != nil {

dialog.ShowError(err, win)

return

}

write.Write([]byte(app.EditWidget.Text))

defer write.Close()

}

}

}

//【打开文件】功能

func (app *config) openFunc(win fyne.Window) func() {

return func() {

openDialog := dialog.NewFileOpen(func(read fyne.URIReadCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if read == nil {

return

}

defer read.Close()

data, err := ioutil.ReadAll(read)

if err != nil {

dialog.ShowError(err, win)

return

}

app.EditWidget.SetText(string(data))

app.CurrentFile = read.URI()

win.SetTitle(win.Title() + "-" + read.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

//添加.md过滤器

openDialog.SetFilter(filter)

openDialog.Show()

}

}

//【另存为】功能实现

func (app *config) saveAsFunc(win fyne.Window) func() {

return func() {

saveDialog := dialog.NewFileSave(func(write fyne.URIWriteCloser, err error) {

if err != nil {

dialog.ShowError(err, win)

return

}

if write == nil {

//user canceled

return

}

if !strings.HasPrefix(strings.ToLower(write.URI().String()), ".md") {

dialog.ShowInformation("Error", "Please name your file with a .md extension!", win)

return

}

//save file

write.Write([]byte(app.EditWidget.Text))

app.CurrentFile = write.URI()

defer write.Close()

win.SetTitle(win.Title() + "-" + write.URI().Name())

app.SaveMenuItem.Disabled = false

}, win)

saveDialog.SetFileName("untitled.md")

saveDialog.SetFilter(filter)

saveDialog.Show()

}

}

//安装打包程序

//go install fyne.io/fyne/v2/cmd/fyne@latest

//fyne package -appVersion 1.0.0 --name MarkDown -appID=.41234 -releas

6 打包

//安装打包程序

go install fyne.io/fyne/v2/cmd/fyne@latest

//执行打包命令

fyne package -appVersion 1.0.0 --name MarkDown -appID=.41234 -release

//运行app

.\MarkDown

7 问题解决(fyne中文乱码问题)

//安装依赖库

go get "github.com/flopp/go-findfont"

在main.go中添加初始化代码:

func init() {

//设置中文字体:解决中文乱码问题

fontPaths := findfont.List()

for _, path := range fontPaths {

if strings.Contains(path, "msyh.ttf") || strings.Contains(path, "simhei.ttf") || strings.Contains(path, "simsun.ttc") || strings.Contains(path, "simkai.ttf") {

os.Setenv("FYNE_FONT", path)

break

}

}

}

8 发布

对于mac系统的朋友来说,如果要想让打包之后的软件可以发给别人运行而不报错,则需要注册一个苹果开发者账号,并且使用付费版(一年大概100$),然后下载一个XCode,通过配置让XCode给我们打包好的软件签名。

其他版本的os类似