在很多桌面应用程序的顶部都有一个菜单栏,把一些功能入口放到菜单栏的菜单中可以使软件界面更简洁。Electron 可以支持原生菜单栏,也可以用 HTML 来制作菜单栏,不过原生菜单栏在键盘操作和可访问性方面都要更好。

禁用菜单

Electron 默认会显示一个菜单栏,其中包含了退出、刷新、打开控制台 等功能,如果不需要菜单栏的话可以禁用,如下:

const {Menu} = require('electron');  // 引入 Menu 模块
Menu.setApplicationMenu(null);

菜单栏需要在 main 主进程中配置。

制作菜单模板

为了方便管理可以单独创建一个菜单模板文件来编写菜单栏,下面是一个简单的菜单栏:

const {Menu} = require('electron');  // 引入 Menu 模块

// 菜单栏模板
const menuBar = [
  {
    label: '文件',
    submenu: [
      {label: '打开'},
      {label: '保存'},
      {label: '退出'}
    ]
  },
  {
    label: '帮助',
    submenu: [
      {label: '访问官网'},
      {label: '关于'}
    ]
  }
];

// 构建菜单项
const menu = Menu.buildFromTemplate(menuBar);
// 设置一个顶部菜单栏
Menu.setApplicationMenu(menu);

菜单模板使用对象和数组,其中 label 是菜单项名称,submenu 是子菜单。

main 主进程的 ready 事件中使用 require 引入菜单模板文件就可以看到菜单栏了。

上面只是编写了菜单模板,下面就给菜单添加点击事件:

const {Menu, app} = require('electron');

// 菜单栏模板
const menuBar = [
  {
    label: '文件',
    submenu: [
      {label: '打开'},
      {label: '保存'},
      {
        label: '退出',
        click() {
          // 退出程序
          app.quit();
        }
      }
    ]
  }
];

// 构建菜单项
const menu = Menu.buildFromTemplate(menuBar);
// 设置一个顶部菜单栏
Menu.setApplicationMenu(menu);

可以直接在菜单项中写 click 函数。

Electron 的菜单配置是写在 main 主进程中的,如果需要通过菜单控制渲染进程可以使用 BrowserWindowwebContents.send 给渲染进程发送指令。

绑定快捷键

在很多文件和编辑菜单的名称后面应该都可以看到 ctrl + octrl + s 的快捷键提示,Electron 也可以给菜单绑定快捷键,只需要按快捷键就可以触发菜单的点击事件。

菜单项可以使用 accelerator 属性来设置快捷键,如下:

// 菜单栏模板
const menuBar = [
  {
    label: '文件',
    submenu: [
      {
        label: '打开',
        accelerator: 'ctrl+o'
      },
      {
        label: '保存',
        accelerator: 'ctrl+s'
      },
      {
        label: '退出',
        accelerator: 'ctrl+w',
        click() {
          // 退出程序
          app.quit();
        }
      }
    ]
  }
];

打开菜单也可以看到快捷键提示,如下:

Electron包含快捷键提示的菜单

单选和多选菜单

一些快捷设置的菜单可能会需要用到 checkbox 多选和 radio 单选,例如下面的 Windows 记事本的格式菜单就用到了单选功能:

windows记事本格式菜单

Electron 的菜单也支持多选和单选,把菜单项的 type 属性设置为 checkbox 就是多选,如下:

// 菜单栏模板
const menuBar = [
  {
    label: '查看',
    submenu: [
      {
        label: '显示工具栏',
        type: 'checkbox',
        checked: true,
        click(ev) {
          // 输出选中状态
          console.log(ev.checked ? '已选中' : '未选中');
        }
      },
      {
        label: '显示状态栏',
        type: 'checkbox',
        checked: false,
        click(ev) {
          // 输出选中状态
          console.log(ev.checked ? '已选中' : '未选中');
        }
      }
    ]
  }
];

checked 属性可以设置菜单的选中状态,点击菜单时 checked 的状态会改变,click 函数可以接收一个 event ,通过 event 对象可获取菜单项的各种属性,包括 checked 选中状态。

Electron 的多选菜单如下:

Electron多选菜单

把菜单项的 type 设置为 radio 就是单选,如下:

// 菜单栏模板
const menuBar = [
  {
    label: '主题配色风格',
    submenu: [
      {
        label: '亮色',
        type: 'radio',
        checked: true,
        click(ev) {
          if (ev.checked) console.log('Light');
        }
      },
      {
        label: '暗色',
        type: 'radio',
        click(ev) {
          if (ev.checked) console.log('Dark');
        }
      },
      {
        label: '高对比度',
        type: 'radio',
        click(ev) {
          if (ev.checked) console.log('High contrast');
        }
      }
    ]
  }
];

一组菜单只有一项可以选中,点击其中的一项其他项都会取消选中,单选菜单也是通过 checked 来设置选中状态,通过 eventchecked 也能获取选中状态。

单选菜单的效果如下:

Electron单选菜单

分隔线

一组相似功能的菜单项之间可以用分隔线分隔,把菜单项的 type 属性设置为 separator 就可以显示分隔线了,如下:

// 菜单栏模板
const menuBar = [
  {
    label: '文件',
    submenu: [
      {label: '打开'},
      {label: '保存'},
      {type: 'separator'},
      {label: '退出'}
    ]
  }
];

效果如下:

Electron包含分隔线的菜单

快捷键展开菜单

很多菜单栏的菜单展开按钮的名称后面都可以看到一个字母提示,如下:

VSCode菜单栏

直接按 Alt + F 就能展开文件菜单、按 Alt + E 就能展开编辑菜单。

Electron 也可以通过快捷键展开菜单,只需要在顶层的 label 属性的字母前加 & 就能通过 Alt + label 首字母 展开菜单。

如下:

// 菜单栏模板
const menuBar = [
  {
    label: '&File',
    submenu: [
      {label: 'Open'},
      {label: '保存'},
      {label: '退出'}
    ]
  },
  {
    label: '&Edit',
    submenu: [
      {label: '复制'},
      {label: '粘贴'}
    ]
  }
];

通过 Alt + F 就能展开 File 菜单、通过 Alt + E 就能展开 Edit 菜单,字母前的 & 是不会显示的,如果需要提示的话可以在后面加 (F)

通过这种方法只能展开英文的菜单,中文的方法目前还没有找到合适的,即便在中文前面加 &F 其中的 F 也会显示出来。

动态增加菜单项

有时候可能会需要动态增加菜单项,例如有的文件菜单会有一个子菜单叫 最近打开的文件 ,其中的内容就是动态增加的,不是固定的。

动态增加菜单还需要引入 MenuItem 模块,下面创建一个菜单,然后给 最近打开的文件 动态插入子菜单:

const {Menu, MenuItem} = require('electron');

// 菜单栏模板
const menuBar = [
  {
    label: '文件',
    submenu: [
      {label: '打开'},
      {label: '保存'},
      {
        label: '最近打开的文件',
        id: 'fileList',
        submenu: []
      },
      {label: '退出'}
    ]
  }
];

// 构建菜单项
const menu = Menu.buildFromTemplate(menuBar);
// 设置一个顶部菜单栏
Menu.setApplicationMenu(menu);

// 动态创建的菜单模板
const fileItem = new MenuItem({
  label: 'file1.txt'
});
// 获取 id 为 fileList 的菜单,然后把菜单添加到子菜单中
menu.getMenuItemById('fileList').submenu.append(fileItem);
// 重新设置顶部菜单栏
Menu.setApplicationMenu(menu);

下面是代码说明:

  1. 使用菜单模板创建了一个菜单,其中 最近打开的文件 的子菜单为空,并且设置了一个 id。
  2. 使用 new MenuItem 创建了一个菜单项。
  3. 通过 getMenuItemById ID 选择器获取了之前创建的 最近打开的文件 ,然后给 submenu 子菜单插入了一个菜单项。
  4. 使用 Menu.setApplicationMenu 重新设置顶部菜单栏。

动态增加的菜单如下:

Electron动态插入菜单项

role 属性

在 HTML 中 role 属性可以给屏幕阅读器之类的辅助软件提供可访问性方面的支持,在 Electron 菜单中 role 属性包含了一些预定义功能,有些功能使用 role 就可以实现,无需在 click 中手动编写。

下面是一组包含 role 属性的菜单:

// 菜单栏模板
const menuBar = [
  {
    label: '预定义功能',
    submenu: [
      {
        label: '打开开发者工具',
        role: 'toggledevtools'
      },
      {
        label: '全屏',
        role: 'togglefullscreen'
      },
      {
        label: '重新加载',
        role: 'reload'
      },
      {
        label: '退出',
        role: 'quit'
      }
    ]
  }
];

上面的功能都无需编写 click ,添加 role 属性就能直接实现。

完整的 role 属性说明可以访问:https://www.electronjs.org/docs/api/menu-item