Electron TouchBarOtherItemsProxy 完全指南:定位 Chromium 继承控件的“其他项代理”
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
Electron(macOS Touch Bar API)中的
TouchBarOtherItemsProxy是一个特殊占位控件:它并不渲染任何自定义内容,而是为 Chromium 通过系统机制注入 Touch Bar 的“继承自浏览器本身”的触控栏元素(如输入框联想、播放控制等原生 NSTouchBarItem)指定一个存放位置。本文以官方 API 文档为骨架,结合 lib/browser/api/touch-bar.ts、shell/browser/ui/cocoa/electron_touch_bar.mm 与 spec/api-touch-bar-spec.ts 中的实现与测试,讲清它的创建方式、单实例约束、默认行为以及如何在自定义 Touch Bar 布局中精确安放这一占位符。
TouchBarOtherItemsProxy 是什么
当应用为 BrowserWindow 设置自定义 Touch Bar 时,Touch Bar 上实际显示的并不只有开发者通过 TouchBar 显式声明的那些按钮、标签和滑杆。Chromium / AppKit 系统中的某些交互(例如文本输入框的联想候选条、媒体相关的原生控件)会以系统托管的NSTouchBarItem形式出现在响应链上。这些由 Chromium 继承而来、非 Electron API 显式创建的条目,需要通过一个特殊占位符——Other Items Proxy——才能决定其落位。
TouchBarOtherItemsProxy类实例化的正是这样一个“其他项代理”。在 AppKit 层面,它对应系统常量NSTouchBarItemIdentifierOtherItemsProxy,即 Apple 官方文档中所说的 “Other Items Proxy” 标识符:凡是系统想往当前 Touch Bar 里塞的额外条目,都会统一被摆放到该代理所占据的位置。
与核心 TouchBar 体系的关系
TouchBarOtherItemsProxy是 Electron Touch Bar 控件体系的一员。在 TouchBar 类静态属性 中,它与TouchBarButton、TouchBarLabel、TouchBarColorPicker、TouchBarGroup、TouchBarPopover、TouchBarSlider、TouchBarSpacer、TouchBarSegmentedControl、TouchBarScrubber并列,共同通过TouchBar上的静态引用对外暴露。
关键属性一览
| 项目 | 说明 |
|---|---|
| 类名 | TouchBarOtherItemsProxy |
| 构造签名 | new TouchBarOtherItemsProxy()(无任何 options 参数) |
| 运行进程 | Main(主进程) |
| 作用 | 为 Chromium / AppKit 继承的 Touch Bar 条目指定显示位置 |
| 单例约束 | 每个 TouchBar 只能添加一个实例 |
| 默认位置 | 每个 TouchBar 的条目列表末尾(未显式添加时自动补入) |
从'electron'模块获取
该代理类不是require('electron')的顶层导出。正确的取用方式是解构 TouchBar 的静态属性:
const { TouchBar, TouchBarButton } = require('electron') const { TouchBarOtherItemsProxy } = TouchBar之所以走这条路,是因为在 lib/browser/api/touch-bar.ts 的源码实现中,TouchBar类以静态字段的方式挂载了全部子类:
static TouchBarButton = TouchBarButton; static TouchBarSpacer = TouchBarSpacer; static TouchBarSegmentedControl = TouchBarSegmentedControl; static TouchBarScrubber = TouchBarScrubber; static TouchBarOtherItemsProxy = TouchBarOtherItemsProxy;从 TS 源码可以确认,TouchBarOtherItemsProxy本身是一个最小化的空壳子类,它继承自抽象基类TouchBarItem,不携带任何配置项与交互回调(touch-bar.ts):
class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy { @ImmutableProperty(() => 'other_items_proxy') type!: string; onInteraction = null; }它的type固定为字符串'other_items_proxy',该标识贯穿 JS 层与原生层,用于识别代理条目。
构造与基本用法
new TouchBarOtherItemsProxy()不接受参数,直接实例化后放进TouchBar的items数组即可:
const { app, BrowserWindow, TouchBar } = require('electron') const { TouchBarButton, TouchBarLabel, TouchBarOtherItemsProxy } = TouchBar const touchBar = new TouchBar({ items: [ new TouchBarLabel({ label: '我的自定义按钮区' }), new TouchBarButton({ label: '确定' }), // 告诉系统:Chromium 继承来的控件显示在这个占位处 new TouchBarOtherItemsProxy() ] }) let window app.whenReady().then(() => { window = new BrowserWindow({ width: 600, height: 400 }) window.setTouchBar(touchBar) })将代理放在items的哪个下标位置,Chromium 继承的条目就会渲染在哪个位置。例如把它放在按钮之前,那么系统注入的控件会出现在 Touch Bar 的左侧;放在中间,则被自定义按钮夹在中央。
核心约束:每个 TouchBar 只能有一个代理
[!NOTE] 每个 TouchBar 只能添加一个
TouchBarOtherItemsProxy实例。
这条约束既有 JS 层校验,也有对应单元测试覆盖。在 touch-bar.ts 中,TouchBar构造函数会遍历所有条目并对type === 'other_items_proxy'计数:
let hasOtherItemsProxy = false; const idSet = new Set(); for (const item of items) { // 类型合法性校验 if (!(item instanceof TouchBarItem)) { throw new TypeError('Each item must be an instance of TouchBarItem'); } if (item.type === 'other_items_proxy') { if (!hasOtherItemsProxy) { hasOtherItemsProxy = true; } else { throw new Error('Must only have one OtherItemsProxy per TouchBar'); } } // 同一实例不可重复加入 if (!idSet.has(item.id)) { idSet.add(item.id); } else { throw new Error('Cannot add a single instance of TouchBarItem multiple times in a TouchBar'); } }也就是说,往同一个TouchBar的items里放两个代理会直接抛出运行时错误。对应测试位于 spec/api-touch-bar-spec.ts:
it('throws an error if multiple OtherItemProxy items are added', () => { expect(() => { const touchBar = new TouchBar({ items: [new TouchBarOtherItemsProxy(), new TouchBarOtherItemsProxy()] }); touchBar.toString(); }).to.throw('Must only have one OtherItemsProxy per TouchBar'); });提示:同一 TouchBarItem 实例重复加入
items也会抛错(测试见 spec/api-touch-bar-spec.ts),因此即使每个 TouchBar 只需要一个代理,也应各自new一个实例。
默认行为:自动追加到条目末尾
即使开发者完全不创建代理,Electron 也会默认在每条 TouchBar 的末尾追加一个 Other Items Proxy。这条规则写在原生实现 electron_touch_bar.mm 中——identifiersFromSettings遍历开发者提供的条目字典,将type == "other_items_proxy"的条目翻译成系统常量:
} else if (type == "other_items_proxy") { identifier = NSTouchBarItemIdentifierOtherItemsProxy; has_other_items_proxy = true; }遍历结束后,若发现没有任何代理,则自动在尾部补上系统代理标识符:
if (!has_other_items_proxy) [identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy];因此有两种等效结果:
- 不写代理:Chromium 继承条目被追加到 Touch Bar 最右侧;
- 显式写代理:继承条目出现在你所指定的位置,而自定义条目按顺序排在其两侧。
「默认在末尾」与「可被显式代理改写位置」这两个行为共同解释了为什么代理适合被当作布局中的“系统区锚点”。
源码层面的实现细节
识别符映射与分层 Touch Bar
每个TouchBar最终在原生层被构造成一个NSTouchBar,其defaultItemIdentifiers即为上述映射后的标识符数组(electron_touch_bar.mm)。由于代理条目并非可交互的自定义控件,makeItemForIdentifier:中没有任何分支会为NSTouchBarItemIdentifierOtherItemsProxy构造真实 item——它只作为位置标记由 AppKit 识别并填入系统条目。
值得注意的是分层(Group/Popover)场景下代理的处理存在差异:
- makeGroupForID: 在手工组装
NSGroupTouchBarItem时显式跳过NSTouchBarItemIdentifierOtherItemsProxy标识符,避免将其作为普通 item 生成; - 而 updateGroup: 仍经由
identifiersFromSettings重建组内 Touch Bar,因此代理的“自动末尾补齐”逻辑在组内同样生效。
从源码结构可以推断:组内与顶层 Touch Bar 各自独立决定是否追加代理,这条约束是逐TouchBar实例生效的。
与“继承元素”的关系再强调
Apple 的NSTouchBarItemIdentifierOtherItemsProxy之所以命名带 “Other Items”,是指那些不属于当前 Touch BardefaultItemIdentifiers显式声明、却由 AppKit/Chromium 视上下文自动提供(例如系统级功能)的条目。Electron 将这套机制原样桥接:代理即“Chromium 条目与自定义条目同框排版”的唯一衔接点。
完整可运行示例
将下面的代码保存为touchbar-proxy.js(macOS + Touch Bar 环境,含模拟器)并运行:
const { app, BrowserWindow, TouchBar } = require('electron') // 注意:代理并非顶层导出,必须从 TouchBar 静态属性解构 const { TouchBarButton, TouchBarLabel, TouchBarOtherItemsProxy } = TouchBar app.whenReady().then(() => { const window = new BrowserWindow({ width: 500, height: 300 }) // 显式把“其他项代理”夹在两组自定义按钮中间, // 让 Chromium 注入的控件呈现在 Touch Bar 中部 const touchBar = new TouchBar({ items: [ new TouchBarLabel({ label: '左侧' }), new TouchBarButton({ label: '上一页' }), new TouchBarOtherItemsProxy(), new TouchBarLabel({ label: '右侧' }), new TouchBarButton({ label: '下一页' }) ] }) window.setTouchBar(touchBar) })运行方式与 Touch Bar 的其他示例一致(详见 touch-bar.md 中的示例章节):
- 将文件保存到本机;
- 安装 Electron:
npm install electron; - 执行:
./node_modules/.bin/electron touchbar-proxy.js。
如果改用无代理版本(仅保留前后两组自定义按钮),Chromium 继承条目会被自动排到整条 Touch Bar 的最右端——两者对比即可直观感受到显式代理对“继承区位置”的控制力。
参考文档
- 本类完整 API 定义:docs/api/touch-bar-other-items-proxy.md
- TouchBar 容器类、静态属性与老虎机示例:docs/api/touch-bar.md
- JS 层实现(类型映射、单实例校验、静态导出):lib/browser/api/touch-bar.ts
- 原生层实现(标识符映射、自动末尾补齐、分组跳过逻辑):shell/browser/ui/cocoa/electron_touch_bar.mm
- 行为验证测试:spec/api-touch-bar-spec.ts
- 其他 Touch Bar 控件:
TouchBarButton(touch-bar-button.md)、TouchBarLabel(touch-bar-label.md)、TouchBarGroup(touch-bar-group.md)等
提醒:与整个 Touch Bar API 一致,该能力属于 macOS 专有,且 Touch Bar API 目前仍标注为实验性,可能在未来的 Electron 版本中调整或移除(参见 touch-bar.md 的说明),设计新界面时应预留在回归测试上的余量。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考