导语:近几年,随着 Electron/ NW.js 等技术的兴起,也催生了一批优秀的桌面端开发者工具,比如 VSCode、微信开发者工具、飞冰(ICE) 等等。对于开发者而言,桌面端开发者工具的优势是:可视化能力、操作系统层面的 API 访问、和良好的开发调试体验。因此,最近准备系统性的深入学习下 Electron 技术并且将学习的知识进行适当沉淀。本篇文章主要总结 Electron 的自定义菜单。
传统的 Web APP 的开发基本上不会涉及到菜单,但是在 Electron 里面它提供了对于菜单全面的控制,你可以通过 Menu、MenuItem 模块来创建应用所需的自定义菜单。这篇文章我们一起探讨下 Electron 中有哪些菜单种类,又是如何通过代码去自定义菜单的?
首先,我们一起看看基本的菜单介绍,方便大家对于基本的概念有初步的认识。
菜单介绍
Electron 里的菜单大体上分为三类:应用菜单、上下文菜单和 Dock 菜单(仅针对 OSX 系统)。
这里以微信开发者工具为例(微信开发者工具基于 NW.js 进行开发,主要出于 Windows XP兼容性考虑),来分别介绍这几种菜单的含义。打开微信开发者工具,可以通过下图,很清晰的发现3个菜单所处的位置。
这三种菜单的含义分别是:
- 应用菜单:应用菜单通常位于应用程序的顶部,提供了用户可能用到的各种操作,如程序的快捷方式、常用的文件夹及系统命令等。
- 上下文菜单:在应用里面点击右键看到的菜单。
- Dock 菜单:只在 OSX 系统才有,通常功能较少,提供特别常用的功能。
了解了菜单的基本概念后,接下来我们一起看看如何通过代码去实现自定义菜单的功能。
应用菜单
首先看看应用菜单,Electron 默认会有一个标准的应用菜单,我们一起看看默认的应用菜单效果:
仔细分析下默认应用菜单包括的菜单结构如下:
如果你希望定制应用菜单,你需要自行实现整个菜单的定义。这里需要注意,应用菜单只能在 Electron 的主进程中进行访问。例如:
代码语言:javascript复制// main.js
const {
app,
Menu
} = require('electron');
app.on('ready', () => {
const appMenu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(appMenu);
});
这里面重点关注 app 的 ready 这段代码块,应用菜单通过 Menu.setApplicationMenu 进行设置。接下来分别从菜单模板、分隔符、快捷键和子菜单几个方面来系统介绍下应用菜单的内容。
菜单模板:
菜单的 template 是一个对象数组,每个对象会定义一个独立的菜单,它会显示在应用菜单的 Bar 位置,显示的文字通过 label 属性进行定义。
以这段代码为例,我们定义了两个菜单,每个菜单都包含两个菜单项,菜单项就是我们点击菜单时下拉出来的内容。
代码语言:javascript复制const template = [
{
label: 'Edit App',
submenu: [
{
label: 'Undo'
},
{
label: 'Redo'
}
]
},
{
label: 'View App',
submenu: [
{
label: 'Reload'
},
{
label: 'Toggle Full Screen'
}
]
}
];
对应的效果:
这里值得注意的是:对于 OSX 而言,应用菜单的第一个菜单项是应用程序的名字,会使得 Edit App 这个菜单被覆盖掉。因此,我们需要针对 OSX 进行特殊处理,处理的过程通常是:
代码语言:javascript复制if (process.platform === 'darwin') {
template.unshift({
label: app.getName(),
submenu: [
{
label: 'Quit',
accelerator: 'CmdOrCtrl Q',
click() {
app.quit();
}
}
]
});
}
分隔符:
通过 type: 'separator' 可以在两个菜单项之间定义一个分隔符,分隔符的作用主要是将功能相似的菜单项分隔在一起,便于更好的操作。
代码语言:javascript复制const template = [
{
label: 'Edit App',
submenu: [
{
label: 'Undo'
},
{
type: 'separator'
},
{
label: 'Redo'
}
]
}
];
可以看到,Undo 和 Redo 之间出现了一个分隔符。
接下来,我们一起了解下常用的快捷键和内置的 role 功能。
快捷键:
快捷键我们日常开发过程中用得很多,比如 Ctrl A 全选,Ctrl C 复制,Ctrl V 粘贴。可以供我们选择的快捷键有:
- Command (简写Cmd)
- Control(简写Ctrl)
- CommandOrControl(简写CmdOrCtrl)
- Alt
- Option
- AltGr
- Shift
- Super
我们把上面的代码修改一下,增加快捷键,快捷键通过 accelerator 属性进行定义。
代码语言:javascript复制const template = [
{
label: 'Edit App',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl Z'
},
{
type: 'separator'
},
{
label: 'Redo',
accelerator: 'Shift CmdOrCtrl Z',
}
]
}
];
添加完快捷键后,可能你会问,点击某个菜单或者某个快捷键后如何触发相应的逻辑呢?这个可以通过编写 click() 自定义回调函数或者使用 Electron 内置的 role 进行指定。我们将上述代码继续修改:
代码语言:javascript复制const template = [
{
label: 'Edit App',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl Z',
role: 'undo'
},
{
type: 'separator'
},
{
label: 'Redo',
accelerator: 'Shift CmdOrCtrl Z',
role: 'redo'
}
]
}
];
增加了 role 之后可以发现就有对应的操作效果了,Electron 的所有内置的 role 如下:
- undo: 撤销
- redo:重做
- cut:剪切
- copy:复制
- paste:粘贴
- pasteAndMatchStyle
- selectAll:全选
- delete:删除
- minimize:当前窗口最小化
- close:关闭当前窗口
- quit:退出应用程序
- reload:刷新当前窗口
- forceReload:强制刷新当前窗口,忽略缓存
- toggleDevTools:打开或者关闭 devtool
- togglefullscreen:进行全屏切换
- resetZoom:重置窗口大小
- zoomIn:放大窗口的10%.
- zoomOut:缩小窗口的10%.
完整的 Role 可以查看:https://electronjs.org/docs/api/menu-item#roles
子菜单:
我们在前面的基础上增加一个新的菜单 Sub Menu,可以看到这个菜单里面的菜单项新增了 submenu 属性,通过这个属性可以继续定义子菜单,此处我们定义了 Submenu item1 和 Submenu item2。
代码语言:javascript复制const template = [
{
label: 'Edit App',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl Z',
role: 'undo'
},
{
type: 'separator'
},
{
label: 'Redo',
accelerator: 'Shift CmdOrCtrl Z',
role: 'redo'
}
]
},
{
label: 'Sub Menu',
submenu: [
{
label: 'Submenu item',
submenu: [
{
label: 'Submenu item1'
},
{
label: 'Submenu item2'
}
]
}
]
},
];
子菜单的效果如下:
到这里,应用菜单这个最重要的内容就介绍完了,接下来我们看看上下文菜单这个部分。
上下文菜单
上下文菜单(context menu)就是我们通常说的右键菜单,文章开头有展示效果。需要注意的是:上下文菜单,需要在渲染进程中进行实现。在渲染进程中是需要通过remote模块调用主进程中的模块。
实现上下文菜单很简单,只需要监听到 contextmenu 事件,然后将菜单展示出来即可。
代码语言:javascript复制//renderer.js
const { remote } = require('electron');
const { Menu } = remote;
const createContextMenu = () => {
const contextTemplate = [
{
label: 'Cut',
role: 'cut'
},
{
label: 'Copy',
role: 'copy'
}
];
const contextMenu = Menu.buildFromTemplate(contextTemplate);
return contextMenu;
}
window.addEventListener('contextmenu', (event) => {
event.preventDefault();
const contextMenu = createContextMenu();
contextMenu.popup({
window: remote.getCurrentWindow()
});
}, false);
Dock菜单
最后,我们一起看看 Dock 菜单,Dock 的菜单实现也是在主进程中,实现思路和前面基本类似,核心是通过 app.dock.setMenu 这个 API 进行实现的。
代码语言:javascript复制// main.js
const createDockMenu = () => {
const dockTempalte = [
{
label: 'New Window',
click () {
console.log('New Window');
}
}, {
label: 'New Window with Settings',
submenu: [
{ label: 'Basic' },
{ label: 'Pro' }
]
},
{
label: 'New Command...'
}
];
const dockMenu = Menu.buildFromTemplate(dockTempalte);
app.dock.setMenu(dockMenu);
}
app.on('ready', function() {
createDockMenu();
});
Dock 菜单的效果如下:
至此,这篇文章到这里就结束了,感谢您的阅读。后面的文章会涉及到对话框、 IPC 通信、Electron 应用的测试、打包、发布和自动更新等内容。