Electron桌面应用开发:自定义菜单
完成初始应用的创建Electron桌面应用开发:创建应用,随后我们就可以自定义软件的菜单了。菜单可以帮助用户快速找到和执行命令,而不需要记住复杂的快捷键,通过将相关功能组织在一起,用户可以更容易地发现和使用应用程序的各种特性。同时菜单允许开发者提供更多的功能选项而不必担心界面会因此变得拥挤或难以导航,比如下拉菜单、弹出菜单等可以在有限的空间内提供大量的选项。
Electron的原始菜单为以下页面:

取消顶部菜单显示
这里可以使用两种常用的方法,第一种是在窗口创建函数中设置frame: false:
const {app, BrowserWindow, Menu} = require('electron');let win = null;function createWindow() {win = new BrowserWindow({width: 800,height: 600,frame: false,webPreferences: {nodeIntegration: true},});win.loadFile('index.html');win.on('closed', () => {win = null;});
}app.on('ready', createWindow);app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}
});
这样大小化和关闭按钮、标题也全部消失了,所以只适合个别情况使用。

第二种是在创建窗口函数中加入Menu.setApplicationMenu(null);设置,这样可以保留标题等内容:
const {app, BrowserWindow, Menu} = require('electron');let win = null;function createWindow() {win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true},});win.loadFile('index.html');Menu.setApplicationMenu(null);win.on('closed', () => {win = null;});
}app.on('ready', createWindow);app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}
});

自定义子菜单
这里我们可以在主文件main.js中直接添加自定义菜单的代码,也可以新建一个menu.js文件,随后在main.js中进行引用(个人推荐做法)
在Electron中定义菜单需要先引入Menu:
const {Menu} = require('electron')
定义格式如下:
const template = [{label: '菜单1',submenu: [{label: '子菜单1'},{label: '子菜单2',}]},{label: '菜单2',submenu: [{label: '子菜单1'},{label: '子菜单2',submenu: [{label: '孙子菜单1'},{label: '孙子菜单2'}]}]}]
可以使用click来监听事件,例如:
{label: '欧耶',accelerator: 'CmdOrCtrl+O',click: () => {console.log('菜单被点击了');}},
在main.js中直接添加菜单代码的格式如下:
const { app, BrowserWindow, Menu } = require('electron');let win = null;function createWindow() {win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true},});win.loadFile('index.html');win.on('closed', () => {win = null;});
}const template = [{label: '菜单1',submenu: [{label: '子菜单1'},{label: '子菜单2',}]},{label: '菜单2',submenu: [{label: '子菜单1'},{label: '子菜单2',submenu: [{label: '孙菜单1'},{label: '孙菜单2'}]}]},{label: '帮助',role: 'help',click() { require('electron').shell.openExternal('https://example.com/help') }}
];const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);app.on('ready', createWindow);app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}
});app.on('activate', () => {if (win === null) {createWindow();}
});
这里我们还是推荐使用将创建菜单的代码转移到其他文件中,比如新建一个menu.js,这样可以更加方便的进行代码编写和问题排查:
main.js:
const {app, BrowserWindow, Menu} = require('electron');
// 引入menu.js文件
const menuTemplate = require('./menu.js'); let win = null;function createWindow() {win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true},});win.loadFile('index.html');win.on('closed', () => {win = null;});// 创建窗口const menu = Menu.buildFromTemplate(menuTemplate(win));Menu.setApplicationMenu(menu);
}app.on('ready', createWindow);app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}
});
menu.js:
const {Menu} = require('electron')module.exports = function (win) {const template = [{label: '菜单1',submenu: [{label: '子菜单1'},{label: '子菜单2',}]},{label: '菜单2',submenu: [{label: '子菜单1'},{label: '子菜单2',submenu: [{label: '孙菜单1'},{label: '孙菜单2'}]}]},{label: '帮助',role: 'help',click() { require('electron').shell.openExternal('https://example.com/help') }}]return template;
}
两种方法的最终效果如下:

上下文菜单
上下文菜单通常在用户右键点击某个元素时显示,通常通过监听事件来创建和显示上下文菜单。
在主文件中需要导入ipcMain模块,main.js:
const { app, BrowserWindow, Menu, ipcMain } = require('electron');
const menuTemplate = require('./menu.js');let win = null;function createWindow() {win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true,contextIsolation: false,preload: __dirname + './preload.js'},});win.loadFile('index.html');const menu = Menu.buildFromTemplate(menuTemplate(win));Menu.setApplicationMenu(menu);win.on('closed', () => {win = null;});
}app.on('ready', createWindow);app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}
});app.on('activate', () => {if (win === null) {createWindow();}
});
renderer.js,DOM内容加载完成后执行的逻辑:
document.addEventListener('DOMContentLoaded', () => {
});
使用preload脚本处理上下文菜单的preload.js, 用于安全地暴露API给渲染进程:
const { contextBridge, ipcRenderer } = require('electron');contextBridge.exposeInMainWorld('electronAPI', {send: (channel, data) => {// 向主进程发送消息ipcRenderer.send(channel, data);},receive: (channel, func) => {// 接收来自主进程的消息ipcRenderer.on(channel, (event, ...args) => func(...args)); }
});
menu.js:
const {Menu} = require('electron')module.exports = function (win) {const template = [{label: '菜单1',submenu: [{label: '子菜单1'},{label: '子菜单2',}]},{label: '菜单2',submenu: [{label: '子菜单1'},{label: '子菜单2',submenu: [{label: '孙菜单1'},{label: '孙菜单2'}]}]},{label: '帮助',role: 'help',click() { require('electron').shell.openExternal('https://example.com/help') }}]return template;
}
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="./style.css"><title>GGBond勇猛无敌</title>
</head>
<body><h1>Hello GGBond</h1><script src="./renderer.js"></script>
</body>
</html>

弹出式菜单
弹出式菜单可以通过编程方式手动显示,不需要特定的触发事件。
main.js:
const { app, BrowserWindow, Menu } = require('electron');
const menuTemplate = require('./menu.js'); // 引入自定义菜单模板let win = null;function createWindow() {win = new BrowserWindow({width: 800,height: 600,webPreferences: {nodeIntegration: true,contextIsolation: false, // 在使用nodeIntegration时需要禁用contextIsolationpreload: __dirname + '/preload.js' // 使用preload脚本处理上下文菜单},});win.loadFile('index.html');win.on('closed', () => {win = null;});const menu = Menu.buildFromTemplate(menuTemplate(win));Menu.setApplicationMenu(menu); // 设置为应用菜单
}app.on('ready', createWindow);app.on('window-all-closed', () => {if (process.platform !== 'darwin') {app.quit();}
});app.on('activate', () => {if (win === null) {createWindow();}
});
使用preload脚本处理上下文菜单的preload.js,使用contextBridge来安全地暴露API给渲染进程,允许渲染进程调用showPopupMenu方法。:
const { contextBridge, ipcRenderer } = require('electron');contextBridge.exposeInMainWorld('electronAPI', {showPopupMenu: (x, y) => {ipcRenderer.invoke('show-popup-menu', x, y);}
});
renderer.js:
document.addEventListener('DOMContentLoaded', () => {document.body.addEventListener('click', (event) => {window.electronAPI.showPopupMenu(event.x, event.y);});
});
menu.js:
const {Menu} = require('electron')module.exports = function (win) {const template = [{label: '菜单1',submenu: [{label: '子菜单1'},{label: '子菜单2',}]},{label: '菜单2',submenu: [{label: '子菜单1'},{label: '子菜单2',submenu: [{label: '孙菜单1'},{label: '孙菜单2'}]}]},{label: '帮助',role: 'help',click() { require('electron').shell.openExternal('https://example.com/help') }}]return template;
}
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="./style.css"><title>GGBond勇猛无敌</title>
</head>
<body><h1>Hello GGBond</h1><script src="./src/renderer.js"></script>
</body>
</html>
相关文章:
Electron桌面应用开发:自定义菜单
完成初始应用的创建Electron桌面应用开发:创建应用,随后我们就可以自定义软件的菜单了。菜单可以帮助用户快速找到和执行命令,而不需要记住复杂的快捷键,通过将相关功能组织在一起,用户可以更容易地发现和使用应用程序…...
理解 JavaScript 中的浅拷贝与深拷贝
在 JavaScript 开发中,我们经常需要复制对象或数组。然而,复制的方式不同,可能会导致不同的结果。本文将详细介绍 浅拷贝 和 深拷贝 的概念、区别以及实现方式,帮助你更好地理解和使用它们。 1. 什么是浅拷贝? 定义 …...
【Java开发指南 | 第三十五篇】Maven + Tomcat Web应用程序搭建
读者可订阅专栏:Java开发指南 |【CSDN秋说】 文章目录 前言Maven Tomcat Web应用程序搭建1、使用Maven构建新项目2、单击项目,连续按两次shift键,输入"添加",选择"添加框架支持"3、选择Java Web程序4、点击&…...
从0到1入门Linux
一、常用命令 ls 列出目录内容 cd切换目录mkdir创建新目录rm删除文件或目录cp复制文件或目录mv移动或重命名文件和目录cat查看文件内容grep在文件中查找指定字符串ps查看当前进程状态top查看内存kill终止进程df -h查看磁盘空间存储情况iotop -o直接查看比较高的磁盘读写程序up…...
golang 从零单排 (一) 安装环境
1.下载安装 打开网址The Go Programming Language 直接点击下载go1.24.1.windows-amd64.msi 下载完成 直接双击下一步 下一步 安装完成 环境变量自动设置不必配置 2.验证 win r 输入cmd 打开命令行 输入go version...
如何下载和使用Git:初学者指南
🌟 如何下载和使用Git:初学者指南 在当今的软件开发中,Git已经成为不可或缺的版本控制系统。无论你是独立开发者还是团队成员,掌握Git的基本操作都能帮助你更高效地管理代码。今天,我将详细介绍如何下载和使用Git&…...
SQL_语法
1 数据库 1.1 新增 create database [if not exists] 数据库名; 1.2 删除 drop database [if exists] 数据库名; 1.3 查询 (1) 查看所有数据库 show databases; (2) 查看当前数据库下的所有表 show tables; 2 数据表 2.1 新增 (1) 创建表 create table [if not exists…...
基于Python实现的智能旅游推荐系统(Django)
基于Python实现的智能旅游推荐系统(Django) 开发语言:Python 数据库:MySQL所用到的知识:Django框架工具:pycharm、Navicat 系统功能实现 总体设计 系统实现 系统首页模块 统首页页面主要包括首页,旅游资讯,景点信息…...
安孚科技携手政府产业基金、高能时代发力固态电池,开辟南孚电池发展新赛道
安孚科技出手,发力固态电池。 3月7日晚间,安孚科技(603031.SH)发布公告称,公司控股子公司南孚电池拟与南平市绿色产业投资基金有限公司(下称“南平绿色产业基金”)、高能时代(广东横…...
p5.js:模拟 n个彩色小球在一个3D大球体内部弹跳
向 豆包 提问:编写一个 p5.js 脚本,模拟 42 个彩色小球在一个3D大球体内部弹跳。每个小球都应留下一条逐渐消失的轨迹。大球体应缓慢旋转,并显示透明的轮廓线。请确保实现适当的碰撞检测,使小球保持在球体内部。 cd p5-demo copy…...
Kali WebDAV 客户端工具——Cadaver 与 Davtest
1. 工具简介 在 WebDAV 服务器管理和安全测试过程中,Cadaver 和 Davtest 是两款常用的命令行工具。 Cadaver 是一个 Unix/Linux 命令行 WebDAV 客户端,主要用于远程文件管理,支持文件上传、下载、移动、复制、删除等操作。Davtest 则是一款…...
MySQL复习笔记
MySQL复习笔记 1.MySQL 1.1什么是数据库 数据库(DB, DataBase) 概念:数据仓库,软件,安装在操作系统(window、linux、mac…)之上 作用:存储数据,管理数据 1.2 数据库分类 关系型数据库&#…...
六十天前端强化训练之第十四天之深入理解JavaScript异步编程
欢迎来到编程星辰海的博客讲解 目录 一、异步编程的本质与必要性 1.1 单线程的JavaScript运行时 1.2 阻塞与非阻塞的微观区别 1.3 异步操作的性能代价 二、事件循环机制深度解析 2.1 浏览器环境的事件循环架构 核心组件详解: 2.2 执行顺序实战分析 2.3 Nod…...
集合论--形式化语言里的汇编码
如果一阶逻辑是数学这门形式化语言里的机器码,那么集合论就是数学这门形式化语言里的汇编码。 基本思想:从集合出发构建所有其它。 构建自然数构建整数构建有理数构建实数构建有序对、笛卡尔积、关系、函数、序列等构建确定有限自动机(DFA) 全景图 常…...
2025最新群智能优化算法:山羊优化算法(Goat Optimization Algorithm, GOA)求解23个经典函数测试集,MATLAB
一、山羊优化算法 山羊优化算法(Goat Optimization Algorithm, GOA)是2025年提出的一种新型生物启发式元启发式算法,灵感来源于山羊在恶劣和资源有限环境中的适应性行为。该算法旨在通过模拟山羊的觅食策略、移动模式和躲避寄生虫的能力&…...
MySQL数据实时同步至Elasticsearch的高效方案:Java实现+源码解析,一文搞定!
引言:为什么需要实时同步? MySQL擅长事务处理,而Elasticsearch(ES)则专注于搜索与分析。将MySQL数据实时同步到ES,可以充分发挥两者的优势,例如: 构建高性能搜索服务 实时数据分析…...
Spring-事务
Spring 事务 事务的基本概念 🔹 什么是事务? 事务是一组数据库操作,它们作为一个整体,要么全部成功,要么全部回滚。 常见的事务场景: 银行转账(扣款和存款必须同时成功) 订单系统…...
Git系列之git tag和ReleaseMilestone
以下是关于 Git Tag、Release 和 Milestone 的深度融合内容,并补充了关于 Git Tag 的所有命令、详细解释和指令实例,条理清晰,结合实际使用场景和案例。 1. Git Tag 1.1 定义 • Tag 是 Git 中用于标记特定提交(commit…...
考研机试常见基本题型
1、求100以内的素数 sqrt()函数在cmath头文件中。 #include <iostream> #include <cmath> using namespace std;int main() {int count 0; // 用于统计素数的个数// 遍历 100 到 200 之间的每一个数for (int num 100; num < 200; num) {bool isPrime true…...
Android AudioFlinger(四)—— 揭开PlaybackThread面纱
前言: 继上一篇Android AudioFlinger(三)—— AndroidAudio Flinger 之设备管理我们知道PlaybackThread继承自Re’fBase, 在被第一次引用的时候就会调用onFirstRef,实现如下: void AudioFlinger::Playbac…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
