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

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

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

  • https://pkg.go.dev/fyne.io/fyne/v2#section-readme
  • https://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类似

你可能感兴趣的:(go,demo,golang,fyne,GUI开发,app,项目实战)