完整指南:顶层菜单构建、角色复用与窗口级定制)
Electron 应用菜单Application Menu完整指南顶层菜单构建、角色复用与窗口级定制【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron导读Electron 的每个应用有且仅有一份顶层应用菜单它是桌面应用体验的指挥中心在不同平台上有完全不同的呈现形态。本文以 Electron 仓库中 docs/tutorial/application-menu.md 为主干结合 Menu API 参考 与 lib/browser/api/menu.ts 等真实源码实现完整讲解应用菜单的构建、默认菜单的手动重建、标准 OS 角色role的批量复用以及 Windows/Linux 上按窗口覆盖菜单的进阶技巧帮助你写出真正跨平台、原生观感的菜单代码。应用菜单的定位全应用唯一的一份顶层菜单Electron 中应用菜单Application Menu指的是应用顶层的菜单栏每个应用同一时刻只有一份在 macOS 上这份菜单显示在系统全局菜单栏System Menu Bar中即屏幕顶部始终存在的那条菜单即使应用窗口不在前台也可见。在 Windows 和 Linux 上这份菜单显示在每个 BaseWindow 窗口内部的最上方。它的设置入口是 Menu 类 的静态方法Menu.setApplicationMenu(menu)把一份构建好的Menu实例传进去即可。该方法在源码 lib/browser/api/menu.ts 中的行为是分平台的在 macOSdarwin上把菜单交给原生绑定bindings.setApplicationMenu(menu)走系统菜单栏若传入null则直接返回、不设置。在 Windows / Linux 上Menu.setApplicationMenu(menu)实际是把菜单逐一设置到每一个已存在的窗口上windows.map((w) w.setMenu(menu))。需要特别强调一条硬性约束应用菜单模板数组中每个顶层数组元素必须是带submenu的子菜单顶层菜单栏只支持顶级菜单 → 下拉子菜单这种两级结构且子菜单不能为空数组。另外Electron 内置类不允许被用户代码继承子类化直接使用 API 即可。[!NOTE] 菜单的视觉实现也因平台而异Windows / Linux 上与 Chromium 观感接近macOS 上则是真正的原生菜单。这也意味着同一个Menu实例跨平台表现略有差异符合原生优先的设计哲学。构建应用菜单的两条路径Electron 提供两种向菜单中添加菜单项的方式二者都基于MenuItem与嵌套submenu的结构命令式 append先new Menu()再反复调用menu.append(menuItem)。模板式 buildFromTemplate调用静态方法Menu.buildFromTemplate(template)用一个数组一次性描述整份菜单。模板方式消除了逐条 append 的样板代码是文档与实践中最常见的用法。以模板方式为例const { Menu } require(electron/main) const menu Menu.buildFromTemplate([ { label: Menu, submenu: [ { label: Hello }, { type: separator }, { label: Electron, type: checkbox, checked: true } ] } ]) Menu.setApplicationMenu(menu)模板数组既可以是MenuItemConstructorOptions普通对象也可以直接放入已实例化的MenuItem。源码中 Menu.buildFromTemplate 的处理流程为校验模板必须是数组且每个元素合法——必须声明label、role、type三者的至少一个对模板按定位关键字排序见后文定位规则清理掉多余的分隔符折叠相邻 separator、剔除首尾分隔符逐个把普通对象构造成MenuItem并append到新菜单。模板数组的校验与清理逻辑位于 lib/browser/api/menu.tsareValidTemplateItems会拒绝既无label也无role且非separator的条目removeExtraSeparators会把连续的分隔符合并为一条并删除出现在菜单最前或最后的separator——这正是菜单不能以分隔符开头/结尾这种平台规范的来源。另外模板条目上附加的任意自定义字段会原样成为对应MenuItem的属性可用来携带业务数据。构造菜单时请记住两条规则除separator外的每个菜单项必须有标签手动label或由role自动继承normal是默认菜单项类型带submenu的条目会被自动判为submenu类型其余显式类型还包括checkbox、radio、palettemacOS 14 横向排列的调色板式子菜单与headermacOS 14 分区标题。手把手用 role 手动重建 Electron 默认菜单如果从不调用Menu.setApplicationMenuElectron 会自动为应用创建一份默认菜单。其实际实现位于 lib/browser/default-menu.ts只是一份由四个标准角色子菜单拼成的精简模板macOS 额外含appMenuconst template [ ...(isMac ? [{ role: appMenu }] : []), { role: fileMenu }, { role: editMenu }, { role: viewMenu }, { role: windowMenu } ];下面这份完整示例等价于在代码里手动重建该默认菜单可当作从零开始的完整应用菜单学习蓝本。它充分利用了平台条件展开isMac判断来同时适配 macOS 与 Windows/Linuxconst { shell } require(electron/common) const { app, Menu } require(electron/main) const isMac process.platform darwin const template [ // { role: appMenu } —— 仅在 macOS 上存在以应用名称为标签的首个子菜单 ...(isMac ? [{ label: app.name, submenu: [ { role: about }, { type: separator }, { role: services }, { type: separator }, { role: hide }, { role: hideOthers }, { role: unhide }, { type: separator }, { role: quit } ] }] : []), // { role: fileMenu } { label: File, submenu: [ isMac ? { role: close } : { role: quit } ] }, // { role: editMenu } { label: Edit, submenu: [ { role: undo }, { role: redo }, { type: separator }, { role: cut }, { role: copy }, { role: paste }, ...(isMac ? [ { role: pasteAndMatchStyle }, { role: delete }, { role: selectAll }, { type: separator }, { label: Speech, submenu: [ { role: startSpeaking }, { role: stopSpeaking } ] } ] : [ { role: delete }, { type: separator }, { role: selectAll } ]) ] }, // { role: viewMenu } { label: View, submenu: [ { role: reload }, { role: forceReload }, { role: toggleDevTools }, { type: separator }, { role: resetZoom }, { role: zoomIn }, { role: zoomOut }, { type: separator }, { role: togglefullscreen } ] }, // { role: windowMenu } { label: Window, submenu: [ { role: minimize }, { role: zoom }, ...(isMac ? [ { type: separator }, { role: front }, { type: separator }, { role: window } ] : [ { role: close } ]) ] }, { role: help, submenu: [ { label: Learn More, click: async () { const { shell } require(electron) await shell.openExternal(https://electronjs.org) } } ] } ] const menu Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)这段代码的关键点值得逐条消化macOS 第一个子菜单固定以应用名为标签。系统要求应用菜单的第一项永远展示你的应用名称一般用appMenu角色或手动构造label: app.name的子菜单来填充它。若平台不是 macOS这段会被展开为空数组从而整体跳过。isMac条件展开是平台差异化惯用法。例如 Edit 菜单在 macOS 上多了pasteAndMatchStyle、Speech 子菜单等File 菜单在 macOS 上是close关闭窗口Windows/Linux 上是quit退出应用Window 菜单在 macOS 上额外包含frontBring All to Front。这与 menu-item-roles.ts 中 editmenu/windowmenu/filemenu 的默认定义 完全一致可见平台差异是 Electron 原生规范的一部分需要开发者显式表达。learn more里的shell.openExternal展示了一个自定义click回调的标准写法点击该菜单项时通过系统浏览器打开外部网址。[!IMPORTANT] 在 macOS 上应用菜单的第一个子菜单永远以你的应用名称为标签。一般建议通过条件性地加入appMenu角色菜单项来填充它而不是依赖手写标签——因为系统会强制改写标签文本。默认菜单的自动创建由是否显式设置过控制自动创建默认菜单并不是无条件的。源码 lib/browser/default-menu.ts 中用模块级标志位applicationMenuWasSet做守卫一旦任何代码路径调用了Menu.setApplicationMenu无论传入菜单还是null该标志即被置位此后默认菜单不再被创建。对应测试位于 spec/api-app-spec.ts覆盖三个场景应用从未设置菜单 → 自动创建默认菜单应用设置了自定义菜单测试桩见 spec/fixtures/api/default-menu/main.jsnew Menu()后Menu.setApplicationMenu(expectedMenu)→ 不创建默认菜单应用显式传nullMenu.setApplicationMenu(null)→ 同样不创建默认菜单。换句话说若你想彻底禁用默认菜单最直接的方式是启动早期调用一次Menu.setApplicationMenu(null)。这与 API 文档行为一致在 Windows/Linux 上传入null还会额外移除窗口的菜单栏。菜单查询与运行时不可增删限制设置后可用Menu.getApplicationMenu()取回当前应用菜单未设置则返回null。需要留意的是取回的实例不支持动态增删菜单项但实例属性仍可修改。macOS 上还有静态方法Menu.sendActionToFirstResponder(action)_macOS_用于模拟默认菜单行为日常场景更推荐使用role而非手动触发 action。角色复用用子菜单角色拼装标准菜单栏逐条手写每个子菜单非常冗长。若只是想复用 Electron 内置的标准子菜单可以使用一组子菜单级角色default menu roles。把上一节的完整模板压缩后如下const { shell } require(electron/common) const { app, Menu } require(electron/main) const template [ ...(process.platform darwin ? [{ role: appMenu }] : []), { role: fileMenu }, { role: editMenu }, { role: viewMenu }, { role: windowMenu }, { role: help, submenu: [ { label: Learn More, click: async () { const { shell } require(electron) await shell.openExternal(https://electronjs.org) } } ] } ] const menu Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)四种核心子菜单角色的行为参见 角色表 与源码 menu-item-roles.tsappMenu_macOS_—— 整份默认应用菜单About、Services、Hide、Quit 等标签自动取app.namefileMenu—— File 菜单Close / Quit随平台切换editMenu—— Edit 菜单Undo、Copy、Paste 等macOS 额外包含 Substitutions、Speech 子菜单viewMenu—— View 菜单Reload、Toggle Developer Tools、缩放与全屏windowMenu—— Window 菜单Minimize、Zoom 等macOS 额外含 Bring All to Front。注意help并不是fileMenu那样的带默认内容的角色它只提供顶层 Help 菜单框架在 macOS 上它会获得内置的菜单搜索栏但要正常工作你必须自己向它的submenu中添加条目。[!TIP] 若要查看每一类 role 可用的具体取值参见 MenuItem roles 一节。文档还提示角色字符串不区分大小写toggleDevTools、toggledevtools、TOGGLEDEVTOOLS等价。源码级拆解role 究竟做了什么菜单项指定role之后标签、快捷键、点击行为多数由 Electron 替你补齐其定义集中在 lib/browser/api/menu-item-roles.ts 的roleList中。每个 role 大致携带以下信息label默认标签部分为惰性 getter如about返回About ${app.name}、quit在 Windows 上返回Exitaccelerator默认快捷键字符串appMethod/windowMethod/webContentsMethod三种可能的执行目标分别作用于 app 全局、聚焦窗口BaseWindow、聚焦的 WebContentsnonNativeMacOSRole标记该角色并非 macOS 原生 action需要 Electron 手动执行其余角色在 macOS 上直接由系统 AppKit 处理。执行函数execute(role, focusedWindow, focusedWebContents)的优先级是appMethod→windowMethod需聚焦窗口存在→webContentsMethod需聚焦 WebContents 存在。而在 macOS 上若角色是原生 actionnonNativeMacOSRole为假则execute直接返回false交给系统处理。下表中高频角色的默认快捷键来自roleList注意源码以全小写形式定义、对外大小写不敏感role默认标签示例默认快捷键执行目标undo/redoUndo / RedoCommandOrControlZWindows 上 Redo 为ControlY其余ShiftCommandOrControlZWebContentscut/copy/pasteCut / Copy / PasteCommandOrControlX/C/V不注册为全局加速键WebContentspasteAndMatchStylePaste and Match StylemacOSCmdOptionShiftV其余ShiftCommandOrControlVWebContentsselectAll/deleteSelect All / DeleteCommandOrControlADelete 无默认快捷键WebContentsreload/forceReloadReload / Force ReloadCmdOrCtrlR/ShiftCmdOrCtrlRWebContentstoggleDevToolsToggle Developer ToolsmacOSAltCommandI其余CtrlShiftIWebContentsresetZoom/zoomIn/zoomOutActual Size / Zoom In / Zoom OutCommandOrControl0/CommandOrControlPlus/CommandOrControl-WebContentstogglefullscreenToggle Full ScreenmacOSControlCommandF其余F11聚焦窗口minimizeMinimizeCommandOrControlM聚焦窗口closeClose Window(macOS) / CloseCommandOrControlW聚焦窗口quitQuit(macOS/Linux) / Exit(Windows)Windows 无默认快捷键其余CommandOrControlQapp 全局aboutAbout / About {app}无Windows/Linux 上弹出自定义 About 面板hide/hideOthersmacOSHide {app} / Hide OthersCommandH/CommandAltH原生 action两个可直接引用的工程结论能匹配到标准角色的菜单项优先用role而非手写click。label与accelerator在声明role后可省略Electron 会按平台填入恰当默认值源码 menu.ts 也印证了这点——命令的加速键优先取自定义accelerator缺省时才通过getDefaultRoleAccelerator()回退到角色默认值。原生角色实现的观感与行为通常好于手写例如copy/cut/paste的registerAccelerator: false是为了避免在菜单不可见时抢走系统快捷键。角色命中后的启用态是动态计算的。源码 menu.ts 的_isCommandIdEnabled会针对minimize、togglefullscreen、close检查聚焦窗口的isMinimizable()/isFullScreenable()/isClosable()窗口不支持时对应菜单项自动置灰——这就是原生行为的体现。若需要自定义click回调其签名为click(event, focusedWindow, focusedWebContents)与源码 menu.ts 的_executeCommand保持一致它先从BaseWindow.getFocusedWindow()取聚焦窗口再调用command.click(event, focusedWindow, webContents.getFocusedWebContents())。Windows / Linux 专属按窗口覆盖应用菜单此节内容仅适用于 Windows 与 Linux。由于在 Windows/Linux 上应用菜单物理上存在于每个BaseWindow窗口内因此你完全可以用窗口方法做更细粒度的覆盖——让不同窗口显示不同菜单栏。设置窗口专属菜单的完整示例const { BrowserWindow, Menu } require(electron/main) const win new BrowserWindow() const menu Menu.buildFromTemplate([ { label: my custom menu, submenu: [ { role: copy }, { role: paste } ] } ]) win.setMenu(menu)注意代码中的顺序先创建BrowserWindow再构建Menu最后调用窗口方法win.setMenu(menu)_Linux_ _Windows_。实际 API 定义在 BaseWindow 上BrowserWindow继承自BaseWindow它把传入的Menu直接设为该窗口的菜单栏。配套的清除能力有以下几种可按需选用win.removeMenu()_Linux_ _Windows_—— 移除该窗口的菜单栏win.setMenu(null)—— 同样可以去掉窗口菜单Menu.setApplicationMenu(null)—— 移除所有窗口的菜单栏仅限 Windows/Linux因为此前已说明该方法在非 macOS 上会广播给每个窗口若只是不想让菜单栏抢焦点可配合autoHideMenuBar窗口选项与win.setMenuBarVisibility(visible)在隐藏/显示之间切换。[!TIP] 面向 macOS 开发时注意macOS 的菜单位于全局系统菜单栏不支持win.setMenu这种窗口级覆盖这正是 Menu.setApplicationMenu 的实现按process.platform darwin分叉的根本原因。进阶模板中控制菜单项位置菜单模板并非只能按数组顺序排列。Electron 支持通过id与定位属性让模板定义顺序与最终展示顺序解耦适用于动态拼接菜单、多个模块贡献菜单项的场景规则细节见 Menus 指南的 Programmatic item positioning排序实现见 lib/browser/api/menu-utils.tsbefore: [id]/after: [id]—— 插入到指定id项之前/之后并把本项归入目标项的分组若引用项不存在则追加到末尾。beforeGroupContaining: [id]/afterGroupContaining: [id]—— 把本项所在的整个分组以分隔符为界移动到指定项所在分组的前/后。id—— 配合上述属性使用的定位锚点菜单构建后可用menu.getMenuItemById(id)按 id 找回菜单项会递归搜索子菜单见 menu.ts。例如[{ id: 1, label: one, after: [3] }, { id: 2, label: two, before: [1] }, { id: 3, label: three }]最终会呈现为three → two → one的顺序。构建流程先经 menu-utils 的排序逻辑 重排模板再进入前面提到的合法性校验与分隔符清理。默认不写定位属性时仍按模板原顺序插入。顺带一提模板条目若含有icon配合nativeImage使用、sublabelmacOS 14.4 的副标题、toolTipmacOS 悬停提示等附加属性会分别映射到对应原生能力Menu层面还提供popup()弹出上下文菜单、closePopup()、items、menu-will-show/menu-will-close事件等丰富能力均可在同一 Menu 模型上复用。小结围绕 Electron 的顶层应用菜单本文覆盖了一条完整的知识链从每应用一份、平台展示各异的定位到Menu.buildFromTemplate的构建与模板校验从默认菜单的手动重建含 macOS 专属的appMenu首项规范到用fileMenu/editMenu/viewMenu/windowMenu批量复用标准子菜单再到 Windows/Linux 上用win.setMenu/win.removeMenu做窗口级覆盖以及role标签、快捷键与启用态在源码中的落点。实践中的三条建议优先角色凡匹配标准 role 的菜单项一律用role让 Electron 处理平台差异与快捷键尽早设置若不需要默认菜单在应用启动早期调用Menu.setApplicationMenu(null)显式禁用避免用户看到与业务不符的内置 File/Edit/View/Window分平台校验编写菜单代码后分别在 macOS 与 Windows/Linux 上跑一遍重点检查首个子菜单是否应用名、Edit/Window 子菜单的项目差异是否如期出现。想继续深挖可参考仓库中的 Menus 总指南、Menu API 参考、MenuItem 参考 以及相关测试 spec/api-menu-spec.ts 与 spec/api-app-spec.ts。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考