.markdown-body { color: rgba(56, 56, 56, 1); font-size: 15px; line-height: 30px; letter-spacing: 2px; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, sans-serif; scroll-behavior: smooth; background-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 24%, rgba(201, 195, 195, 0.33) 25%, rgba(209, 201, 201, 0.05) 26%, rgba(0, 0, 0, 0) 27%, rgba(0, 0, 0, 0) 74%, rgba(209, 204, 204, 0.18) 75%, rgba(180, 176, 176, 0.05) 76%, rgba(0, 0, 0, 0) 77%, rgba(0, 0, 0, 0)), linear-gradient(90deg, rgba(0, 0, 0, 0) 24%, rgba(204, 196, 196, 0.22) 25%, rgba(172, 165, 165, 0.05) 26%, rgba(0, 0, 0, 0) 27%, rgba(0, 0, 0, 0) 74%, rgba(209, 204, 204, 0.18) 75%, rgba(180, 176, 176, 0.05) 76%, rgba(0, 0, 0, 0) 77%, rgba(0, 0, 0, 0)); background-color: rgba(255, 255, 255, 1); background-size: 50px 50px; padding-bottom: 60px }
.markdown-body ::selection { color: rgba(255, 255, 255, 1); background-color: rgba(168, 98, 234, 1) }
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin: 24px 0 12px; color: rgba(168, 98, 234, 1) }
.markdown-body h1 { line-height: 2; font-size: 1.4em }
.markdown-body h1~p:first-of-type:first-letter { color: rgba(168, 98, 234, 1); float: left; font-size: 2em; margin-right: 0.4em; font-weight: bolder }
.markdown-body h2 { font-size: 1.2em }
.markdown-body h3 { font-size: 1.1em }
.markdown-body ol, .markdown-body ul { padding-left: 2em }
.markdown-body ol li, .markdown-body ul li { margin-bottom: 0; padding-left: 0.2em }
.markdown-body ol li::marker,.markdown-body ul li::marker { color: rgba(168, 98, 234, 1) }
.markdown-body ol li.task-list-item, .markdown-body ul li.task-list-item { list-style: none }
.markdown-body ol li.task-list-item ol, .markdown-body ol li.task-list-item ul, .markdown-body ul li.task-list-item ol, .markdown-body ul li.task-list-item ul { margin-top: 0 }
.markdown-body ol ol, .markdown-body ol ul, .markdown-body ul ol, .markdown-body ul ul { margin-top: 10px }
.markdown-body a, .markdown-body code, .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6, .markdown-body li, .markdown-body p { opacity: 0.85; vertical-align: baseline; transition: all 0.1s ease }
.markdown-body a:hover, .markdown-body code:hover, .markdown-body h1:hover, .markdown-body h2:hover, .markdown-body h3:hover, .markdown-body h4:hover, .markdown-body h5:hover, .markdown-body h6:hover, .markdown-body li:hover, .markdown-body p:hover { opacity: 1 }
.markdown-body a { display: inline-block; color: rgba(168, 98, 234, 1); cursor: pointer; text-decoration: none; position: relative }
.markdown-body a:after { content: ""; position: absolute; width: 98%; height: 1px; bottom: 0; left: 0; transform: scaleX(0); background-color: rgba(168, 98, 234, 1); transform-origin: right bottom; transition: transform 0.3s ease-in-out }
.markdown-body a:hover:after { transform: scale(1); transform-origin: left bottom }
.markdown-body a:active, .markdown-body a:link { color: rgba(168, 98, 234, 1) }
.markdown-body img { max-width: 100%; user-select: none; margin: 1em 0; transition: transform 0.2s ease 0s; background-color: rgba(248, 245, 255, 1); box-shadow: 0 0 10px rgba(231, 218, 255, 1) }
.markdown-body img:hover { opacity: 1; box-shadow: 0 0 20px rgba(231, 218, 255, 1); transform: translateY(-1px) }
.markdown-body blockquote { padding: 0.5em 1em; margin: 12px 0; border-top-left-radius: 2px; border-bottom-left-radius: 2px; border-left: 3px solid rgba(168, 98, 234, 1); background-color: rgba(248, 245, 255, 1) }
.markdown-body blockquote>p { margin: 0 }
.markdown-body .math { font-style: italic; margin: 12px 0; padding: 0.5em 1em; background-color: rgba(248, 245, 255, 1) }
.markdown-body .math>p { margin: 0 }
.markdown-body code { padding: 2px 0.4em; overflow-x: auto; color: rgba(168, 98, 234, 1); font-weight: 700; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; background-color: rgba(248, 245, 255, 1) }
.markdown-body pre { margin: 2em 0 }
.markdown-body pre>code { display: block; padding: 1.5em; word-break: normal; font-size: 0.9em; font-style: normal; font-weight: 400; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; line-height: 18px; color: rgba(56, 56, 56, 1); border-radius: 2px; scroll-behavior: smooth; box-shadow: 0 0 10px rgba(231, 218, 255, 1) }
.markdown-body pre>code:hover { box-shadow: 0 0 20px rgba(231, 218, 255, 1) }
.markdown-body pre>code::-webkit-scrollbar { height: 6px; background-color: rgba(248, 245, 255, 1) }
.markdown-body pre>code::-webkit-scrollbar-thumb { background-color: rgba(231, 218, 255, 1); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px }
.markdown-body hr { margin: 2em 0; border-top: 1px solid rgba(168, 98, 234, 1) }
.markdown-body table { width: 100%; font-size: 12px; max-width: 100%; overflow: auto; border-collapse: collapse }
.markdown-body thead { color: rgba(168, 98, 234, 1); background: rgba(248, 245, 255, 1) }
.markdown-body td, .markdown-body th { padding: 0.5em; border: 1px solid rgba(231, 218, 255, 1) }
.markdown-body tr { background-color: rgba(248, 245, 255, 1) }
@media (max-width: 720px) { .markdown-body { font-size: 12px } }.markdown-body pre, .markdown-body pre>code.hljs { color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.hljs-comment, .hljs-quote { color: rgba(153, 153, 136, 1); font-style: italic }
.hljs-keyword, .hljs-selector-tag, .hljs-subst { color: rgba(51, 51, 51, 1); font-weight: 700 }
.hljs-literal, .hljs-number, .hljs-tag .hljs-attr, .hljs-template-variable, .hljs-variable { color: rgba(0, 128, 128, 1) }
.hljs-doctag, .hljs-string { color: rgba(221, 17, 68, 1) }
.hljs-section, .hljs-selector-id, .hljs-title { color: rgba(153, 0, 0, 1); font-weight: 700 }
.hljs-subst { font-weight: 400 }
.hljs-class .hljs-title, .hljs-type { color: rgba(68, 85, 136, 1); font-weight: 700 }
.hljs-attribute, .hljs-name, .hljs-tag { color: rgba(0, 0, 128, 1); font-weight: 400 }
.hljs-link, .hljs-regexp { color: rgba(0, 153, 38, 1) }
.hljs-bullet, .hljs-symbol { color: rgba(153, 0, 115, 1) }
.hljs-built_in, .hljs-builtin-name { color: rgba(0, 134, 179, 1) }
.hljs-meta { color: rgba(153, 153, 153, 1); font-weight: 700 }
.hljs-deletion { background: rgba(255, 221, 221, 1) }
.hljs-addition { background: rgba(221, 255, 221, 1) }
.hljs-emphasis { font-style: italic }
.hljs-strong { font-weight: 700 }

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

  • html模拟生成,非原生。
  • 每个功能均需自行代码调用或实现,而实际原生菜单有很多预设行为可直接使用。
  • Mac 和 Linux 中模拟的菜单无法显示到最上方的菜单栏中去。
  • html 模拟菜单无法生成系统托盘的菜单。

下面将介绍一下 Electron 中的原生菜单。

菜单类型

在 Electron 中有三种类型的菜单:应用菜单、上下文菜单及托盘菜单。

应用菜单

应用菜单即应用上方的那一条菜单。

构建菜单只需要一个数组即可,其中每个成员即为一个菜单项,每个菜单项均有一些指定的配置。以如下代码为例:

const myMenuTemplate = [
{
// 设置菜单项文本
label: '文件',
// 设置子菜单
submenu: [
{
label: '关于 Electron',
// 设置菜单角色
role: 'about', // about (关于),此值只针对 Mac OS 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下可以看到如下效果:

Mac 下是如下效果:

window下我们自定义的关于效果:

而 Mac 下about直接由系统提供:

效果图

很明显, 在 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下依次点击切换显示和切换启用效果如下:

新增效果如下:

mac 中切换和新增效果:

上下文菜单

上下文菜单即鼠标右键菜单,构建菜单的方式还是和上面应用菜单中讲到的方式一样,通过菜单项的配置数组来生成。不过使之成为上下文菜单不再需要使用 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 中效果:

Mac 中:

关于系统托盘 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原生菜单的更多相关文章

  1. 自己动手DIY macos下的绘图软件Pencil之原生菜单

    自从进入到Nodejs这个生态后,体验到了更多的可能性. Pencil是我从Linux时代就开始用的免费开源的原型/流程图软件,它之前版本是基于Firefox的XUL生态开发的,其作者从15年开始基于 ...

  2. electron 自定义菜单

    快捷键:http://electronjs.org/docs/api/accelerator

  3. Pencil 基于Electron的GUI原型工具之菜单再探

    为什么要重试呢? 主要是觉得Pencil这个工具还是比较有价值.就像Linus对Linux下分发版的态度"让用户有选择"一样,在现在这个Sass服务.Web服务化越来越普遍越便利的 ...

  4. (译)通过 HTML、JS 和 Electron 创建你的第一个桌面应用

    原文:Creating Your First Desktop App With HTML, JS and Electron 作者:Danny Markov 近年来 web 应用变得越来越强大,但是桌面 ...

  5. Electron结合React和TypeScript进行开发

    目录 结合React+TypeScript进行Electron开发 1. electron基本简介 为什么选择electron? 2. 快速上手 2.1 安装React(template为ts) 2. ...

  6. electron 起步

    electron 起步 为什么要学 Electron,因为公司需要调试 electron 的应用. Electron 是 node 和 chromium 的结合体,可以使用 JavaScript,HT ...

  7. 【C#】分享一个弹出容器层,像右键菜单那样召即来挥则去

    适用于:.net2.0+ Winform项目 ------------------201508261813更新(源码有更新.Demo未更新)------------------ 重新绘制调整大小手柄( ...

  8. [转]【C#】分享一个弹出浮动层,像右键菜单那样召即来挥则去

    适用于:.net2.0+ Winform项目 背景: 有时候我们需要开一个简单的窗口来做一些事,例如输入一些东西.点选一个item之类的,可能像这样: 完了返回原窗体并获取刚刚的输入,这样做并没有什么 ...

  9. 纯CSS菜单样式,及其Shadow DOM,Json接口 实现

    先声明,要看懂这篇博客要求你具备少量基础CSS知识, 当然如果你只是要用的话就随便了,不用了解任何知识 完整项目github链接:https://github.com/git-Code-Shelf/M ...

  10. 新版本MenuDemo——使用Duilib模拟Windows本机菜单

    相信玩Duilib朋友已经开始期待一个很长的文章.由于我的文章在一周前公布--"无焦点窗体的实现"里面提到了无焦点窗体在菜单里面的应用,并承诺大家,写一个关于Menu实现的Demo ...

随机推荐

  1. 从0开发属于自己的nestjs框架的mini 版 —— ioc篇

    如今,nodejs的框架也是层出不穷,偏向向底层的有 express.koa. Fastify,偏向于上层有阿里的 Egg.thinkjs .还有国外的 nestjs. 在这里我更喜欢 nestjs, ...

  2. React: 路由重定向

    解决方案 参考链接 https://v5.reactrouter.com/web/example/route-config

  3. 高级SQL分析函数-窗口函数

    摘要:本文由葡萄城技术团队于博客园原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 SQL语句中,聚合函数在统计业务数据结果时起到了重要作用 ...

  4. ChatGPT接入Siri(保姆级教程)

    今天,我将为大家分享如何将ChatGPT应用集成到苹果手机的Siri中 (当然手机是需要魔法(TZ)的) 第一步:获取OpenAPI的Key 提取API网址:https://platform.open ...

  5. Doris 再次启动FE失败的思考

    Doris再次启动FE失败的思考 背景描述 在昨天已经成功下载安装最新稳定版docker.拉取doris-0.15.0版本的镜像.将镜像挂载道本地Doris源码目录.完成了doris的编译之后,今天在 ...

  6. 【matplotlib基础】--子图

    使用Matplotlib对分析结果可视化时,比较各类分析结果是常见的场景.在这类场景之下,将多个分析结果绘制在一张图上,可以帮助用户方便地组合和分析多个数据集,提高数据可视化的效率和准确性. 本篇介绍 ...

  7. 构建iOS交叉编译环境

    要进行高级的iOS编程,我们需要很多工具链来帮我们完成这一目的 构建iOS交叉编译环境: 1.新建一个iphone交叉编译虚拟机 2. 为我们的虚拟机添加第二个网卡,设为host-only来达到能与宿 ...

  8. 全是中文的txt文件查找特定字符并输出该行到新文件

    tangshi.txt文件为全为汉唐诗 在该文件中查找指定字符 codecs库为打开中文文件的库,详情自行知乎 tangshi.txt大概十几万行,需要该文件练手的同学下方评论 要点:更改文件字符编码 ...

  9. Nomad 系列-快速上手

    系列文章 Nomad 系列文章 Nomad 重要术语 Nomad 安装设置相关术语 agent - 代理.Agent 是在 Server(服务器) 或 Client(客户端) 模式下运行的 Nomad ...

  10. SQL函数Intersect,except整理

    1.  集合函数使用的规则 ①   每个集合要求列数量相同,列顺序相同. ②   每个集合显示的列,要求数据类型一致或者可隐式转换成同一数据类型 ③   最终集合列名称与第一个集合的列名称一致  2. ...