菜单
Electron 里的菜单大体上分为三类:
- 应用菜单:应用菜单通常位于应用程序的顶部,提供了用户可能用到的各种操作,如程序的快捷方式、常用的文件夹及系统命令等。
- 上下文菜单:在应用里面点击右键看到的菜单。
- Dock 菜单:只在 OSX 系统才有,通常功能较少,提供特别常用的功能
应用菜单
新建菜单
const menu = new Menu()
新建菜单项
const menuItem = new MenuItem({
label: '菜单项名',
click: handler,
enabled,
visible,
type: normal|separator|submenu|checkbox|radio,
role: copy|paste|cut|quit|...
})
添加菜单项
menu.append(new MenuItem({...}))
menu.append(new MenuItem({type: 'separator'}))
menu.append(new MenuItem({...}))
Electron 的所有内置的 role 如下:
- undo: 撤销
- redo:重做
- cut:剪切
- copy:复制
- paste:粘贴
- pasteAndMatchStyle
- selectAll:全选
- delete:删除
- minimize:当前窗口最小化
- close:关闭当前窗口
- quit:退出应用程序
- reload:刷新当前窗口
- forceReload:强制刷新当前窗口,忽略缓存
- toggleDevTools:打开或者关闭 devtool
- togglefullscreen:进行全屏切换
- resetZoom:重置窗口大小
- zoomIn:放大窗口的10%.
- zoomOut:缩小窗口的10%.
更多请参考: 菜单项 | Electron
新建menu.js
const { app, Menu } = require('electron')
const isMac = process.platform === 'darwin'
const template = [
// { role: 'appMenu' }
...(isMac ? [{
label: app.name,
// submenu 代表下一级菜单
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
// { role: 'fileMenu' }
{
label: 'File',
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
},
// { role: 'editMenu' }
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
},
// { role: 'viewMenu' }
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// { role: 'windowMenu' }
{
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' }
] : [
{ role: 'close' }
])
]
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
const { shell } = require('electron')
await shell.openExternal('https://electronjs.org')
}
}
]
}
]
// 从模板中创建菜单
const menu = Menu.buildFromTemplate(template)
// 设置为应用程序菜单
Menu.setApplicationMenu(menu)
main.js
const { app, BrowserWindow } = require("electron");
let win;
app.on("ready", () => {
win = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true, // 设置能在页面使用nodejs的API
contextIsolation: false, // 关闭警告信息
},
});
win.loadFile("./index.html");
win.webContents.openDevTools();
require("./menu");
});
index.html
<html>
<body>
<div>菜单</div>
<ol>
<li>应用菜单</li>
<li>上下文菜单</li>
<li>Dock菜单</li>
</ol>
</body>
</html>
运行npx electron main.js
注意的是:对于 OSX 而言,应用菜单的第一个菜单项是应用程序的名字,会使得 Edit App 这个菜单被覆盖掉。因此,我们需要针对 OSX 进行特殊处理,处理的过程通常是:
if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click() {
app.quit();
}
},
// ...
]
});
}
上下文菜单
上下文菜单(context menu)就是我们通常说的右键菜单。要创建由渲染器启动的菜单,请通过 IPC 发送所需的信息到主过程,并让主过程代替渲染器显示菜单。
renderer.js
//renderer.js
const { ipcRenderer } = require("electron");
window.addEventListener("contextmenu", (e) => {
e.preventDefault();
ipcRenderer.send("show-context-menu");
});
ipcRenderer.on("context-menu-command", (event, command) => {
// ...
console.log(event, command);
});
并且在index.html页面中引入将renderer.js
修改main.js
// ...
app.on("ready", () => {
// ...
handleIPC();
});
function handleIPC() {
ipcMain.on("show-context-menu", (event) => {
const contextTemplate = [
{
label: "Menu Item 1",
click: () => {
event.sender.send("context-menu-command", "menu-item-1");
},
},
{
label: "Cut",
role: "cut",
},
{
label: "Copy",
role: "copy",
},
];
const menu = Menu.buildFromTemplate(contextTemplate);
menu.popup(BrowserWindow.fromWebContents(event.sender));
});
}
运行main.js后,右键:
Dock菜单
修改main.js
const { app, BrowserWindow, ipcMain, Menu } = require("electron");
let win;
app.on("ready", () => {
// ...
createDockMenu();
});
const createDockMenu = () => {
const dockTempalte = [
{
label: "New Window",
click() {
console.log("New Window");
},
},
{
label: "New Window with Settings",
submenu: [{ label: "Basic" }, { label: "Pro" }],
},
{
label: "New Command...",
},
];
const dockMenu = Menu.buildFromTemplate(dockTempalte);
app.dock.setMenu(dockMenu);
};
运行后,右键icon
进程间通信
从渲染进程到主进程
Callback写法
- ipcRenderer.send(channel, ...args)
- ipcMain.on(channel, handler)
Promise写法
- ipcRenderer.invoke(channel, ...args)
- ipcMain.handle(channel, handler)
index.html
<script src="renderer.js"></script>
main.js
const { app, BrowserWindow, ipcMain } = require("electron");
let win;
app.on("ready", () => {
win = new BrowserWindow({
width: 300,
height: 300,
webPreferences: {
nodeIntegration: true, //设置能在页面使用nodejs的API
contextIsolation: false, //关闭警告信息
},
});
win.loadFile("./index.html");
handleIPC();
});
function handleIPC() {
ipcMain.on("asynchronous-message", (event, arg) => {
console.log(arg); // asynchronous ping
event.reply("asynchronous-reply", "asynchronous pong");
});
ipcMain.on("synchronous-message", (event, arg) => {
console.log(arg); // synchronous ping
event.returnValue = "synchronous pong";
});
ipcMain.handle("my-invokable-ipc", async (event, ...args) => {
console.log(...args);
});
}
renderer.js
const { ipcRenderer } = require("electron");
console.log(ipcRenderer.sendSync("synchronous-message", "synchronous ping")); // synchronous pong
ipcRenderer.on("asynchronous-reply", (event, arg) => {
console.log(arg); // asynchronous pong
});
ipcRenderer.send("asynchronous-message", "asynchronous ping");
ipcRenderer.invoke("my-invokable-ipc", 1, 2);
// 渲染进程
async function hanldeInvoke() {
const result = await ipcRenderer.invoke("my-invokable-ipc", 1, 2);
// ...
}
hanldeInvoke();
运行npx electron main.js
从主进程到渲染进程
主进程通知渲染进程,因为只有一个主进程,可能有多个渲染进程,那么就需要找到具体的窗体内容, 主进程用 window 的 webContent 对象与网页内容进行交互
- ipcRenderer.on(channel, handler)
- webContents.send(channel)
main.js
const { app, BrowserWindow } = require("electron");
let win;
app.on("ready", () => {
win = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true, //设置能在页面使用nodejs的API
contextIsolation: false, //关闭警告信息
},
});
win.loadFile("./index.html");
handleIPC();
});
function handleIPC() {
win.webContents.send('do-some-render-work');
}
renderer.js
const { ipcRenderer } = require("electron");
ipcRenderer.on('do-some-render-work', () => {
alert('do some work');
})
运行npx electron main.js
页面间(渲染进程与渲染进程间)通信
通知事件
- 利用主进程作为中转站
- ipcRenderer.sendTo(webContentsId, channel, ...args)
数据共享
- Web技术(localStorage、sessionStorage、indexedDB)
- 使用remote(14.0.0版本后被废弃,改成@electron/remote)
利用主进程作消息中转
main.js
const { app, BrowserWindow, ipcMain } = require("electron");
let win, win2;
app.on("ready", () => {
win = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true, //设置能在页面使用nodejs的API
contextIsolation: false, //关闭警告信息
},
});
win.loadFile("./index.html");
win2 = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true, //设置能在页面使用nodejs的API
contextIsolation: false, //关闭警告信息
},
});
win2.loadFile("./index2.html");
handleIPC();
});
function handleIPC() {
ipcMain.on("message1", (event, arg) => {
win2.webContents.send("do-some-work", arg);
});
}
index2.html
<script src="renderer2.js"></script>
renderer.js
const { ipcRenderer } = require("electron");
ipcRenderer.send("message1", "hello!");
renderer2.js
const { ipcRenderer } = require("electron");
ipcRenderer.on('do-some-work', (event, arg) => {
console.log('renderer2 handle ' + arg);
})
利用ipcRenderer.sendTo
先安装@electron/remote
main.js
const { app, BrowserWindow, ipcMain } = require("electron");
// 初始化
require("@electron/remote/main").initialize();
let win, win2;
app.on("ready", () => {
win = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true, //设置能在页面使用nodejs的API
contextIsolation: false, //关闭警告信息
},
});
win.loadFile("./index.html");
require("@electron/remote/main").enable(win.webContents);
win2 = new BrowserWindow({
width: 600,
height: 600,
webPreferences: {
nodeIntegration: true, //设置能在页面使用nodejs的API
contextIsolation: false, //关闭警告信息
},
});
win2.loadFile("./index2.html");
global.sharedObject = {
win2WebContentsId: win2.webContents.id,
};
});
renderer.js
const { ipcRenderer } = require("electron");
const remote = require('@electron/remote');
// 利用remote 、sendTo
let sharedObject = remote.getGlobal("sharedObject");
let win2WebContentsId = sharedObject.win2WebContentsId;
ipcRenderer.sendTo(win2WebContentsId, "do-some-work", "hello!");
renderer2.js
const { ipcRenderer } = require("electron");
ipcRenderer.on("do-some-work", (event, arg) => {
console.log("renderer2 handle " + arg);
});
利用本地存储进行通信
renderer.js
window.onload = function () {
localStorage.setItem("message", "hello!");
};
renderer2.js
window.onload = function () {
var msg = localStorage.getItem("message");
alert(msg);
};
参考资料: