news 2026/9/7 8:06:22

Electron MenuItemBadge 深度解析:在 macOS 菜单项上显示系统级角标的完整机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Electron MenuItemBadge 深度解析:在 macOS 菜单项上显示系统级角标的完整机制

Electron MenuItemBadge 深度解析:在 macOS 菜单项上显示系统级角标的完整机制

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

本篇基于 Electron 仓库的docs/api/structures/menu-item-badge.md官方文档展开,系统讲解MenuItemBadge对象的全部字段(typecountcontent)、取值约束与本地化规则,并结合lib/browser/api/menu-item.ts的参数校验逻辑与shell/browser/ui/cocoa/electron_menu_controller.mm中到NSMenuItemBadge的转换实现,帮助你在 macOS 应用菜单中正确配置、动态更新和移除菜单角标。

一、MenuItemBadge 是什么

MenuItemBadge是 ElectronMenu/MenuItemAPI 中的一个结构体(structure)对象,用于在macOS菜单项的标签旁显示角标(badge)。它可以呈现两种形态:

  • 系统样式角标:通过alertsupdatesnew-items三种预定义类型之一,由 macOS 系统自动本地化并处理复数形式(pluralization);
  • 自定义文本角标:使用none类型搭配content字符串,显示任意自定义内容,此时本地化与复数形式需开发者自行处理。

该能力对应 macOS 14(Sonoma)引入的NSMenuItemBadgeAppKit API,因此文档在 docs/api/menu-item.md 中明确标注:仅 macOS 14 及以上版本可用

二、字段详解与取值约束

以下是 docs/api/structures/menu-item-badge.md 中定义的完整字段说明:

1.type(string,可选)

  • 可选值:'alerts''updates''new-items''none'
  • 默认值:'none'

三种预定义类型分别对应 AppKit 中不同语义的系统角标(警报、更新、新条目)。使用预定义类型(非none)时,系统会自动为角标做本地化和复数形式处理,例如根据用户语言显示“3 个更新”或“1 update”这类本地化文案。

2.count(number,可选)

  • 角标显示的条目数量;
  • alertsupdatesnew-items三种类型必填
  • 不能none类型同时使用。

3.content(string,可选)

  • 显示在角标中的自定义字符串;
  • 可与none类型一起使用,且此时为必填。

原文档特别强调了一条本地化规则:

如果你使用预定义角标类型(非none),系统会替你完成本地化和复数处理;如果你创建自定义角标字符串,你需要自己完成该字符串的本地化与复数处理。

三、主进程侧的参数校验:lib/browser/api/menu-item.ts

文档中的约束在 Electron 主进程 JS 层被严格实现。lib/browser/api/menu-item.ts 定义了合法类型白名单和validateBadge校验函数:

const badgeTypes = ['alerts', 'updates', 'new-items', 'none']; const validateBadge = (badge: any) => { if (badge == null) return; if (typeof badge !== 'object') { throw new TypeError('badge must be a MenuItemBadge object'); } const type = badge.type ?? 'none'; if (!badgeTypes.includes(type)) { throw new TypeError(`Invalid badge type '${type}': must be one of ${badgeTypes.join(', ')}`); } if (type === 'none') { if (typeof badge.content !== 'string') { throw new TypeError("badge.content must be a string when badge.type is 'none'"); } if (badge.count != null) { throw new TypeError("badge.count cannot be used when badge.type is 'none'"); } } else { if (!Number.isInteger(badge.count) || badge.count < 0) { throw new TypeError(`badge.count must be a non-negative integer when badge.type is '${type}'`); } if (badge.content != null) { throw new TypeError("badge.content can only be used when badge.type is 'none'"); } } };

从这段源码可以确认几个比文档更细的约束:

  1. badge必须是对象:传入非对象(非null/undefined)会直接抛出TypeError
  2. type省略时按'none'处理:所以none类型下content是实际必填项,只传badge: {}会因content不是字符串而报错;
  3. count必须是非负整数Number.isInteger(badge.count) && badge.count >= 0,浮点数或负数都会被拒绝;
  4. countcontent互斥none类型下不允许出现count,预定义类型下不允许出现content

此外,badge属性只在process.platform === 'darwin'分支上被定义——这意味着在 Windows 和 Linux 上MenuItem实例根本没有badge属性,从源码结构看这是对该 API macOS 专属定位的直接体现。

四、badge 的动态更新与原生同步机制

1. JS 层的 setter:改完立即下推原生菜单

MenuItem构造器中通过Object.definePropertybadge安装 getter/setter(见 lib/browser/api/menu-item.ts 第 73-86 行):

if (process.platform === 'darwin') { validateBadge(options.badge); let badgeValue = options.badge ?? undefined; Object.defineProperty(this, 'badge', { get: () => badgeValue, set: (newValue) => { validateBadge(newValue); badgeValue = newValue ?? undefined; // Push the change to the native item if this item is already in a menu. if (this.menu) { const index = this.menu.getIndexOfCommandId(this.commandId); if (index !== -1) this.menu.setBadge(index, badgeValue ?? null); } }, enumerable: true }); }

关键行为:

  • 动态可改:docs/api/menu-item.md 指出menuItem.badge可以被动态修改,设置为undefined即移除角标
  • 即时下推:如果该菜单项已经挂在某个菜单上(this.menu存在),setter 会通过内部方法menu.setBadge(index, badgeValue ?? null)把变更实时推送到原生NSMenuItem,无需重建整个Menu。移除时传null,与 C++ 侧“无 badge”语义对应。

setBadge由 shell/browser/api/electron_api_menu.cc 中的.SetMethod("setBadge", &Menu::SetBadge)暴露,其内部声明见 typings/internal-electron.d.ts:

setBadge(index: number, badge: MenuItemBadge | null): void;

2. C++ 模型层:按 commandId 存储

原生模型 shell/browser/ui/electron_menu_model.cc 中维护了一个以command_id为键的badges_映射:

void ElectronMenuModel::SetBadge(size_t index, std::optional<Badge> badge) { const int command_id = GetCommandIdAt(index); if (badge) badges_[command_id] = std::move(*badge); else badges_.erase(command_id); } bool ElectronMenuModel::GetBadgeAt(size_t index, Badge* badge) const { int command_id = GetCommandIdAt(index); const auto iter = badges_.find(command_id); if (iter != badges_.end()) { *badge = iter->second; return true; } return false; }

SetBadge传入空值即从映射中删除该条目,这正是 JS 侧设置undefined后角标消失的底层实现。

3. macOS 层:转换为 NSMenuItemBadge

最终的角标创建发生在 shell/browser/ui/cocoa/electron_menu_controller.mm:

// Convert a Badge to an NSMenuItemBadge, or nil if it has nothing to show. NSMenuItemBadge* CreateBadge(const electron::ElectronMenuModel::Badge& badge) { if (badge.type == "none") { if (!badge.content) return nil; return [[NSMenuItemBadge alloc] initWithString:base::SysUTF8ToNSString(*badge.content)]; } const NSInteger count = badge.count.value_or(0); if (badge.type == "alerts") return [NSMenuItemBadge alertsWithCount:count]; if (badge.type == "updates") return [NSMenuItemBadge updatesWithCount:count]; if (badge.type == "new-items") return [NSMenuItemBadge newItemsWithCount:count]; return nil; }

这段代码把 JS 字段与 AppKit 工厂方法一一映射:

type调用的 AppKit 方法使用的字段
noneinitWithString:content(UTF-8 转 NSString)
alertsalertsWithCount:count(缺省取 0)
updatesupdatesWithCount:count(缺省取 0)
new-itemsnewItemsWithCount:count(缺省取 0)

菜单构建与刷新路径中,ElectronMenuController通过model->GetBadgeAt(index, &badge)查询当前项的角标并赋值给item.badge(无角标时为nil)。这也说明角标状态由模型层按commandId持久保存,菜单重新构建时不会丢失。

五、完整使用示例

在菜单模板中静态声明角标

const { Menu } = require('electron'); const template = [ { label: 'My App', submenu: [ { label: 'Preferences…', accelerator: 'CmdOrCtrl+,' }, { label: 'Inbox', // 系统样式角标:显示 “3 updates”(系统自动本地化 + 复数处理) badge: { type: 'updates', count: 3 }, click: () => console.log('open inbox') }, { label: 'Drafts', // 自定义文本角标:本地化与复数需自行处理 badge: { type: 'none', content: 'New' } }, { type: 'separator' } ] } ]; Menu.setApplicationMenu(Menu.buildFromTemplate(template));

动态更新与移除

const item = appMenu.items[3]; // 假设是 “Inbox” 项 // 更新数量 item.badge = { type: 'new-items', count: 7 }; // 切换为自定义文本 item.badge = { type: 'none', content: '1,024' }; // 移除角标 item.badge = undefined;

由于 setter 内部会即时调用menu.setBadge(index, ...),上述改动无需重建Menu,对已弹出的应用菜单实时生效(下一次菜单展开时由ElectronMenuControllerGetBadgeAt重新渲染)。

常见错误对照

结合 lib/browser/api/menu-item.ts 的validateBadge实现,以下写法会抛出TypeError

写法报错原因
badge: 'alerts'badge must be a MenuItemBadge object,必须是对象
badge: { type: 'foo', count: 1 }非法 type,必须是alertsupdatesnew-itemsnone之一
badge: { type: 'none' }none类型要求content为字符串
badge: { type: 'none', count: 2 }count不能与none同时使用
badge: { type: 'updates', count: 1.5 }count必须是非负整数
badge: { type: 'updates', content: 'x' }content只能搭配none使用

六、限制与注意事项

  1. 平台限制:仅 macOS 14 及以上。JS 层仅在process.platform === 'darwin'时定义badge属性,非 macOS 平台上访问该属性会得到undefined
  2. Dock 菜单不显示角标:docs/api/menu-item.md 明确说明,badge 不会出现在 Dock 菜单(dock.setMenu(menu))中,但同一菜单项在应用菜单里正常显示角标;
  3. 本地化责任划分:预定义类型由系统负责本地化与复数形式;none类型的自定义字符串则由开发者自行完成本地化和复数处理,这一点在 docs/api/structures/menu-item-badge.md 末尾被特别强调;
  4. 校验时机:无论是构造MenuItem时通过options.badge传入,还是运行时给menuItem.badge赋值,都会先经过validateBadge,非法组合在 JS 层即被拒绝,不会到达原生代码。

七、小结

MenuItemBadge以三个字段(type/count/content)把 macOSNSMenuItemBadge的能力暴露给 Electron 应用:预定义类型换取系统的本地化与复数能力,none类型换取完全自定义的显示内容。整条链路为:JS 层validateBadge校验(lib/browser/api/menu-item.ts)→Menu.setBadge下推(shell/browser/api/electron_api_menu.cc)→ElectronMenuModelcommandId存储(shell/browser/ui/electron_menu_model.cc)→CreateBadge转换为NSMenuItemBadge(shell/browser/ui/cocoa/electron_menu_controller.mm)。掌握这条链路与各字段互斥约束,即可在 macOS 应用菜单中正确落地系统级角标功能。

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 8:05:25

用MATLAB计算普朗克公式:黑体辐射计算与单位换算全指南

简介&#xff1a;面向红外仿真与黑体辐射研究的MATLAB代码包&#xff0c;实现普朗克公式对辐射出射度的数值计算&#xff0c;适合需要分析不同温度与波长组合下黑体辐射特性的工程师、科研人员与相关专业学习者。普朗克公式是描述黑体辐射能量分布的经典定律&#xff0c;其数值…

作者头像 李华
网站建设 2026/9/7 8:04:20

FunASR 时间戳对齐实操:3 步修复文字与音频不同步

FunASR 时间戳对齐实操&#xff1a;3 步修复文字与音频不同步 【免费下载链接】FunASR Open-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving. 项目地址: …

作者头像 李华
网站建设 2026/9/7 8:04:01

宝可梦机甲盲盒:Three.js与随机算法实现3D交互项目

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 8:03:56

StateAct:解决AI智能体长时任务状态管理的核心技术

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 8:00:07

LabWindows/CVI调用DLL全指南:原理方法实战排查

简介&#xff1a;这是一份演示CVI调用DLL的完整工程示例&#xff0c;适合使用LabWindows/CVI进行视觉应用开发的工程师学习。压缩包共包含19个文件&#xff0c;总大小约253KB&#xff0c;囊括两个工程文件&#xff08;prj&#xff09;、C源代码、头文件、界面文件&#xff08;u…

作者头像 李华
网站建设 2026/9/7 7:57:56

机器学习入门:核心算法原理与Python实战详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华