likes
comments
collection
share

Electron 中的原生菜单

作者站长头像
站长
· 阅读数 82

在 Electron 桌面应用中,有四类原生菜单:

  • 窗口菜单
  • 上下文菜单
  • 托盘菜单
  • Dock 菜单(Mac 平台特有)

菜单示例

接下来以 macOS 系统为例,描述四类原生菜单的使用场景:

  1. 窗口菜单是指左上角的下拉菜单:

Electron 中的原生菜单

  1. 上下文菜单类似于在 html 中通过右键触发的 contextmenu,由于是原生的,因此交互体验上比用 html 和 css 实现的菜单要好:

Electron 中的原生菜单

  1. 托盘菜单是点击任务栏的小图标弹出的下拉菜单:

Electron 中的原生菜单

  1. Dock 菜单是 macOS 专属的,当右击底部程序坞上的应用图标时会出现:

Electron 中的原生菜单

创建原生菜单

Electron 中的 Menu 模块封装了菜单相关的各种方法,它的类结构如下:

Electron 中的原生菜单

其中 buildFromTemplate用于创建原生菜单,该方法需要传入一个 MenuItem 对象数组,该对象的结构如下:

Electron 中的原生菜单

虽然有很多属性,但都是非必填的,最常用的就是以下几个:

  • 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 键就是一个修饰键:

Electron 中的原生菜单

常用的修饰键有:

  • 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)

此时窗口菜单的效果就出来了:

Electron 中的原生菜单

这里需要注意的是:在 Mac 系统下,第一个菜单的位置一定要留出来,因为 macOS 会自动设置第一个菜单,其菜单名与应用名相同。

Electron 中的原生菜单

因此,通过条件判断来为 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)

最后的效果如下:

Electron 中的原生菜单

上下文菜单

直接调用 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 上的效果:

Electron 中的原生菜单

在 Windows 上的效果:

Electron 中的原生菜单

托盘菜单

托盘 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 上的效果:

Electron 中的原生菜单

在 Windows 上的效果:

Electron 中的原生菜单

Dock菜单

dock 的菜单项也是通过 buildFromTemplate 来创建的,但是需要调用 Dock 模块提供的 dock.setMenu方法来设置 dock 栏的菜单:

const { dock } = app
const menu = Menu.buildFromTemplate(tpl)
dock.setMenu(menu)

Electron 中的原生菜单

转载自:https://juejin.cn/post/7209826725365645369
评论
请登录