在 Electron 中创建自定义菜单栏
(原文发表于我的博客)
想让你的 Electron 应用的菜单栏看起来更酷炫吗?让我们来看看如何创建一个类似 Slack 菜单栏的自定义菜单栏。
先决条件
ElectronJS 基础知识。请查看本教程开始学习。
资源
完整的代码可在https://github.com/saisandeepvaddi/electron-custom-menu-bar获取。
我们将建造什么
这就是我们完工后的样子。
设置 Electron 项目
根据 Electron 官方快速入门 GitHub 仓库搭建一个最小的 Electron 应用。
# Clone the Quick Start repository
$ git clone https://github.com/electron/electron-quick-start
# Go into the repository
$ cd electron-quick-start
# Install the dependencies and run
$ npm install && npm start
主进程代码
首次运行时,npm start您会看到一个带有默认菜单栏的窗口。要将其替换为我们自己的菜单栏,我们需要做两件事。在main.js我们拥有的文件中,
frame: false在options对象中设置new BrowserWindow({frame: false, ...other-options})。这将创建一个没有工具栏、边框等的窗口。有关更多详细信息,请查看无边框窗口。- 注册一个事件监听器,
ipcMain当鼠标点击汉堡图标时,该监听器会接收鼠标点击位置。
// main.js
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, "preload.js")
// (NOT RECOMMENDED)
// If true, we can skip attaching functions from ./menu-functions.js to window object in preload.js.
// And, instead, we can use electron APIs directly in renderer.js
// From Electron v5, nodeIntegration is set to false by default. And it is recommended to use preload.js to get access to only required Node.js apis.
// nodeIntegration: true
},
frame: false //Remove frame to hide default menu
});
// ...other stuff
}
// Register an event listener. When ipcRenderer sends mouse click co-ordinates, show menu at that position.
ipcMain.on(`display-app-menu`, function(e, args) {
if (isWindows && mainWindow) {
menu.popup({
window: mainWindow,
x: args.x,
y: args.y
});
}
});
// ... other stuff.
创建一个名为 `<filename>` 的文件menu-functions.js,并在其中定义以下函数。这里的所有函数都接收 ElectronBrowserWindow对象(mainWindow在本应用中),并执行最小化、最大化、关闭和打开菜单等操作,这些操作需要从我们自定义的菜单栏触发。
// menu-functions.js
const { remote, ipcRenderer } = require("electron");
function getCurrentWindow() {
return remote.getCurrentWindow();
}
function openMenu(x, y) {
ipcRenderer.send(`display-app-menu`, { x, y });
}
function minimizeWindow(browserWindow = getCurrentWindow()) {
if (browserWindow.minimizable) {
// browserWindow.isMinimizable() for old electron versions
browserWindow.minimize();
}
}
function maximizeWindow(browserWindow = getCurrentWindow()) {
if (browserWindow.maximizable) {
// browserWindow.isMaximizable() for old electron versions
browserWindow.maximize();
}
}
function unmaximizeWindow(browserWindow = getCurrentWindow()) {
browserWindow.unmaximize();
}
function maxUnmaxWindow(browserWindow = getCurrentWindow()) {
if (browserWindow.isMaximized()) {
browserWindow.unmaximize();
} else {
browserWindow.maximize();
}
}
function closeWindow(browserWindow = getCurrentWindow()) {
browserWindow.close();
}
function isWindowMaximized(browserWindow = getCurrentWindow()) {
return browserWindow.isMaximized();
}
module.exports = {
getCurrentWindow,
openMenu,
minimizeWindow,
maximizeWindow,
unmaximizeWindow,
maxUnmaxWindow,
isWindowMaximized,
closeWindow,
};
我们需要将这些函数附加到window对象上,以便在渲染进程中使用。如果您使用的是旧版本的 Electron(<5.0.0)或者您nodeIntegration: true在BrowserWindow`s` 选项中进行了设置,则可以menu-functions.js在渲染进程中直接使用上述文件。出于安全考虑false,Electron 新版本默认已启用此功能。
// preload.js
const { remote } = require("electron");
const {
getCurrentWindow,
openMenu,
minimizeWindow,
unmaximizeWindow,
maxUnmaxWindow,
isWindowMaximized,
closeWindow,
} = require("./menu-functions");
window.addEventListener("DOMContentLoaded", () => {
window.getCurrentWindow = getCurrentWindow;
window.openMenu = openMenu;
window.minimizeWindow = minimizeWindow;
window.unmaximizeWindow = unmaximizeWindow;
window.maxUnmaxWindow = maxUnmaxWindow;
window.isWindowMaximized = isWindowMaximized;
window.closeWindow = closeWindow;
});
现在我们需要一个菜单。请在新文件中创建一个简单的菜单。您可以在官方文档menu.js中了解如何向菜单中添加自定义选项。Electron 提供了一些易于理解且包含示例的文档。
// menu.js
const { app, Menu } = require("electron");
const isMac = process.platform === "darwin";
const template = [
{
label: "File",
submenu: [isMac ? { role: "close" } : { role: "quit" }],
},
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
module.exports = {
menu,
};
主流程部分已经完成。现在,让我们来构建自定义菜单栏。如果您查看图片中的菜单,您会看到我们的菜单栏上有这些选项。
- 左侧有一个汉堡图标,点击即可打开菜单。
- 右侧有最小化按钮、最大化/取消最大化按钮和关闭按钮。
我使用了来自fontawesome.com的 Font Awesome JS 文件来添加图标。请将其添加到 HTML 的 `<head>`<head>标签中。Content-Security-Policy为了暂时让 Font Awesome JS 文件运行,我移除了 meta 标签。在生产环境中,请确保正确配置代码的运行权限。更多详情请查看内容安全策略 (CSP) 。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<title>My Awesome App</title>
<link rel="stylesheet" href="style.css" />
<script src="https://kit.fontawesome.com/1c9144b004.js" crossorigin="anonymous"></script>
</head>
</head>
<body>
<div id="menu-bar">
<div class="left" role="menu">
<button class="menubar-btn" id="menu-btn"><i class="fas fa-bars"></i></button>
<h5>My Awesome App</h5>
</div>
<div class="right">
<button class="menubar-btn" id="minimize-btn"><i class="fas fa-window-minimize"></i></button>
<button class="menubar-btn" id="max-unmax-btn"><i class="far fa-square"></i></button>
<button class="menubar-btn" id="close-btn"><i class="fas fa-times"></i></button>
</div>
</div>
<div class="container">
Hello there!
</div>
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
/* style.css */
body {
padding: 0;
margin: 0;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
color: white;
}
#menu-bar {
display: flex;
justify-content: space-between;
align-items: center;
height: 30px;
background: #34475a;
-webkit-app-region: drag;
}
#menu-bar > div {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.menubar-btn {
-webkit-app-region: no-drag;
}
.container {
height: calc(100vh - 30px);
background: #34475ab0;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
}
button {
height: 100%;
padding: 0 15px;
border: none;
background: transparent;
outline: none;
}
button:hover {
background: rgba(221, 221, 221, 0.2);
}
#close-btn:hover {
background: rgb(255, 0, 0);
}
button i {
color: white;
}
现在你的窗户应该看起来像这样。太棒了!我们快完成了。
如果你猜对了,菜单栏里的按钮一个都没反应。因为我们没有给onclick它们添加事件监听器。现在就来添加。还记得我们window之前给对象添加了一些实用函数preload.js吗?我们会在按钮点击事件监听器里用到它们。
// renderer.js
window.addEventListener("DOMContentLoaded", () => {
const menuButton = document.getElementById("menu-btn");
const minimizeButton = document.getElementById("minimize-btn");
const maxUnmaxButton = document.getElementById("max-unmax-btn");
const closeButton = document.getElementById("close-btn");
menuButton.addEventListener("click", e => {
// Opens menu at (x,y) coordinates of mouse click on the hamburger icon.
window.openMenu(e.x, e.y);
});
minimizeButton.addEventListener("click", e => {
window.minimizeWindow();
});
maxUnmaxButton.addEventListener("click", e => {
const icon = maxUnmaxButton.querySelector("i.far");
window.maxUnmaxWindow();
// Change the middle maximize-unmaximize icons.
if (window.isWindowMaximized()) {
icon.classList.remove("fa-square");
icon.classList.add("fa-clone");
} else {
icon.classList.add("fa-square");
icon.classList.remove("fa-clone");
}
});
closeButton.addEventListener("click", e => {
window.closeWindow();
});
});
就这样。重启应用后npm run start,新的菜单栏按钮应该就能正常工作了。
注意:为了简洁起见,以上脚本中省略了部分代码。您可以在https://github.com/saisandeepvaddi/electron-custom-menu-bar获取完整代码。
如果你想查看一个功能更丰富的 Electron 应用,可以看看https://github.com/saisandeepvaddi/ten-hands这个应用,它使用了类似风格的菜单栏(不过目前自定义风格的菜单栏仅在 Windows 系统上可见),但它是用 React 和 TypeScript 构建的。我就是在使用了这个菜单栏之后才写这篇教程的。
谢谢。🙏
文章来源:https://dev.to/saisandeepvaddi/creating-a-custom-menu-bar-in-electron-1pi3



