您现在的位置是:网站首页> Go语言

Go 跨平台GUI技术相关收集

摘要

Go 跨平台GUI技术相关收集


1.jpg


在Windows 10专业版搭建Fyne(Go 跨平台GUI)开发环境

walk UI程序

使用 Go 和 Web 技术构建桌面应用程序

Wails 使用 Go 和 JavaScript/TypeScript 构建跨平台桌面应用程序的框架




在Windows 10专业版搭建Fyne(Go 跨平台GUI)开发环境

点击查看原文

国人整理的开放文档

点击查看源码

点击进入创建者主页

点击查看原文档

目录

Fyne 和 MSYS2简介

安装 MSYS2

安装Fyne打包


强制设置环境变量,最好在终端管理员Shell里执行: setx ANDROID_NDK_HOME "C:\android-ndk-r20b"


一 Fyne 和 MSYS2简介

1.1 Fyne

fyne 是一个用于 Go 语言的 GUI 包,它提供了一个简单而强大的界面构建框架。

 fyne 的核心是一个事件驱动的渲染引擎,它可以轻松地创建具有丰富交互性的图形界面。该框架提供了丰富的内置控件,如按钮、文本框、标签、列表等,并且支持自定义控件的创建。

 此外,fyne 还提供了一系列的工具和函数,用于处理界面事件、绘制图形、处理图像和字体等。它还支持跨平台开发,可以在 Windows、macOS 和 Linux 等多个操作系统上运行。

 总的来说,fyne 是一个非常强大和灵活的 GUI 包,可以帮助开发人员快速创建具有高交互性的图形界面。它简单易用,并且具有很高的可扩展性,可以满足各种不同的界面需求。


1.2 MSYS2

MSYS2 是一个基于 MinGW-w64 的发行版,它提供了一个完整的 POSIX 工具链和开发环境,可以让开发人员在 Windows 上轻松地进行开源开发。


MSYS2 包括了许多流行的开源工具,如 GCC、GDB、Make、Python、Perl 等等。它还提供了一个 Bash shell,可以让开发人员像在 Linux 上一样使用命令行。


使用 MSYS2,开发人员可以编译、调试和运行各种开源软件,包括 C、C++、Java、Python 等语言的程序。此外,MSYS2 还可以与其他 Windows 应用程序无缝集成,例如 Microsoft Visual Studio、 Eclipse CDT 等。


总的来说,MSYS2 是一个非常强大和灵活的发行版,为开发人员在 Windows 上进行开源开发提供了一个完整的解决方案。



二 安装 MSYS2

2.1 下载MSYS2

MSYS2下载地址


2.2 安装

选择安装目录(根据个人喜好进行选择),如图1:

1.png


 安装完成,如图2:

 

2.png


 启动UCRT64环境:

3.png


 执行以下安装命令:

pacman -Syu

pacman -S git mingw-w64-x86_64-toolchain

pacman -S mingw-w64-ucrt-x86_64-gcc

gcc --version

各环境的差别,

1.png

2.3 环境变量设置

环境变量设置包括windows环境变量设置和UCRT64两个环境,Windows 环境需要加入:


1.png


 UCRT64环境变量PATH加入Go的bin目录:

1.png

 执行:

source .bashrc

go version

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

正确显示go版本及安装fyne命令行,即配置成功!


2.4 检测安装环境

下载Fyne Setup

解压后双击exe文件,显示如下则环境配置成功!

 

1.png

 注意:使用Goland进行开发时,编译环境需加入:


CC=gcc;CGO_ENABLED=1;GOARCH=amd64;GOOS=windows

CC=gcc;CGO_ENABLED=1;GOARCH=amd64;GOOS=windows

否则报错:


也可使用mingw64编译配置如下:

1.下载mingw64:https://winlibs.com/

下载最新的 “MinGW-w64 GCC standalone” 版本(推荐 posix-seh 版本)。

2. 解压 MinGW-w64

下载完成后,将压缩包解压到 C:\mingw-w64(或其他你喜欢的目录)。

例如:

C:\mingw-w64\mingw64

3. 配置环境变量

右键 此电脑 → 属性 → 高级系统设置 → 环境变量。

在 系统变量 里找到 Path,点击 编辑。

添加以下路径(根据你的安装路径调整):

注意:用MSYS2 安装的环境变量不是这个目录,是MSYS2 里面的目录

C:\mingw-w64\mingw64\bin


配置go的编译环境文件如win64位的

##########开始

# native compiler windows amd64


GOROOT=c:\Go

#GOBIN=

GOARCH=amd64

GOOS=windows

CGO_ENABLED=1


CC=C:\mingw64\bin\gcc.exe

CXX=C:\mingw64\bin\g++.exe



PATH=c:\mingw64\bin;%GOROOT%\bin;%PATH%


LITEIDE_GDB=gdb64

LITEIDE_MAKE=mingw32-make

LITEIDE_TERM=%COMSPEC%

LITEIDE_TERMARGS=

LITEIDE_EXEC=%COMSPEC%

LITEIDE_EXECOPT=/C

##########结束


中文显示乱码解决方法:

 在main包中init函数加入:

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

    }

    }

}

Windows下打包:


fyne package -os windows -icon app.jpg

三 参考文档

3.1 Fyne 官方帮助文档



安装

使用go install将可执行文件复制到您的 gobin目录中。要将带有图标等的应用程序安装到操作系统的标准应用程序位置,您可以使用 fyne 实用程序和“install”子命令。

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

fyne install


手机包装

要在移动设备上运行,必须打包应用程序。为此,我们可以使用 fyne 实用程序“package”子命令。您需要根据提示添加适当的参数,但基本命令如下所示。打包后,您可以使用平台开发工具或 fyne“install”子命令进行安装。

fyne package -os android -appID my.domain.appname -icon mobileIcon.png

也可以

fyne package -os android/arm64 -appID my.domain.appname -icon mobileIcon.png

fyne install -os android

构建的 Android 应用程序可以在真实设备或 Android 模拟器中运行。但是,为 iOS 构建略有不同。如果“-os”参数为“ios”,则它仅适用于真实的 iOS 设备。将“-os”指定为“iossimulator”可让应用程序在 iOS 模拟器中运行:

fyne package -os ios -appID my.domain.appname -icon mobileIcon.png

fyne package -os iossimulator -appID my.domain.appname -icon mobileIcon.png


编译windows程序

fyne package -os windows -icon favicon.ico --name test.exe


准备发布

使用 fyne 实用程序“release”子命令,您可以打包您的应用以发布到应用商店和市场。确保您已安装标准构建工具,并已按照平台文档设置帐户和签名。然后您可以执行类似下面的操作,请注意该-os ios参数允许从 macOS 计算机构建 iOS 应用。其他组合也同样有效 :)

$ fyne release -os ios -certificate "Apple Distribution" -profile "My App Distribution" -appID "com.example.myapp"

上述命令将创建一个“.ipa”文件,然后可以将其上传到 iOS App Store。



walk UI程序

简单代码

package main


import (

    "syscall"

    "github.com/lxn/walk"

    "github.com/lxn/win"

)


func main() {

    window, _ := walk.NewMainWindow()

    // 设置窗体标题

    window.SetTitle(`你好世界!`)

    // 设置窗体的宽高

    window.SetWidth(400)

    window.SetHeight(400)

    // 设置窗体生成在屏幕的正中间

    // 窗体横坐标 = ( 屏幕宽度 - 窗体宽度 ) / 2

    // 窗体纵坐标 = ( 屏幕高度 - 窗体高度 ) / 2

    window.SetX((int(win.GetSystemMetrics(0)) - window.Width()) / 2)

    window.SetY((int(win.GetSystemMetrics(1)) - window.Height()) / 2)

    // 设置窗体为显示状态(默认:隐藏状态)

    window.Show()

    // 运行窗体

    window.Run()

}

// HelloWalkUI project main.go

package main


import (

"fmt"

"io"

"os"

"strings"


"github.com/lxn/walk"

. "github.com/lxn/walk/declarative"

)


type MyMainWindow struct {

*walk.MainWindow

edit *walk.TextEdit

}


func main() {

mw := &MyMainWindow{}

if err := (MainWindow{

AssignTo: &mw.MainWindow,

MinSize:  Size{400, 300},

Size:     Size{600, 400},

MenuItems: []MenuItem{

Menu{

Text: "文件",

Items: []MenuItem{

Action{

Text: "打开文件",

Shortcut: Shortcut{ //定义快捷键后会有响应提示显示

Modifiers: walk.ModControl,

Key:       walk.KeyO,

},

OnTriggered: mw.openFileActionTriggered, //点击动作触发响应函数

},

Action{

Text: "另存为",

Shortcut: Shortcut{

Modifiers: walk.ModControl | walk.ModShift,

Key:       walk.KeyS,

},

OnTriggered: mw.saveFileActionTriggered,

},

Action{

Text: "退出",

OnTriggered: func() {

mw.Close()

},

},

},

},

Menu{

Text: "帮助",

Items: []MenuItem{

Action{

Text: "关于",

OnTriggered: func() {

walk.MsgBox(mw, "关于", "这是一个菜单和工具栏的实例",

walk.MsgBoxIconInformation|walk.MsgBoxDefButton1)

},

},

},

},

},

ToolBar: ToolBar{ //工具栏

ButtonStyle: ToolBarButtonTextOnly,

Items: []MenuItem{

Menu{

Text: "New",

Items: []MenuItem{

Action{

Text:        "A",

OnTriggered: mw.newAction_Triggered,

},

Action{

Text:        "B",

OnTriggered: mw.newAction_Triggered,

},

},

OnTriggered: mw.newAction_Triggered, //在菜单中不可如此定义,会无响应

},

Separator{}, //分隔符

Action{

Text:        "View",

OnTriggered: mw.changeViewAction_Triggered,

},

},

},

Layout: VBox{},

Children: []Widget{

TextEdit{

AssignTo: &mw.edit,

},

},

OnDropFiles: mw.dropFiles, //放置文件事件响应函数

}).Create(); err != nil {

fmt.Fprintln(os.Stderr, err)

return

}


mw.Run()

}


func (mw *MyMainWindow) openFileActionTriggered() {

dlg := new(walk.FileDialog)

dlg.Title = "打开文件"

dlg.Filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"


if ok, err := dlg.ShowOpen(mw); err != nil {

fmt.Fprintln(os.Stderr, "错误:打开文件时\r\n")

return

} else if !ok {

fmt.Fprintln(os.Stderr, "用户取消\r\n")

return

}


s := fmt.Sprintf("选择了:%s\r\n", dlg.FilePath)

mw.edit.SetText(s)

}


func (mw *MyMainWindow) saveFileActionTriggered() {

dlg := new(walk.FileDialog)

dlg.Title = "另存为"


if ok, err := dlg.ShowSave(mw); err != nil {

fmt.Fprintln(os.Stderr, err)

return

} else if !ok {

fmt.Fprintln(os.Stderr, "取消")

return

}


data := mw.edit.Text()

filename := dlg.FilePath

f, err := os.Open(filename)

if err != nil {

f, _ = os.Create(filename)

} else {

f.Close()

f, err = os.OpenFile(filename, os.O_WRONLY, 0x666)

}

if len(data) == 0 {

f.Close()

return

}

io.Copy(f, strings.NewReader(data))

f.Close()

}


func (mw *MyMainWindow) newAction_Triggered() {

walk.MsgBox(mw, "New", "Newing something up... or not.", walk.MsgBoxIconInformation)

}


func (mw *MyMainWindow) changeViewAction_Triggered() {

walk.MsgBox(mw, "Change View", "By now you may have guessed it. Nothing changed.", walk.MsgBoxIconInformation)

}


func (mw *MyMainWindow) dropFiles(files []string) {

mw.edit.SetText("")

for _, v := range files {

mw.edit.AppendText(v + "\r\n")

}

}

需要同名exe文件加个.manifest文件内容为:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>

<dependency>

<dependentAssembly>

<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>

</dependentAssembly>

</dependency>

<application xmlns="urn:schemas-microsoft-com:asm.v3">

<windowsSettings>

<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>

<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>

</windowsSettings>

</application>

</assembly>

1.png




使用 Go 和 Web 技术构建桌面应用程序

点击访问,为 Go 程序提供 Web 界面的传统方法是通过内置 Web 服务器。Wails 提供了一种不同的方法:它提供了将 Go 代码和 Web 前端一起打包成单个二进制文件的能力。通过提供的工具,可以很轻松的完成项目的创建、编译和打包。你所要做的就是发挥创造力!



Wails 使用 Go 和 JavaScript/TypeScript 构建跨平台桌面应用程序的框架

安装 Wails:

go install github.com/wailsapp/wails/v2/cmd/wails@latest


# 1. 初始化项目(选择前端框架,如 Vue/React/Svelte,或空白模板)

wails init -n my-first-wails-app -t vue


# 2. 进入项目目录

cd my-first-wails-app


# 3. 运行开发模式(实时热重载,前端修改即时生效)

wails dev


# 4. 打包为桌面应用(根据当前系统自动打包)

wails build



核心代码示例(前后端通信)

Go 后端(main.go):定义可被前端调用的函数

package main


import (

  "context"

  "github.com/wailsapp/wails/v2/pkg/runtime"

)


// App 结构体(后端核心,用于承载函数和状态)

type App struct {

  ctx context.Context

}


// NewApp 创建 App 实例

func NewApp() *App {

  return &App{}

}


// 初始化回调(应用启动时执行)

func (a *App) startup(ctx context.Context) {

  a.ctx = ctx

}


// 定义前端可调用的函数(需大写开头,暴露给前端)

func (a *App) Greet(name string) string {

  // 调用系统原生弹窗

  runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{

    Title:   "Hello",

    Message: "Hello from Wails!",

  })

  return "Hello, " + name + "!"

}


func main() {

  // 配置应用并启动

  err := runtime.NewAppWithOptions(&runtime.AppOptions{

    Title:  "My Wails App",

    Width:  800,

    Height: 600,

    AssetServer: &runtime.AssetServerOptions{

      Assets: runtime.BuiltinAssets, // 关联前端编译后的资源

    },

    BackgroundColour: &runtime.RGBA{255, 255, 255, 1},

  }, NewApp()).Run()


  if err != nil {

    println("Error:", err.Error())

  }

}


前端(Vue 组件):调用 Go 函数

vue

<template>

  <div class="container">

    <input v-model="name" placeholder="Enter your name" />

    <button @click="greet">Click Me</button>

    <p>{{ result }}</p>

  </div>

</template>


<script setup>

import { ref } from 'vue';

// 导入 Wails 前端 API(用于调用 Go 函数)

import { invoke } from '@wailsapp/runtime';


const name = ref('');

const result = ref('');


// 调用 Go 后端的 Greet 函数

const greet = async () => {

  try {

    const res = await invoke('Greet', name.value); // 第一个参数是 Go 函数名,后续是参数

    result.value = res;

  } catch (err) {

    console.error(err);

  }

};

</script>


Go调用页面函数例子

在 Wails 中,Go 后端可以主动调用前端页面的 JavaScript 函数,这通过 runtime.EventsEmit 或 runtime.CallJS 实现。以下是具体示例:

1. 使用 runtime.CallJS 直接调用前端函数

适用于需要同步或异步获取返回值的场景。

前端代码(JavaScript):

javascript

// 在全局作用域定义可被调用的函数

window.addNumbers = (a, b) => {

  return a + b;

};


window.showMessage = (message) => {

  document.getElementById("output").textContent = message;

};

Go 后端代码:

package main


import (

  "context"

  "fmt"

  "github.com/wailsapp/wails/v2/pkg/runtime"

)


type App struct {

  ctx context.Context

}


func (a *App) startup(ctx context.Context) {

  a.ctx = ctx

  

  // 1. 调用带返回值的前端函数

  var result int

  err := runtime.CallJS(a.ctx, "window.addNumbers(3, 5)", &result)

  if err != nil {

    fmt.Println("调用失败:", err)

  } else {

    fmt.Println("计算结果:", result) // 输出: 8

  }

  

  // 2. 调用无返回值的前端函数

  err = runtime.CallJS(a.ctx, "window.showMessage('来自Go的消息')", nil)

  if err != nil {

    fmt.Println("调用失败:", err)

  }

}


// 其他代码省略...

2. 使用事件机制(推荐)

通过 Go 发送事件,前端监听事件并执行对应函数,解耦性更好。

前端代码:

// 监听Go发送的事件

window.addEventListener("greet-event", (event) => {

  const name = event.detail.name;

  alert(`Hello, ${name}!`);

  // 可以在这里调用其他页面函数

});


Go 后端代码:

// 在需要的地方发送事件

func (a *App) SendGreetEvent() {

  // 发送事件并传递数据

  runtime.EventsEmit(a.ctx, "greet-event", map[string]interface{}{

    "name": "Wails User",

  })

}


关键说明:

1.runtime.CallJS:

第一个参数是上下文(ctx)

第二个参数是 JavaScript 代码字符串

第三个参数是用于接收返回值的变量指针(可传 nil)

2.事件机制:

Go 用 runtime.EventsEmit 发送事件

前端用 window.addEventListener 监听

适合复杂交互和状态同步场景

3.注意事项:

确保前端函数在全局作用域(如挂载到 window)

复杂数据传递会自动序列化为 JSON

避免在 Go 主线程中执行耗时操作


这种双向通信机制让 Wails 应用能够灵活地在前后端之间传递数据和调用功能。









Top