基于 golang fyne 构建 windows 桌面程序

基于 golang fyne 构建 windows 桌面程序

背景

链接 我用 Golang 和 go-winres 做了一个文件大搬运

经反馈,使用控制台完成某些功能控制对非程序员来说有一定难度,于是再次致敬劳动工作者和各位卷王,基于 fyne 创建了一个 windows 桌面程序。

程序设计

  1. 程序主体使用 Golang 开发

  2. 使用 fyne 进行封装(更多使用方法需自行了解)

源码

main .go

package main

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

var fromVal string
var toVal string
var fileNameVal string
var fuzzyVal bool
var childVal bool
var coverVal bool

var status binding.String

func init() {
	// 默认启动模糊匹配
	fuzzyVal = true
}

func main() {
	a := app.New()
	w := a.NewWindow("BigMove @Locker")

	entryFrom := &widget.Entry{PlaceHolder: "eg: c:/a/b"}
	entryTo := &widget.Entry{PlaceHolder: "eg: c:/a/b/d"}
	entryFileName := &widget.Entry{PlaceHolder: "full name or part"}

	radioFuzzy := widget.NewRadioGroup([]string{"ENABLE", "DISABLE"}, func(v string) {
		if v == "ENABLE" {
			fuzzyVal = true
		} else {
			fuzzyVal = false
		}
	})
	radioFuzzy.SetSelected("ENABLE")
	radioFuzzy.Horizontal = true

	radioChild := widget.NewRadioGroup([]string{"ENABLE", "DISABLE"}, func(v string) {
		if v == "ENABLE" {
			childVal = true
		} else {
			childVal = false
		}
	})
	radioChild.SetSelected("DISABLE")
	radioChild.Horizontal = true

	radioCover := widget.NewRadioGroup([]string{"ENABLE", "DISABLE"}, func(v string) {
		if v == "ENABLE" {
			coverVal = true
		} else {
			coverVal = false
		}
	})
	radioCover.SetSelected("DISABLE")
	radioCover.Horizontal = true

	// 执行状态
	status = binding.NewString()

	form := widget.Form{
		Items: []*widget.FormItem{
			{Text: "From", Widget: entryFrom},
			{Text: "To", Widget: entryTo},
			{Text: "FileName", Widget: entryFileName},
			{Text: "FuzzyMode", Widget: radioFuzzy},
			{Text: "ChildMode", Widget: radioChild},
			{Text: "CoverMode", Widget: radioCover},
		},
		OnSubmit: func() {
			// 操作目录地址校验
			fromVal = entryFrom.Text
			if fromVal == "" {
				log.Printf("[ERROR] form validation: need From directory\n")
				validationAlert("need From directory")
				return
			}
			// 目标目录校验
			toVal = entryTo.Text
			if toVal == "" {
				log.Printf("[ERROR] form validation: need To directory\n")
				validationAlert("need To directory")
				return
			}
			// 文件名校验
			fileNameVal = entryFileName.Text
			if fileNameVal == "" {
				log.Printf("[ERROR] form validation: need FileName or part\n")
				validationAlert("need FileName or part")
				return
			}

			validationAlert("running...")

			// 执行命令
			ok, err := doBigMove()
			if err != nil {
				log.Printf("[ERROR] do big move fail, exp: %s\n", err.Error())
				validationAlert("error: move file fail, cause: " + err.Error())
				return
			}
			if ok {
				log.Printf("ok\n")
				validationAlert("success: move file finish")
			} else {
				log.Printf("fail\n")
				validationAlert("error: move file fail")
			}
		},
	}

	// 创建布局
	content := container.NewVBox(&form, widget.NewLabelWithData(status))

	w.SetContent(content)
	w.Resize(fyne.NewSize(500, 300))
	w.CenterOnScreen()
	w.ShowAndRun()
}

func validationAlert(msg string) {
	status.Set(msg)
}

domove.go

package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync/atomic"
)

// doBigMove 执行大搬运
func doBigMove() (bool, error) {
	checkOk, err := beforeCheck()
	if !checkOk {
		return false, err
	}

	// 读取操作目录信息
	fromDir, err := os.ReadDir(fromVal)
	if err != nil {
		return false, errors.New(fmt.Sprintf("读取操作目录失败 err: %s", err.Error()))
	}

	// 判断是否需要执行子目录任务
	if childVal {
		doWithChild(fromVal, fromDir)
	} else {
		doCurrent(fromVal, fromDir)
	}
	return true, nil
}



// beforeCheck 前置检查
func beforeCheck() (bool, error) {
	// 操作目录检查
	if fromVal == "" {
		return false, errors.New("须指定操作目录 from")
	}
	// 目标目录检查
	if toVal == "" {
		return false, errors.New("须指定操作目录 to")
	}
	if !isFolderExists(toVal) {
		err := os.MkdirAll(toVal, os.ModePerm)
		if err != nil {
			return false, errors.New(fmt.Sprintf("创建目标目录失败 err: %s", err.Error()))
		}
	}

	return true, nil
}

// doCurrent 操作当前目录
func doCurrent(dir string, fromDir []os.DirEntry) {
	for _, f := range fromDir {
		// 跳过子目录
		if f.IsDir() {
			continue
		}
		// 操作文件
		moveFile(dir, f)
	}
}

// doWithChild 操作包含子目录
func doWithChild(dir string, fromDir []os.DirEntry) {
	for _, f := range fromDir {
		// 检查子目录
		if f.IsDir() {
			childDir := filepath.Join(dir, f.Name())
			fromChildDir, err := os.ReadDir(childDir)
			if err != nil {
				log.Printf("[ERROR] 读取目录[%s]失败 err: %s", f.Name(), err.Error())
				continue
			}
			// 优先处理子目录
			doWithChild(childDir, fromChildDir)
			continue
		}
		// 操作文件
		moveFile(dir, f)
	}
}

// moveFile 移动文件
func moveFile(dir string, f os.DirEntry) {
	// 仅处理文件,此为双重校验
	if f.IsDir() {
		return
	}
	// 文件名匹配
	fileName := f.Name()
	if fileNameVal != "" {
		if fuzzyVal {
			if !strings.Contains(fileName, fileNameVal) {
				return
			}
		} else {
			if fileName != fileNameVal {
				return
			}
		}
	}

	// 移动文件
	srcFilePathName := filepath.Join(dir, fileName)
	srcFile, err := os.Open(srcFilePathName)
	if err != nil {
		log.Printf("[ERROR] 读取文件[%s]失败 err: %s", srcFilePathName, err.Error())
		return
	}
	defer srcFile.Close() // release
	// 创建目标文件
	dstFilePathName := filepath.Join(toVal, fileName)
	// 非覆盖模式,需检查序号
	if !coverVal {
		var index int32
		dirs, err := os.ReadDir(toVal)
		if err != nil {
			log.Printf("[ERROR] 读取目标文件[%s]失败 err: %s", toVal, err.Error())
			return
		}
		// 检查该目录下文件,确定增补序号
		for _, dir := range dirs {
			if strings.Contains(dir.Name(), fileName) {
				// 精确匹配
				atomic.AddInt32(&index, 1)
			} else if strings.Contains(dir.Name(), ").") {
				// 序号扩展匹配
				curNamePre := dir.Name()[:strings.Index(dir.Name(), "(")]
				compareNamePre := fileName[:strings.Index(fileName, ".")]
				if compareNamePre == curNamePre {
					atomic.AddInt32(&index, 1)
				}
			}
		}

		if index > 0 {
			ns := strings.Split(fileName, ".")
			baseName := ns[0]
			var baseExt string
			if len(ns) > 1 { // for non ext file
				baseExt = ns[1]
			}
			dstFilePathName = filepath.Join(toVal, fmt.Sprintf("%s(%d).%s", baseName, index, baseExt))
		}
	}
	// 复制文件并删除原文件
	dstFile, err := os.Create(dstFilePathName)
	if err != nil {
		log.Printf("[ERROR] 创建目标文件[%s]失败 err: %s", dstFilePathName, err.Error())
		return
	}
	defer dstFile.Close() // release
	_, err = io.Copy(dstFile, srcFile)
	if err != nil {
		log.Printf("[ERROR] 复制文件内容[%s]失败 err: %s", srcFilePathName, err.Error())
		return
	}
	err = dstFile.Sync()
	if err != nil {
		log.Printf("[ERROR] 移动文件[%s]失败 err: %s", srcFilePathName, err.Error())
		return
	}
}



// isFolderExists 检查目录是否存在
func isFolderExists(folderName string) bool {
	info, err := os.Stat(folderName)
	if os.IsNotExist(err) {
		return false
	}
	if err != nil {
		return false
	}
	return info.IsDir()
}

FyneApp.toml

[Details]
  Icon = "./images/fyne.png"
  Name = "BigMove"
  ID = "com.locker.bigmove"
  Version = "1.1.0"
  Build = 7

[Development]
  HelperText = "This binary was built with debug symbols"

[Release]
  HelperText = "This binary was built without debug symbols"

[LinuxAndBSD]
  GenericName = "Toolkit Demo"
  Categories = ["Development"]
  Comment = "A demo of Fyne and its capabilities."
  Keywords = ["bigmove", "fyne"]

编译脚本

  1. 安装 fyne
go install fyne.io/fyne/v2@latest
  1. 打包
fyne package -os windows -icon images/fyne.png

界面展示

在这里插入图片描述
在这里插入图片描述


后来

朋友在软件的帮助下……工作得挺好。
我们都很高兴。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

差点GDP

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值