Electron 中的原生菜单
在 Electron 桌面应用中,有四类原生菜单:
- 窗口菜单
- 上下文菜单
- 托盘菜单
- Dock 菜单(Mac 平台特有)
菜单示例
接下来以 macOS 系统为例,描述四类原生菜单的使用场景:
- 窗口菜单是指左上角的下拉菜单:
- 上下文菜单类似于在 html 中通过右键触发的 contextmenu,由于是原生的,因此交互体验上比用 html 和 css 实现的菜单要好:
- 托盘菜单是点击任务栏的小图标弹出的下拉菜单:
- Dock 菜单是 macOS 专属的,当右击底部程序坞上的应用图标时会出现:
创建原生菜单
Electron 中的 Menu 模块封装了菜单相关的各种方法,它的类结构如下:
其中 buildFromTemplate
用于创建原生菜单,该方法需要传入一个 MenuItem 对象数组,该对象的结构如下:
虽然有很多属性,但都是非必填的,最常用的就是以下几个:
label
:菜单或菜单项标题click
:菜单项的点击事件type
:菜单类型,有 normal、separator、submenu、checkbox、radio 五种submenu
:定义子菜单role
:预定义的菜单项accelerator
:键盘加速键
可以看到,菜单本质上是一个树状结构,由 submenu 字段定义子菜单,下面是一段示例代码:
const tpl = [
{
label: '自定义菜单',
submenu: [
{
label: '点我试试',
click: () => {
console.log('try')
},
},
{
label: '默认选中',
type: 'checkbox',
checked: true,
},
{ type: 'separator' },
{
label: '单选菜单',
submenu: [
{ label: '选项1', type: 'radio' },
{ label: '选项2', type: 'radio' },
{ label: '选项3', type: 'radio' },
],
},
{
label: '多级菜单',
submenu: [
{
label: '二级菜单',
submenu: [{ label: '三级菜单', submenu: [{ label: '四级菜单' }] }],
},
],
},
],
},
{
label: '调试菜单',
submenu: [
{ role: 'toggleDevTools' },
{
role: 'reload',
accelerator: 'Shift+K',
click: () => {
console.log('reload')
},
},
],
},
]
虽然 MenuItem 里面大部分属性都通俗易懂,但也有一些比较难懂的属性,这里单独拿出来讲一下:
role 属性
role 属性之所以单独拎出来讲,因为它的取值非常多:
role?: ('undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'pasteAndMatchStyle' | 'delete' | 'selectAll' | 'reload' | 'forceReload' | 'toggleDevTools' | 'resetZoom' | 'zoomIn' | 'zoomOut' | 'toggleSpellChecker' | 'togglefullscreen' | 'window' | 'minimize' | 'close' | 'help' | 'about' | 'services' | 'hide' | 'hideOthers' | 'unhide' | 'quit' | 'startSpeaking' | 'stopSpeaking' | 'zoom' | 'front' | 'appMenu' | 'fileMenu' | 'editMenu' | 'viewMenu' | 'shareMenu' | 'recentDocuments' | 'toggleTabBar' | 'selectNextTab' | 'selectPreviousTab' | 'mergeAllWindows' | 'clearRecentDocuments' | 'moveTabToNewWindow' | 'windowMenu')
这些 role 都是 Electron 的预定义的菜单项,有固定的行为,可能会自动设置了 label、accelerator、click、submenu 等字段,如果有 role 字段,用户自己设置的 click 会被忽略掉,但是 label 和 accelerator 仍然会起作用。
accelerator 属性
accelerator 用于设置键盘加速键(俗称「快捷键」),它的定义如下:
加速键是一个由多个修饰符和单一键代码通过加号+组合而成的的字符串
其中修饰键是指键盘上的特殊键,用于改变其他键的默认行为,例如下图中的 Option 键就是一个修饰键:
常用的修饰键有:
Command
(缩写为Cmd
)Control
(缩写为Ctrl
)Alt
Option
Shift
Super
Meta
由于 macOS 上的 Command
和 Windows 上的 Control
键功能类似,所以 Electron 提供了一个 CommandOrControl
的虚拟修饰键(可以缩写为 CmdOrCtrl
),目的是为了方便开发者定义快捷键,Electron 会根据用户当前系统来自动判断。
下面都是合法的快捷键:
CmdOrCtrl+A
CmdOrCtrl+Shift+Z
Shift+Space
Option+Delete
使用原生菜单
定义好菜单结构,通过 buildFromTemplate
方法创建原生菜单对象,接下来就可以将其应用到不同的场景下了(下面示例中的 tpl 变量均指的是上文中的 MenuItem 数组):
窗口菜单
使用 Menu 类提供的静态方法 setApplicationMenu
来生成窗口菜单:
const menu = Menu.buildFromTemplate(tpl)
Menu.setApplicationMenu(menu)
此时窗口菜单的效果就出来了:
这里需要注意的是:在 Mac 系统下,第一个菜单的位置一定要留出来,因为 macOS 会自动设置第一个菜单,其菜单名与应用名相同。
因此,通过条件判断来为 Mac 系统添加一个空菜单即可:
if (process.platform === 'darwin') {
tpl.unshift({ label: '' })
}
另外,在 macOS 系统上,应用所有窗口都共享左上角的菜单,但是 Windows 和 Linux 系统是可以为 BrowserWindow 单独设置菜单的:
const menu = Menu.buildFromTemplate(tpl)
win.setMenu(menu)
const menu2 = Menu.buildFromTemplate(tpl2)
win2.setMenu(menu2)
最后的效果如下:
上下文菜单
直接调用 Menu 的 popup 方法即可,示例代码:
const menu = Menu.buildFromTemplate(tpl)
menu.popup({
// window: BrowserWindow.getFocusedWindow(),
// x: 10, // 相对于窗口的x轴偏移,默认是鼠标位置的横坐标
// y: 20, // 相对于窗口的y轴偏移,默认是鼠标位置的纵坐标
callback: () => {
console.log('menu closed')
},
})
popup 接收以下参数:
window
:指定窗口(默认是当前聚焦的窗口)x
:菜单位置横坐标(相对于窗口的 x 轴偏移,默认是鼠标位置的横坐标)y
:菜单位置纵坐标(相对于窗口的 y 轴偏移,默认是鼠标位置的纵坐标)callback
:菜单关闭回调函数
需要注意的是,Menu 相关的 API 要在主进程使用,建议采用 ipc 通信的方式通知主进程,不要用 remote 方法直接在渲染进程中调用,因为 click 事件响应函数会在页面刷新后失效。
在 macOS 上的效果:
在 Windows 上的效果:
托盘菜单
托盘 Tray 的对象提供了 setContextMenu
方法可以注册托盘菜单:
const { app, Menu, Tray } = require('electron')
let tray = null
app.whenReady().then(() => {
tray = new Tray('/path/to/my/icon')
const contextMenu = Menu.buildFromTemplate(tpl)
tray.setToolTip('This is my application.')
tray.setContextMenu(contextMenu)
})
在 macOS 上的效果:
在 Windows 上的效果:
Dock菜单
dock 的菜单项也是通过 buildFromTemplate 来创建的,但是需要调用 Dock 模块提供的 dock.setMenu
方法来设置 dock 栏的菜单:
const { dock } = app
const menu = Menu.buildFromTemplate(tpl)
dock.setMenu(menu)
转载自:https://juejin.cn/post/7209826725365645369