首页 / Electron 入门教程 / 对话框 dialog

Electron 入门教程

对话框 dialog

本教程共 45 篇 · 第 22 篇 · 更新于 2026-08-03

Electron对话框dialog文件选择消息框IPC

22. 对话框 dialog

本节目标

  • 用 dialog 显示文件选择、保存、消息框
  • 理解各方法的返回值结构
  • 在渲染进程经 IPC 触发原生对话框
  • 掌握文件类型过滤器等细节

原生对话框是桌面应用的基本交互:选个文件、挑个保存位置、弹个确认框。Electron 的 dialog 模块封装了操作系统自带的文件选择器和消息框,外观和系统一致,比自己用 HTML 画一个更可靠。

本章讲三个最常用的能力:打开文件、保存文件、消息框。并演示渲染进程如何经 IPC 触发这些主进程才能调用的对话框。

22-1 打开文件对话框

dialog.showOpenDialog 在 v43.2.0 返回 Promise,解析值里有 canceledfilePathsproperties 控制能选文件还是目录、是否多选。

// 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 同时含 openFileopenDirectory,这两个平台会退化成目录选择器。

22-2 保存文件对话框

dialog.showSaveDialog 同样返回 Promise,解析值含 canceledfilePath(注意这里是单数,因为是单个路径)。

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 可选 noneinfoerrorquestionwarning,影响图标。defaultId 指定默认高亮按钮,cancelId 指定按 Esc 时对应的按钮索引。还有同步版 showMessageBoxSync,会阻塞进程,一般不推荐。

此外 dialog.showErrorBox(title, content) 可在 appready 之前调用,适合早期启动报错;它接受一个标题和正文,简单直接。

22-4 渲染进程经 IPC 触发

dialog 是主进程模块,渲染进程不能直接调用。正确做法是渲染进程发 IPC 请求,主进程弹出对话框,再把结果回传。下面用 ipcMain.handleipcRenderer.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 还有对应的同步版本:showOpenDialogSyncshowSaveDialogSyncshowMessageBoxSync。它们会阻塞调用进程直到用户操作完成,并返回直接结果而非 Promise。

const { dialog } = require('electron/main')

// 同步阻塞,返回字符串或空串
const path = dialog.showSaveDialogSync({ defaultPath: 'note.txt' })
console.log('保存到', path)

一般推荐异步版本,避免界面卡顿。只有在启动早期、窗口还没建好时用 showErrorBox 报错最合适:

dialog.showErrorBox('启动失败', '配置文件已损坏,请重新安装。')

showErrorBox 不依赖 appready,可在最早阶段使用,是兜底报错的好工具。

22-6 文件类型过滤器细节

filters 控制对话框里能看到的文件类型,每个过滤器有 nameextensionsextensions 不带点、不带通配符,单个 '*' 表示全部文件。

const filters = [
  { name: '图片', extensions: ['jpg', 'png', 'gif', 'webp'] },
  { name: '文档', extensions: ['txt', 'md', 'pdf'] },
  { name: '所有文件', extensions: ['*'] }
]

macOS 上还有 message(输入框上方的提示)、securityScopedBookmarks(打包进 Mac App Store 时用的安全书签)等专属选项。Windows/Linux 则支持 promptToCreatedontAddToRecent 等。跨平台时只写通用项,平台专属按需补充。

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。打开用 showOpenDialogfilePaths 是数组),保存用 showSaveDialogfilePath 是单值),询问用 showMessageBoxresponse 为按钮索引)。

因为 dialog 属主进程,渲染进程要触发就走 IPC:preload 暴露最小接口,主进程 ipcMain.handle 里真正弹框并返回结果。这样既用上系统原生体验,又守住进程边界。