对话框 Dialog
本教程共 42 篇 · 第 17 篇 · 更新于 2026-08-03
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 |
| CancelButton | Esc 对应的取消按钮 | 仅 Mac |
Note标准对话框的按钮文字是系统定的。Windows 和 Linux 上
Buttons字段不生效,返回值只会是"Ok"、"Yes"、"No"、"Cancel"那一套。只有 Mac 允许你自定义最多 4 个按钮文字。
17-4 用 FileFilter 过滤文件类型
打开或保存时,用 Filters 限定可选文件类型,避免用户选错。每个 FileFilter 有 DisplayName(展示名)和 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,结果通过绑定返回。下一章讲另一类原生能力——系统通知。