Electron原生菜单

Electron 中可以使用html来开发其中展示的内容,一些菜单也可以以html的形式来绘制,点击时调用相关api即可,虽然构建方便,样式可任意调整。但其实际是模拟的菜单,并非应用原生的菜单。这种模拟的菜单有如下不足:

  • html模拟生成,非原生。
  • 每个功能均需自行代码调用或实现,而实际原生菜单有很多预设行为可直接使用。
  • Mac 和 Linux 中模拟的菜单无法显示到最上方的菜单栏中去。
  • html 模拟菜单无法生成系统托盘的菜单。
下面将介绍一下 Electron 中的原生菜单。
菜单类型 在 Electron 中有三种类型的菜单:应用菜单、上下文菜单及托盘菜单。
应用菜单 应用菜单即应用上方的那一条菜单。
构建菜单只需要一个数组即可,其中每个成员即为一个菜单项,每个菜单项均有一些指定的配置。以如下代码为例:
const myMenuTemplate = [ { // 设置菜单项文本 label: '文件', // 设置子菜单 submenu: [ { label: '关于 Electron', // 设置菜单角色 role: 'about', // about (关于),此值只针对 MacOS X 系统 // 点击事件 role 属性能识别时 点击事件无效 click: () => { var aboutWin = new BrowserWindow({ width: 300, height: 200, parent: win, modal: true }); aboutWin.loadFile('about.html'); } }, { // 设置菜单的类型是分隔栏 type: 'separator' }, { label: '关闭', // 设置菜单的热键 accelerator: 'Command+Q', click: () => { win.close(); } } ] }, { label: '编辑', submenu: [ { label: '复制', click: () => { win.webContents.insertText('复制'); } }, { label: '剪切', click: () => { win.webContents.insertText('剪切'); } }, { type: 'separator' }, { label: '查找', accelerator: 'Command+F', click: () => { win.webContents.insertText('查找'); } }, { label: '替换', accelerator: 'Command+R', click: () => { win.webContents.insertText('替换'); } } ] } ];

以上就是一个菜单的配置,常用的有如下配置:
  • id 可选,指定菜单的id,后续如需使用可直接通过id获取。
  • type 菜单类型,可选值 normalseparatorsubmenucheckboxradio
  • label 用于配置菜单上显示的文本。
  • click 菜单点击处理函数。
  • checked 是否已经勾选 ,仅对类型为 checkbox 或 radio 的菜单项有效。
  • accelerator 此菜单对应的快捷键。
  • icon 菜单项目上的图标。需特别注意,图标不会自动缩放,需提供合适大小图片,兼容高 DPI 设备需按照命名规范提供图标,参考高分辨率
  • role 指此菜单的一些系统预定义行为,如 复制、粘贴、最小化、最大化等,值系统可识别时,配置的点击事件将会无效。此配置的值在 Mac 下有更多的支持,具体可参考菜单角色。
更多菜单项目的配置可以参考 菜单项
要应用菜单,必须要使用 Menu 类,其定义放在 electron 命名空间下。 Menu 类有以下两个静态方法,用于生成并应用菜单
  • buildFromTemplate(menuTpl /* menu 菜单配置 */) 用来根据菜单配置生成菜单实例,返回值为菜单对象,可替换为 new Menu()
  • setApplicationMenu(menu /* 菜单对象 */) 方法将其作为应用菜单。
加载文件并应用菜单代码如下:
const electron = require('electron'); const app = electron.app; const BrowserWindow = electron.BrowserWindow; const Menu = electron.Menu; function createWindow() { win = new BrowserWindow({ file: 'index.html' }); win.loadFile('./index.html'); // 应用上面准备好的菜单配置生成 const template = myMenuTemplate; //创建菜单对象 const menu = Menu.buildFromTemplate(template); //设置应用菜单 Menu.setApplicationMenu(menu); win.on('closed', () => { console.log('closed'); win = null; }); } app.on('ready', createWindow); app.on('activate', () => { if (win === null) { createWindow(); } });

基于以上代码,window下可以看到如下效果:
Electron原生菜单
文章图片

Electron原生菜单
文章图片

Mac 下是如下效果:
Electron原生菜单
文章图片

Electron原生菜单
文章图片

window下我们自定义的关于效果:
Electron原生菜单
文章图片

而 Mac 下about直接由系统提供:
Electron原生菜单
文章图片

效果图
很明显, 在 window 下正如配置的样子,而 Mac 上却有明显不同,第一个文件菜单不见了,编辑里面又多了两个菜单项目。
这是 Mac 系统本身的特性,其中第一个菜单在 Mac 下的 文本显示必然是应用的名称,此处是以 electron 命令启动的所以是 electron 。可以理解为 Mac 下第一个菜单项配置 label 是无效的。(想想看你的 Mac 第一个菜单大多数都是应用的名称,如 Safri 、 PhotoShop CC)
如果你就是有强迫症,一定要改这个需修改应用程序包的 Info.plist 文件,请参考 About Information Property List Files
以上即为默认配置应用菜单的形式,不过在实际场景中经常还会有动态调整菜单的需求,如在某些状态下某个菜单不可用,进入某种状态时多一个菜单,此时我们需要能够调整菜单项目的可用性或可见性并提供动态新增菜单的能力。
有如下相关配置可调整菜单项的状态:
  • menuItem.enabled 布尔值标识菜单项是否启用该项。
  • menuItem.visible 布尔值标识菜单项是否可见。
  • menuItem.checked 布尔值标识菜单项是否选中该项。
获取菜单项可以使用菜单对象上的 getMenuItemById(id) 方法,此方法将返回对应的菜单项,对菜单项的相关属性进行调整即可。
新增菜单项目,可以先使用 new MenuItem() 构建一个菜单项,然调用菜单实例的如下方法进行插入:
  • menu.append(menuItem) 在菜单中添加一个菜单。
  • menu.insert(pos, menuItem) 在菜单中的指定位置添加一个新菜单项
windows下依次点击切换显示和切换启用效果如下:
Electron原生菜单
文章图片

新增效果如下:
Electron原生菜单
文章图片

mac 中切换和新增效果:
Electron原生菜单
文章图片

上下文菜单 上下文菜单即鼠标右键菜单,构建菜单的方式还是和上面应用菜单中讲到的方式一样,通过菜单项的配置数组来生成。不过使之成为上下文菜单不再需要使用 setApplicationMenu() 方法,仅需构建好菜单,然后监听 contextmenu 事件,调用 popup({x,y}) 在指定位置显示菜单即可。
不完整代码示例如下:
const menu = new Menu( /* 菜单配置数组*/ ); document.getElementById('panel').addEventListener('contextmenu', (ev) => { event.preventDefault(); const {x, y} = ev; //弹出上下文菜单 menu.popup({x, y}); return false; });

当在某种情况下需要关闭上下菜单时,仅需调用菜单对象的 colsePopup(browserWindow) 方法即可。如果应用涉及多窗口,需关闭其他窗口的上下文菜单时,可将此窗口作为参数传入即可,默认是操作当前窗口。
其余的地方和应用菜单完全相同。
托盘菜单 托盘菜单即window中右下角点击时的菜单,mac 上为右上方应用小图标点击的菜单。
不过默认应用是不会在托盘中显示的,如需显示,需要使用 Electron 中的 Tray 类来创建。
以如下演示代码即可创建托盘图标,并关联点击的托盘菜单。
const {app, Menu, Tray, BrowserWindow} = require('electron') let tray; let contextMenu; function createWindow() { win = new BrowserWindow({ file: 'index.html' }); win.loadFile('./index.html'); //创建 Tray 对象,并指定托盘图标 tray = new Tray('/images/tray.png'); //创建用于托盘图标的上下文菜单 contextMenu = Menu.buildFromTemplate([{ label: '复制', role: 'copy' }, { label: '剪切', role: 'cut' }, { label: '粘贴', role: 'paste' } ]); //设置托盘图标的提示文本 tray.setToolTip('这是Electron的应用托盘图标') //将托盘图标与上下文菜单关联 tray.setContextMenu(contextMenu) win.on('closed', () => { tray.destroy(); win = null; }); }app.on('ready', createWindow) app.on('activate', () => { if (win === null) { createWindow(); } });

关键操作说明如下:
  1. 使用 new Tray(image) 来创建应用托盘。
  2. 使用 setToolTip(toolTip) 为托盘添加 toolTip 。
  3. 配置菜单,并使用 setContextMenu 将菜单和托盘关联, 这一步是必须操作,托盘图标必须具备上下文菜单时才会展示
演示效果如下:
windows 中效果:
Electron原生菜单
文章图片

Mac 中:
Electron原生菜单
文章图片

关于系统托盘 Tray 的更说明和请参考官方文档 系统托盘
不同系统中交互不同的解决方案:
在windows中直接点击应用托盘是激活当前应用,右键点击才是展示托盘菜单。而 Mac 中通常左键点击即为展示托盘菜单,如果你需要你的应用在各个平台下均以有相同的交互方式,可手动使用代码进行调整。涉及如下几个事件和方法:
  • click 托盘单击时触发
  • right-click 托盘右键点击时触发
  • tray.popUpContextMenu([menu, position]) 弹出托盘图标的上下文菜单。如果传入了 menu 参数,将会弹出 menu 而不是托盘图标的上下文菜单,参数 position 只在 Windows 上可用, 并拥有默认值 (0, 0)
windows 的系统托盘的气泡通知
应用托盘在 windows 系统中有一个非常有用且常见的功能——气泡通知,仅需使用托盘的 displayBalloon() 方法即可,演示如下:
tray.displayBalloon({ // icon: './images/icon.png', // 通知的图标 (可选) title: '气泡通知标题', content: '气泡通知的内容' }); tray.on('balloon-show', () => { console.log('气泡通知显示'); }); //添加气泡消息单击事件 tray.on('balloon-click', () => { console.log('气泡通知被用户点击'); }); //添加气泡消息关闭事件 tray.on('balloon-closed', () => { console.log('气泡通自动关闭'); });

气泡通知还具备点击的功能,因此系统托盘还具备如下事件:
  • balloon-show ,当气泡消息显示时触发;
  • balloon-click ,当单击气泡消息时触发;
  • balloon-closed ,当气泡消息关闭时触发。
需注意的是:balloon-click 和 balloon-closed 是互斥的,两者仅会触发其中一个:单击气泡消息后,气泡消息会立刻关闭,在这种情况下,并不会触发 balloon-closed 事件;balloon-closed 事件仅在气泡消息显示几秒后自动关闭的情况下触发。
效果图如下:
Electron原生菜单
文章图片

Electron原生菜单
文章图片

【Electron原生菜单】以上就是 Electron 原生菜单常用的使用方法说明,想要了解更深入的内容请参考如下内容:
  • Electron
  • Menu
  • MenuItem
  • Tray
  • nativeImage

    推荐阅读