跳到主要内容

对话框

对话框是桌面应用与用户交互的重要方式。Electron 提供了多种对话框类型,包括文件选择、消息提示、错误提示等。本节介绍如何使用 dialog 模块创建各种对话框。

dialog 模块

dialog 模块只能在主进程中使用。它提供了多种对话框类型:

  • showOpenDialog:打开文件/目录选择对话框
  • showSaveDialog:保存文件对话框
  • showMessageBox:消息对话框
  • showErrorBox:错误对话框

文件选择对话框

选择文件

const { dialog } = require('electron')

async function openFile() {
const result = await dialog.showOpenDialog({
title: '选择文件',
defaultPath: '/Users/username/Documents',
buttonLabel: '选择',
filters: [
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '图片', extensions: ['jpg', 'png', 'gif'] },
{ name: '所有文件', extensions: ['*'] }
],
properties: ['openFile']
})

console.log(result)
}

返回结果:

{
canceled: false,
filePaths: ['/path/to/file.txt']
}

选择多个文件

const result = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections']
})

if (!result.canceled) {
result.filePaths.forEach(filePath => {
console.log('选中文件:', filePath)
})
}

选择目录

const result = await dialog.showOpenDialog({
title: '选择目录',
properties: ['openDirectory']
})

if (!result.canceled) {
console.log('选中目录:', result.filePaths[0])
}

配置选项

选项类型说明
titleString对话框标题
defaultPathString默认路径
buttonLabelString确认按钮文本
filtersArray文件类型过滤器
propertiesArray对话框属性
messageStringmacOS 上的额外消息

properties 选项

属性说明
openFile允许选择文件
openDirectory允许选择目录
multiSelections允许多选
showHiddenFiles显示隐藏文件
createDirectory允许创建目录
promptToCreate不存在时提示创建
noResolveAliases不解析别名
treatPackageAsDirectory将包视为目录

文件过滤器

filters: [
{ name: '图片', extensions: ['jpg', 'png', 'gif'] },
{ name: '文档', extensions: ['doc', 'docx', 'pdf'] },
{ name: '所有文件', extensions: ['*'] }
]

保存对话框

基本用法

const result = await dialog.showSaveDialog({
title: '保存文件',
defaultPath: 'untitled.txt',
buttonLabel: '保存',
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: '所有文件', extensions: ['*'] }
]
})

if (!result.canceled) {
console.log('保存路径:', result.filePath)
fs.writeFileSync(result.filePath, '文件内容')
}

返回结果

{
canceled: false,
filePath: '/path/to/save/file.txt'
}

配置选项

保存对话框支持与打开对话框相同的选项,以及额外的:

选项类型说明
showsTagFieldBooleanmacOS 上显示标签字段

消息对话框

基本用法

const result = await dialog.showMessageBox({
type: 'info',
title: '提示',
message: '操作已完成',
detail: '文件已成功保存',
buttons: ['确定']
})

console.log('点击了按钮:', result.response)

消息类型

类型说明
none无图标
info信息图标
error错误图标
question问号图标
warning警告图标

确认对话框

const result = await dialog.showMessageBox({
type: 'question',
title: '确认',
message: '确定要删除这个文件吗?',
buttons: ['取消', '删除'],
defaultId: 0,
cancelId: 0
})

if (result.response === 1) {
console.log('用户确认删除')
}

配置选项

选项类型说明
typeString消息类型
titleString标题
messageString主消息
detailString详细信息
buttonsArray按钮文本数组
defaultIdNumber默认选中按钮
cancelIdNumber按 ESC 时的按钮
checkboxLabelString复选框文本
checkboxCheckedBoolean复选框默认状态
iconNativeImage自定义图标
noLinkBoolean禁用链接样式

带复选框的消息框

const result = await dialog.showMessageBox({
type: 'info',
message: '是否记住这个选择?',
checkboxLabel: '不再提示',
checkboxChecked: false,
buttons: ['确定', '取消']
})

console.log('按钮:', result.response)
console.log('复选框:', result.checkboxChecked)

同步方法

如果不需要等待用户响应,可以使用同步方法:

const response = dialog.showMessageBoxSync({
type: 'warning',
message: '这是一个警告',
buttons: ['确定']
})

错误对话框

dialog.showErrorBox('错误标题', '发生了错误,请检查输入')

错误对话框是同步的,会阻塞应用直到用户关闭它。适合显示严重错误。

通过渲染进程调用

由于 dialog 模块只能在主进程使用,需要通过 IPC 从渲染进程调用。

主进程

const { ipcMain, dialog } = require('electron')

ipcMain.handle('dialog:openFile', async (event, options) => {
const result = await dialog.showOpenDialog(options)
return result
})

ipcMain.handle('dialog:saveFile', async (event, options) => {
const result = await dialog.showSaveDialog(options)
return result
})

ipcMain.handle('dialog:showMessage', async (event, options) => {
const result = await dialog.showMessageBox(options)
return result
})

预加载脚本

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
openFile: (options) => ipcRenderer.invoke('dialog:openFile', options),
saveFile: (options) => ipcRenderer.invoke('dialog:saveFile', options),
showMessage: (options) => ipcRenderer.invoke('dialog:showMessage', options)
})

渲染进程

const result = await window.electronAPI.openFile({
title: '选择文件',
filters: [{ name: '文本文件', extensions: ['txt'] }],
properties: ['openFile']
})

if (!result.canceled) {
console.log('选中文件:', result.filePaths[0])
}

完整示例

下面是一个文件编辑器的对话框使用示例:

main.js

const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('node:path')
const fs = require('node:fs')

let mainWindow
let currentFilePath = null
let isModified = false

function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

mainWindow.loadFile('index.html')

mainWindow.on('close', async (event) => {
if (isModified) {
event.preventDefault()
await confirmClose()
}
})
}

async function confirmClose() {
const result = await dialog.showMessageBox(mainWindow, {
type: 'question',
title: '保存更改',
message: '文件已修改,是否保存?',
buttons: ['保存', '不保存', '取消'],
defaultId: 0,
cancelId: 2
})

if (result.response === 0) {
await saveFile()
mainWindow.destroy()
} else if (result.response === 1) {
mainWindow.destroy()
}
}

async function openFile() {
const result = await dialog.showOpenDialog(mainWindow, {
title: '打开文件',
filters: [
{ name: '文本文件', extensions: ['txt', 'md'] },
{ name: '所有文件', extensions: ['*'] }
],
properties: ['openFile']
})

if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0]
const content = fs.readFileSync(filePath, 'utf-8')
currentFilePath = filePath
isModified = false
mainWindow.setRepresentedFilename(filePath)
mainWindow.setTitle(path.basename(filePath))
mainWindow.webContents.send('file-loaded', { filePath, content })
}
}

async function saveFile() {
if (!currentFilePath) {
return saveFileAs()
}

const content = await mainWindow.webContents.executeJavaScript('document.getElementById("editor").value')
fs.writeFileSync(currentFilePath, content)
isModified = false
mainWindow.setDocumentEdited(false)
}

async function saveFileAs() {
const result = await dialog.showSaveDialog(mainWindow, {
title: '保存文件',
defaultPath: currentFilePath || 'untitled.txt',
filters: [
{ name: '文本文件', extensions: ['txt'] },
{ name: '所有文件', extensions: ['*'] }
]
})

if (!result.canceled && result.filePath) {
const content = await mainWindow.webContents.executeJavaScript('document.getElementById("editor").value')
fs.writeFileSync(result.filePath, content)
currentFilePath = result.filePath
isModified = false
mainWindow.setRepresentedFilename(result.filePath)
mainWindow.setTitle(path.basename(result.filePath))
mainWindow.setDocumentEdited(false)
}
}

ipcMain.handle('file:open', openFile)
ipcMain.handle('file:save', saveFile)
ipcMain.handle('file:saveAs', saveFileAs)

ipcMain.on('content-changed', () => {
isModified = true
mainWindow.setDocumentEdited(true)
})

app.whenReady().then(createWindow)

平台差异

macOS

  • 对话框通常是 sheet 形式(附着在窗口上)
  • 支持 message 属性显示额外信息
  • 支持 showsTagField 显示标签字段

Windows

  • 对话框是独立的模态窗口
  • 支持 defaultPath 设置默认路径

Linux

  • 使用 GTK 对话框
  • 某些选项可能不被支持

最佳实践

提供合理的默认值

const result = await dialog.showSaveDialog({
defaultPath: path.join(app.getPath('documents'), 'untitled.txt')
})

使用合适的过滤器

filters: [
{ name: '支持的图片格式', extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp'] },
{ name: '所有文件', extensions: ['*'] }
]

处理取消操作

const result = await dialog.showOpenDialog()

if (result.canceled) {
return
}

// 处理选中的文件

关联窗口

将对话框关联到特定窗口,可以获得更好的用户体验:

const result = await dialog.showOpenDialog(mainWindow, options)

总结

对话框是桌面应用的重要交互方式:

  1. showOpenDialog 用于选择文件或目录
  2. showSaveDialog 用于选择保存位置
  3. showMessageBox 用于显示消息和确认
  4. showErrorBox 用于显示严重错误
  5. 通过 IPC 让渲染进程也能调用对话框