.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. Variable 'xxxx' is accessed from within inner class, needs to be final or effectively final-Lambda 表达式的变量与作用域

    问题的原因 问题代码: public static void main(String[] args) { Integer sum = 0; Integer count = 0; List<Int ...

  2. python3 使用位图排序

    代码 from bitmap import BitMap a=[1,5,3,4,7,8,15,6,9] print(a) bm=BitMap(max(a)) #print(dir(bm)) print ...

  3. Linux系统安装CH341驱动

    Linux系统安装CH341驱动 Linux系统(这里以ubuntu20.04为例)本身会自动安装CH340驱动,随着时间的推移,旧版本的驱动已经无法支持当下的CH340模块,所以我们需要重新安装驱动 ...

  4. loopback4:单元测试冻结时间

    解决方案 import {expect} from '@loopback/testlab'; import sinon from 'sinon'; describe('example test', ( ...

  5. Unity的AssetPostprocessor之Model之动画:深入解析与实用案例 3

    Unity AssetPostprocessor的Model的动画相关的函数修改实际应用 在Unity中,AssetPostprocessor是一个非常有用的工具,它可以在导入资源时自动执行一些操作. ...

  6. EXP 一款 Java 插件化热插拔框架

    EXP 一款 Java 插件化热插拔框架 前言 多年以来,ToB 的应用程序都面临定制化需求应该怎么搞的问题. 举例,大部分本地化软件厂家,都有一个标准程序,这个程序支持大部分企业的功能需求,但面对世 ...

  7. SpringBoot3集成ElasticSearch

    目录 一.简介 二.环境搭建 1.下载安装包 2.服务启动 三.工程搭建 1.工程结构 2.依赖管理 3.配置文件 四.基础用法 1.实体类 2.初始化索引 3.仓储接口 4.查询语法 五.参考源码 ...

  8. vue 实现 pdf 预览功能

    1 技术背景 1.1 Vue.js 简介和特点 Vue.js 是一种用于构建用户界面的渐进式框架.它具有以下特点: 易学易用:Vue.js 的 API 设计简单直观,使得开发者可以快速上手. 响应式数 ...

  9. Linux ALSA 核心简单分析

    Linux 内核 ALSA 框架通过向用户空间导出多个设备文件,以使用户空间程序可以与内核的音频子系统交互,可以访问音频硬件设备. Linux 内核 ALSA 音频框架初始化 Linux 内核 ALS ...

  10. RocketMQ系列(一) 基本介绍

    RocketMQ系列(一) 基本介绍 1.MQ 作用 MQ 的应用场景主要包含以下 3 个方面: 1.1.异步与解耦 当我们下了一个订单之后,订单服务会进行 RPC 同步调用 支付服务.库存服务.物流 ...