首页 / Wails 入门教程 / 对话框 Dialog

Wails 入门教程

对话框 Dialog

本教程共 42 篇 · 第 17 篇 · 更新于 2026-08-03

Wails桌面开发Dialog对话框文件选择

17. 对话框 Dialog

本节目标

  • 明白对话框为什么只能从 Go 侧调用
  • 会用四种文件对话框:打开、多选、选目录、保存
  • 会用消息框 MessageDialog,区分四种 DialogType
  • 会用 FileFilter 限制文件类型
  • 知道前端怎么通过绑定间接调起对话框

17-1 对话框只能从 Go 侧调

上一章提到,Runtime 的 Dialog 模块在 JS 运行时里是「不支持」的。也就是说 window.runtime 上没有 OpenFileDialog 这种东西。

这不是疏漏,是设计决定。文件选择、消息框这些原生控件,Wails 把它们封装在 Go 侧,前端拿不到。

那前端想弹一个「打开文件」的框怎么办?标准做法是:前端调你绑定的一个 Go 方法,那个方法内部去调 runtime.OpenFileDialog,把选中的路径当返回值传回前端。

所有对话框方法的签名规律一致,第一个参数都是 context:

Go: OpenFileDialog(ctx context.Context, options OpenDialogOptions) (string, error)

记住这一点,后面四种文件对话框和一个消息框,形式都差不多,只是 Options 结构体不一样。

17-2 四种文件对话框

打开单个文件 OpenFileDialog

最常用。弹出一个系统文件选择器,用户选中后返回完整路径,取消则返回空字符串和 nil。

func (a *App) PickFile() (string, error) {
	result, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
		Title: "选择一个文件",
	})
	if err != nil {
		return "", err
	}
	return result, nil
}

多选文件 OpenMultipleFilesDialog

返回 []string。用户取消时返回的是 nil(不是空切片),判断时要留意。

func (a *App) PickFiles() ([]string, error) {
	return runtime.OpenMultipleFilesDialog(a.ctx, runtime.OpenDialogOptions{
		Title: "选择多个文件",
	})
}

选择目录 OpenDirectoryDialog

只要目录不要文件。注意 macOS 上 CanCreateDirectories 字段可让用户现场建文件夹,Windows 和 Linux 不支持这个字段。

func (a *App) PickDir() (string, error) {
	return runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{
		Title: "选择工作目录",
	})
}

保存文件 SaveFileDialog

用于「另存为」场景。用户选好文件名后返回路径,取消返回空串。它用的是另一套 SaveDialogOptions,字段和 OpenDialogOptions 几乎一样。

func (a *App) SaveTo() (string, error) {
	return runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
		Title:           "保存文件",
		DefaultFilename: "untitled.txt",
	})
}

OpenDialogOptions 的常用字段:

字段作用
DefaultDirectory打开时默认所在的目录
DefaultFilename默认文件名
Title对话框标题
Filters文件类型过滤器
ShowHiddenFiles是否显示隐藏文件(Mac/Linux)
CanCreateDirectories是否允许建目录(Mac)
ResolvesAliases返回真实文件而非别名(Mac)
TreatPackagesAsDirectories可进入包内部(Mac)
Tip

想让对话框一打开就落在用户上次的目录,可以自己记一个路径存到结构体里,下次把 DefaultDirectory 填上。这在批量处理文件的工具里体验会好很多。

17-3 消息框 MessageDialog

消息框用来提示信息、确认操作。它用 MessageDialogOptions,最关键的是 Type 字段,对应四个 DialogType 常量:

const (
	InfoDialog     DialogType = "info"
	WarningDialog  DialogType = "warning"
	ErrorDialog    DialogType = "error"
	QuestionDialog DialogType = "question"
)
func (a *App) Confirm() (string, error) {
	return runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
		Type:    runtime.QuestionDialog,
		Title:   "确认",
		Message: "确定要删除吗?",
	})
}

返回值是用户点掉的那个按钮的文字。比如 Question 对话框在 Windows 上通常返回 "Yes""No"

MessageDialogOptions 字段:

字段作用平台
Type对话框类型全平台
Title标题全平台
Message提示正文全平台
Buttons自定义按钮文字仅 Mac
DefaultButton回车对应的默认按钮Win/Mac
CancelButtonEsc 对应的取消按钮仅 Mac
Note

标准对话框的按钮文字是系统定的。Windows 和 Linux 上 Buttons 字段不生效,返回值只会是 "Ok""Yes""No""Cancel" 那一套。只有 Mac 允许你自定义最多 4 个按钮文字。

17-4 用 FileFilter 过滤文件类型

打开或保存时,用 Filters 限定可选文件类型,避免用户选错。每个 FileFilterDisplayName(展示名)和 Pattern(扩展名,分号分隔):

func (a *App) PickImage() (string, error) {
	return runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
		Title: "选择图片",
		Filters: []runtime.FileFilter{
			{
				DisplayName: "图片 (*.png;*.jpg)",
				Pattern:     "*.png;*.jpg",
			},
			{
				DisplayName: "视频 (*.mov;*.mp4)",
				Pattern:     "*.mov;*.mp4",
			},
		},
	})
}

平台差异值得注意:Windows 和 Linux 会把每个 FileFilter 显示成下拉框里的一项;Mac 只认「一组」过滤规则,多个 Filter 的 Pattern 会被合并生效。所以 Mac 上写多个 Filter,结果是把所有扩展名一起过滤。

type FileFilter struct {
	DisplayName string // 展示名,如 "Image Files (*.jpg, *.png)"
	Pattern     string // 扩展名,分号分隔,如 "*.jpg;*.png"
}

17-5 前端怎么弹对话框

因为 JS 运行时没有 Dialog,前端必须绕道。典型写法是绑定一个 Go 方法,方法内部调对话框,把结果返回给前端。React 里直接 await 这个绑定方法。

Go 侧:

package main

import (
	"context"
	"github.com/wailsapp/wails/v2/pkg/runtime"
)

type App struct {
	ctx context.Context
}

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

func (a *App) SelectFile() (string, error) {
	return runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
		Title: "选择文件",
	})
}

前端侧(React + TypeScript,import 生成的模块):

import { SelectFile } from "../wailsjs/go/main/App";

function Toolbar() {
  async function handlePick() {
    try {
      const path = await SelectFile();
      if (path === "") {
        // 用户取消了,path 是空串
        return;
      }
      console.log("选中的文件:", path);
    } catch (e) {
      console.error("打开失败", e);
    }
  }

  return <button onClick={handlePick}>选择文件</button>;
}

wails dev 之后,frontend/wailsjs/go/main/App.js.d.ts 会自动生成,SelectFile 的类型签名也一并有了。

Warning

取消对话框的返回值是空字符串,多选是 nil,不是 error。所以判断「用户取消」要用 path === "",不要误以为会进 catch。只有真正的系统错误(比如权限问题)才会走 reject。

17-6 平台差异与坑

对话框在不同系统表现不一,这里列几个容易踩的:

  • 按钮文字:Windows/Linux 用系统固定的文字,自定义的 Buttons 无效。
  • Question 默认按钮:Windows 上默认是 "Yes"、取消是 "No",想换默认可设 DefaultButton: "No"
  • 取消按钮绑定 Esc:Mac 上设 CancelButton 后按 Esc 就返回该按钮文字。
  • 隐藏文件ShowHiddenFiles 只在 Mac 和 Linux 生效,Windows 忽略。
  • 建目录CanCreateDirectories 仅 Mac 支持。

这些差异不用背,写跨平台应用时在目标系统上各点一次对话框验证即可。

17-7 在生命周期里弹对话框

对话框需要运行时环境就绪才能用。回顾第 16 章的提醒:OnStartup 时窗口还在初始化,此刻调对话框不一定成功。稳妥的做法是放到 OnDomReady 之后。

一个典型场景是「首次启动引导」。应用装好后第一次打开,想弹个欢迎框问用户要不要导入旧数据:

func (a *App) domready(ctx context.Context) {
	firstRun := !a.hasConfig()
	if !firstRun {
		return
	}
	answer, _ := runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
		Type:    runtime.QuestionDialog,
		Title:   "欢迎",
		Message: "检测到这是首次运行,是否导入旧配置?",
	})
	if answer == "Yes" {
		path, _ := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{
			Title: "选择旧配置文件",
		})
		if path != "" {
			a.importConfig(path)
		}
	}
}

这里把确认框和文件框串起来用,组合很自然:先用 MessageDialog 拿用户的意图,再视情况用 OpenFileDialog 拿具体文件。两处的取消都要单独判空串,不能假设用户一定会选。

Tip

不要在每个按钮回调里都写一遍对话框逻辑。把对话框封装成结构体方法(如 a.pickFile()),前端只管 await 绑定方法,Go 侧统一处理空串和 error,代码会清爽很多。

常见误区

在前端直接调 window.runtime.OpenFileDialog。这个函数在 JS 运行时不存在,会报 undefined。必须走 Go 绑定。

用 error 判断取消。取消返回空串或 nil,不报错。判断取消要看返回值,不是 catch。

以为 Mac 也支持自定义 Windows 按钮。Mac 的 Buttons 才是自定义,Windows/Linux 不认。

忘记存 context。对话框需要 Wails 给的那个 context,自己造的 context.Background() 会让对话框调不起来甚至 panic。

多选返回值是 nil 还去遍历。取消时返回 nil,直接 for 循环会安全,但如果在它上面调 len() 不会崩,只是得先判空再决定要不要处理。

小结

对话框是 Runtime 里唯一的「Go 专属」模块,四种文件对话框加一个消息框,覆盖了绝大多数原生交互需求。

文件对话框共享 OpenDialogOptions,保存用 SaveDialogOptions;消息框用 MessageDialogOptions,靠 DialogType 区分 info/warning/error/question。FileFilter 负责限定扩展名。

前端调对话框的唯一姿势:绑一个 Go 方法,方法里调 runtime.XxxDialog,结果通过绑定返回。下一章讲另一类原生能力——系统通知。