对话框 dialog
本教程共 45 篇 · 第 22 篇 · 更新于 2026-08-03
22. 对话框 dialog
本节目标
- 用 dialog 显示文件选择、保存、消息框
- 理解各方法的返回值结构
- 在渲染进程经 IPC 触发原生对话框
- 掌握文件类型过滤器等细节
原生对话框是桌面应用的基本交互:选个文件、挑个保存位置、弹个确认框。Electron 的 dialog 模块封装了操作系统自带的文件选择器和消息框,外观和系统一致,比自己用 HTML 画一个更可靠。
本章讲三个最常用的能力:打开文件、保存文件、消息框。并演示渲染进程如何经 IPC 触发这些主进程才能调用的对话框。
22-1 打开文件对话框
dialog.showOpenDialog 在 v43.2.0 返回 Promise,解析值里有 canceled 和 filePaths。properties 控制能选文件还是目录、是否多选。
// main.js(主进程)
const { dialog } = require('electron/main')
async function pickFiles() {
const result = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections'],
filters: [
{ name: '图片', extensions: ['jpg', 'png', 'gif'] },
{ name: '所有文件', extensions: ['*'] }
]
})
if (result.canceled) {
console.log('用户取消了')
} else {
console.log('选中文件:', result.filePaths)
}
}
filters 限制可显示的文件类型;extensions 不带点也不带通配符('png' 正确,'.png' 与 '*.png' 都错误)。给窗口对象当第一个参数,对话框会作为该窗口的模态框附着其上。
注意 Windows 和 Linux 不能同时选文件和目录。若 properties 同时含 openFile 和 openDirectory,这两个平台会退化成目录选择器。
22-2 保存文件对话框
dialog.showSaveDialog 同样返回 Promise,解析值含 canceled 与 filePath(注意这里是单数,因为是单个路径)。
const { dialog } = require('electron/main')
async function saveFile() {
const result = await dialog.showSaveDialog({
title: '保存配置',
defaultPath: 'config.json',
filters: [{ name: 'JSON', extensions: ['json'] }]
})
if (!result.canceled) {
console.log('将保存到:', result.filePath)
}
}
defaultPath 指定默认目录或文件名;buttonLabel 可改确认按钮文字。在 macOS 上,官方推荐用异步版本,避免展开/收起对话框时出问题。
22-3 消息框
dialog.showMessageBox 弹出一个提示、警告或询问框。buttons 是按钮文字数组,返回值的 response 是被点击按钮的索引。
const { dialog } = require('electron/main')
async function confirmQuit() {
const result = await dialog.showMessageBox({
type: 'question',
title: '确认退出',
message: '确定要退出吗?',
detail: '未保存的内容将会丢失。',
buttons: ['取消', '退出'],
defaultId: 1,
cancelId: 0
})
if (result.response === 1) {
console.log('用户选择退出')
}
}
type 可选 none、info、error、question、warning,影响图标。defaultId 指定默认高亮按钮,cancelId 指定按 Esc 时对应的按钮索引。还有同步版 showMessageBoxSync,会阻塞进程,一般不推荐。
此外 dialog.showErrorBox(title, content) 可在 app 的 ready 之前调用,适合早期启动报错;它接受一个标题和正文,简单直接。
22-4 渲染进程经 IPC 触发
dialog 是主进程模块,渲染进程不能直接调用。正确做法是渲染进程发 IPC 请求,主进程弹出对话框,再把结果回传。下面用 ipcMain.handle 与 ipcRenderer.invoke 做请求-响应。
// main.js(主进程)
const { app, BrowserWindow, dialog, ipcMain } = require('electron/main')
const path = require('node:path')
function createWindow() {
const win = new BrowserWindow({
webPreferences: { preload: path.join(__dirname, 'preload.js') }
})
win.loadFile('index.html')
}
ipcMain.handle('open-file', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile']
})
return result.canceled ? null : result.filePaths[0]
})
app.whenReady().then(createWindow)
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('dialog', {
openFile: () => ipcRenderer.invoke('open-file')
})
// 渲染进程脚本
document.getElementById('pick').addEventListener('click', async () => {
const file = await window.dialog.openFile()
if (file) document.getElementById('out').innerText = file
})
这套范式的要点:对话框永远在主进程执行,渲染进程只暴露一个「请打开文件」的安全接口,拿回路径即可。绝不要把 dialog 直接塞进 contextBridge 全量暴露。
22-5 同步版本与错误框
除了异步方法,dialog 还有对应的同步版本:showOpenDialogSync、showSaveDialogSync、showMessageBoxSync。它们会阻塞调用进程直到用户操作完成,并返回直接结果而非 Promise。
const { dialog } = require('electron/main')
// 同步阻塞,返回字符串或空串
const path = dialog.showSaveDialogSync({ defaultPath: 'note.txt' })
console.log('保存到', path)
一般推荐异步版本,避免界面卡顿。只有在启动早期、窗口还没建好时用 showErrorBox 报错最合适:
dialog.showErrorBox('启动失败', '配置文件已损坏,请重新安装。')
showErrorBox 不依赖 app 的 ready,可在最早阶段使用,是兜底报错的好工具。
22-6 文件类型过滤器细节
filters 控制对话框里能看到的文件类型,每个过滤器有 name 和 extensions。extensions 不带点、不带通配符,单个 '*' 表示全部文件。
const filters = [
{ name: '图片', extensions: ['jpg', 'png', 'gif', 'webp'] },
{ name: '文档', extensions: ['txt', 'md', 'pdf'] },
{ name: '所有文件', extensions: ['*'] }
]
macOS 上还有 message(输入框上方的提示)、securityScopedBookmarks(打包进 Mac App Store 时用的安全书签)等专属选项。Windows/Linux 则支持 promptToCreate、dontAddToRecent 等。跨平台时只写通用项,平台专属按需补充。
22-7 模态与默认路径
把窗口对象作为第一个参数传给对话框方法,对话框会以「模态」形式附着在该窗口上,用户必须先处理对话框才能操作窗口。开发时常用 mainWindow 当父窗口,体验更聚焦。
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile']
})
defaultPath 指定对话框打开时的默认目录或文件名。不传时,系统通常落到用户的下载目录或主目录。对保存对话框来说,给个合理的默认文件名(如按日期命名)能明显减少用户操作成本。
另外,title 在 Windows/Linux 上会显示,macOS 部分桌面环境不显示,所以关键信息应放在 message 里,而不是只靠 title 传达。
22-8 完整示例:从渲染进程选文件并回显
把 IPC 触发、主进程弹框、结果回传串起来,下面是一个完整可运行片段。渲染进程点按钮 → 经 preload 调安全接口 → 主进程弹打开对话框 → 把路径回传给页面显示。
// main.js(主进程)
const { app, BrowserWindow, dialog, ipcMain } = require('electron/main')
const path = require('node:path')
function createWindow() {
const win = new BrowserWindow({
webPreferences: { preload: path.join(__dirname, 'preload.js') }
})
ipcMain.handle('pick-file', async () => {
const r = await dialog.showOpenDialog(win, {
properties: ['openFile'],
filters: [{ name: '文本', extensions: ['txt', 'md'] }]
})
return r.canceled ? null : r.filePaths[0]
})
win.loadFile('index.html')
}
app.whenReady().then(createWindow)
// preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('fs', {
pick: () => ipcRenderer.invoke('pick-file')
})
// 渲染进程脚本
document.getElementById('btn').addEventListener('click', async () => {
const p = await window.fs.pick()
if (p) document.getElementById('out').innerText = p
})
这套写法把「系统能力」严格关在主进程,渲染进程只拿到一个返回路径的异步函数。逻辑清晰,也守住了 Electron 的进程边界。
对话框设计的小建议
对话框要「够用就好」。文件选择用系统原生框,别自己画一个文件树,既费力又不如原生的好用。消息框按钮要少而明确,别堆五六个选项让用户选择困难。
默认路径和过滤器能显著提升体验。保存对话框给个合理的默认文件名,打开对话框按文件类型过滤,用户少走很多弯路。同时记得把窗口当父窗口传入,让对话框以模态贴合当前窗口。
最重要的是守边界:对话框在主进程,渲染进程需用时走 IPC。把 dialog 整体暴露给页面是错误做法,只暴露封装好的、返回结果的接口,既安全又好维护。
小结补充
补充一点:同步版本(showOpenDialogSync 等)虽然写起来简单,但会阻塞主进程,界面会短暂卡住。除非在早期启动、还没有窗口时使用 showErrorBox,否则都推荐异步版本,保持界面流畅。
小结
dialog 模块提供原生文件选择、保存和消息框,全部返回 Promise。打开用 showOpenDialog(filePaths 是数组),保存用 showSaveDialog(filePath 是单值),询问用 showMessageBox(response 为按钮索引)。
因为 dialog 属主进程,渲染进程要触发就走 IPC:preload 暴露最小接口,主进程 ipcMain.handle 里真正弹框并返回结果。这样既用上系统原生体验,又守住进程边界。