Skip to content

Electron - 定义快捷键

键盘快捷键通过快速访问应用程序功能,显著增强用户体验。Electron 的 globalShortcut 模块允许你注册全局键盘快捷键,即使你的应用程序窗口没有焦点,这些快捷键也能触发操作(尽管此行为可能因操作系统而异)。

关于 globalShortcut 的重要事项:

  • 它只能在 主进程 中使用。
  • 快捷键使用 Accelerator 字符串 定义(例如,'CommandOrControl+O'、'Shift+Alt+F1')。CommandOrControl 在 macOS 上自动映射到 Command,在 Windows/Linux 上映射到 Control。
  • 应用程序退出时,务必 取消注册 快捷键,使用 app.on('will-quit', () => { globalShortcut.unregisterAll(); }) 或取消注册特定快捷键,以防止它们在应用程序关闭后仍然生效。

如果全局快捷键需要触发渲染进程中的操作(例如,更新 UI),则主进程中处理快捷键的处理器应使用 webContents.send() 与相关的 BrowserWindow 通信。

我们来修改之前的文件打开示例。我们将注册 CommandOrControl+O 来触发文件打开对话框。

你的 main.js 文件将注册快捷键并处理操作:

const { app, BrowserWindow, globalShortcut, dialog, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs').promises;
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
mainWindow.loadFile('index.html');
mainWindow.on('closed', () => { mainWindow = null; });
}
async function openFileAndSendData() {
if (!mainWindow) return; // Ensure window exists
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
title: 'Open Text File via Shortcut',
properties: ['openFile'],
filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }]
});
if (canceled || filePaths.length === 0) {
mainWindow.webContents.send('file-data', { error: 'File open canceled.' });
return;
}
const filePath = filePaths[0];
try {
const content = await fs.readFile(filePath, 'utf-8');
mainWindow.webContents.send('file-data', { filePath, content });
} catch (error) {
console.error('Failed to read file:', error);
dialog.showErrorBox('Error Reading File', `Could not read file: ${error.message}`);
mainWindow.webContents.send('file-data', { error: `Error reading file: ${error.message}` });
}
}
app.whenReady().then(() => {
createWindow();
// Register the global shortcut
const ret = globalShortcut.register('CommandOrControl+O', () => {
console.log('CommandOrControl+O is pressed');
openFileAndSendData();
});
if (!ret) {
console.error('Failed to register global shortcut CommandOrControl+O');
}
// Check if a shortcut is registered
console.log('Is CommandOrControl+O registered?', globalShortcut.isRegistered('CommandOrControl+O'));
});
app.on('will-quit', () => {
// Unregister all shortcuts when the application is about to quit.
globalShortcut.unregisterAll();
console.log('All global shortcuts unregistered.');
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
// Optional: Allow triggering via IPC as well for a button
ipcMain.handle('trigger-open-file', async () => {
// This function is not directly called by the shortcut but can be used by UI elements
// The shortcut calls openFileAndSendData directly.
// For simplicity, we can reuse the logic if a button also needs this.
// However, the current example's shortcut directly sends data to renderer.
// This IPC handler would be for a button that wants the main process to initiate.
// For this example, we'll focus on the shortcut path.
});

你的 preload.js 文件将暴露一个供渲染进程使用的监听器:

const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
onFileData: (callback) => ipcRenderer.on('file-data', (_event, data) => callback(data)),
// To clean up listener if component unmounts (useful in frameworks)
removeOnFileData: () => ipcRenderer.removeAllListeners('file-data')
});

以及你的 index.html 文件,用于显示消息并监听数据:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Global Shortcuts</title>
<style>
body { font-family: sans-serif; margin: 20px; }
#fileInfo { margin-bottom: 10px; font-style: italic; }
#fileContent { white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; min-height: 100px; }
</style>
</head>
<body>
<h1>Global Shortcut Demo</h1>
<p>Press **CommandOrControl+O** to open a text file.</p>
<div id="fileInfo">No file opened yet via shortcut.</div>
<pre id="fileContent">File content will appear here.</pre>
<script>
const fileInfoDiv = document.getElementById('fileInfo');
const fileContentPre = document.getElementById('fileContent');
window.electronAPI.onFileData((data) => {
if (data.error) {
fileInfoDiv.textContent = `Error: ${data.error}`;
fileContentPre.textContent = '';
console.error('Received error from main:', data.error);
} else if (data.filePath && data.content !== undefined) {
fileInfoDiv.textContent = `Displaying: ${data.filePath}`;
fileContentPre.textContent = data.content;
} else {
// Fallback for unexpected data structure
fileInfoDiv.textContent = 'Received data, but format is unexpected.';
fileContentPre.textContent = JSON.stringify(data, null, 2);
}
});
// Optional: Clean up the listener when the window is about to be unloaded
// window.addEventListener('beforeunload', () => {
// window.electronAPI.removeOnFileData();
// });
</script>
</body>
</html>

运行应用程序(electron .)。现在,即使你点击应用程序窗口外部(如果操作系统允许),按下 Ctrl+O(或 macOS 上的 Cmd+O)应该会触发文件打开对话框。选择文件后,其内容将显示在应用程序窗口中。

常见的 Accelerator 修饰键:Command(仅 macOS)、Control(通常缩写为 Ctrl)、Alt、Shift、Super(Windows 键或 Command 键)。按键包括 A-Z、0-9、F1-F24,以及标点符号如 Plus、Space、Tab、方向键(Up、Down、Left、Right)、MediaNextTrack 等。请参考 Electron Accelerator 文档 获取完整列表。

如果你的应用程序有很多操作,提供用户可配置的快捷键是一个很好的实践,但对于常见的操作(如打开、保存、复制、粘贴),强烈建议使用标准的系统快捷键。