
Electron TouchBar 分段控件 SegmentSegmentedControlSegment 对象完全解析【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron本篇以 Electron API 文档中的 SegmentedControlSegment 结构定义 为主体讲解 macOS TouchBar 分段控件Segmented Control中每个分段segment的数据结构——label、icon、enabled三个可选字段的含义、默认值与更新行为并结合 主进程 TouchBar API 实现 与 macOS 原生桥接层源码 说明这些数据如何一路传递到 AppKit 的NSSegmentedControl。读完后你可以独立编写出带图标、可禁用分段、支持单选/多选/按钮模式的 TouchBar 分段控件并理解「改数组里的深层属性不会刷新 TouchBar」这一关键行为背后的实现机制。SegmentedControlSegment 是什么SegmentedControlSegment 对象文档 本身只有简短的定义但它承载的信息量并不小它是 TouchBarSegmentedControl 构造参数segments数组的元素类型决定了 TouchBar 上分段控件中每一个分段长什么样、能不能被点选。TouchBarSegmentedControl是一个「带选中态的按钮组」运行于 Main 进程。文档中有一处重要说明该类不从electron模块导出只能通过其他 Electron API 方法即TouchBar的静态属性TouchBarSegmentedControl作为返回值拿到。也就是说正确的获取方式是const { TouchBarSegmentedControl } require(electron).TouchBar而不是单独require这个类。此外 TouchBar 文档 还明确提示TouchBar API 目前是实验性的未来版本可能改变或移除。字段定义原文档完整继承SegmentedControlSegment对象包含三个全部可选的字段字段类型是否可选默认值说明labelstring可选空字符串显示在该分段中的文本iconNativeImage可选无图显示在该分段中的图片enabledboolean可选true该分段是否可被选择补充几点原文档未展开、但源码可以确认的细节label与icon可以共存也可以都缺省。从原生实现看见下文当label缺失时底层会显式调用setLabel: forSegment:i即写入空字符串而不是保留旧值icon必须是NativeImage类型由nativeImage模块创建缺失时底层会把该分段的图片置为nilenabled缺省为true只有显式传false时该分段才变为不可选灰化状态。segments 数组构造参数与实例属性的双重身份segments既出现在TouchBarSegmentedControl的构造参数中也出现在其实例属性上二者的语义略有不同。作为构造参数在new TouchBarSegmentedControl(options)中segments是必填的SegmentedControlSegment[]数组其他构造参数还包括segmentStyle可选automatic默认自动根据宿主窗口类型与控件位置决定外观、rounded、textured-rounded、round-rect、textured-square、capsule、small-square、separated各分段彼此紧挨但不接触。这些值一一对应 AppKit 的NSSegmentStyle*常量mode可选single默认同一时间只有一项选中选中新项会取消旧项、multiple可多项同时选中、buttons分段作为按钮使用按下松开即可永远不标记为激活态selectedIndex可选当前选中分段的索引用户交互后会自动更新mode为multiple时它是最后一次被选中的项change可选用户选择新分段时回调参数为selectedIndexInteger与isSelectedboolean。作为实例属性一条「坑」要牢记TouchBarSegmentedControl 实例属性文档 对touchBarSegmentedControl.segments的说明是一个SegmentedControlSegment[]数组表示控件中的分段。更新这个值会立即刷新 TouchBar 上的控件但更新数组内部的深层属性deep properties不会刷新 TouchBar。这句「更新数组深层属性不生效」是整个对象最容易被踩的坑。它在源码中有直接对应lib/browser/api/touch-bar.ts 中segments被实现为一个LivePropertyclass TouchBarSegmentedControl extends TouchBarItemElectron.TouchBarSegmentedControlConstructorOptions implements Electron.TouchBarSegmentedControl { LivePropertyTouchBarSegmentedControl((config) config.segments || []) segments!: Electron.SegmentedControlSegment[];LiveProperty装饰器touch-bar.ts 第 4061 行的机制是构造时把config.segments的当前引用存入隐藏属性对control.segments赋值整体替换数组时触发 setter调用this.emit(change, this)通知上层刷新而control.segments[0].label New这种写法只是修改了数组元素对象内部的属性JS 层没有任何 setter 被触发因此change事件不会发出TouchBar 自然不刷新。正确的刷新姿势是重新构造数组后整体赋值例如const { TouchBar } require(electron) const { TouchBarSegmentedControl } TouchBar const { nativeImage } require(electron) // 初始三个分段第二个禁用 const control new TouchBarSegmentedControl({ mode: single, segmentStyle: rounded, selectedIndex: 0, segments: [ { label: Small }, { label: Large, enabled: false }, { icon: nativeImage.createFromDataURL(dataURL), enabled: true } ], change: (selectedIndex, isSelected) { console.log(selected ${selectedIndex}, isSelected${isSelected}) } }) // 想改第 0 个分段的 label必须整体替换数组 control.segments [ { label: XS }, // 改了这一个 { label: Large, enabled: false }, { icon: nativeImage.createFromDataURL(dataURL), enabled: true } ] // 赋值触发 LiveProperty 的 setter - emit(change) - 窗口刷新该条目同一文件中的selectedIndex与mode、segmentStyle也是LiveProperty第 271281 行因此control.selectedIndex 2这类直接赋值都会立即同步到 TouchBar无需重建数组。数据如何落到 AppKit原生桥接层解析macOS 侧的桥接代码位于 shell/browser/ui/cocoa/electron_touch_bar.mm。updateSegmentedControl:withSettings:方法第 623681 行完整展示了SegmentedControlSegment三个字段的消费方式std::vectorgin_helper::Dictionary segments; settings.Get(segments, segments); control.segmentCount segments.size(); for (size_t i 0; i segments.size(); i) { std::string label; gfx::Image image; const bool enabled segments[i].ValueOrDefault(enabled, true); if (segments[i].Get(label, label)) { [control setLabel:base::SysUTF8ToNSString(label) forSegment:i]; } else { [control setLabel: forSegment:i]; } if (segments[i].Get(icon, image)) { [control setImage:image.AsNSImage() forSegment:i]; [control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i]; } else { [control setImage:nil forSegment:i]; } [control setEnabled:enabled forSegment:i]; }从中可以确认四个文档层面的事实enabled的默认值确实是true——ValueOrDefault(enabled, true)直接对应文档中 Default: truelabel与icon相互独立label未提供时写空串icon未提供时置nil两者不会互相顶替提供icon时同时设置NSImageScaleProportionallyUpOrDown缩放策略即图片等比缩放、允许放大也允许缩小这解释了为什么高分辨率NativeImage在小分段里不会变形segmentStyle的八个取值逐一映射到NSSegmentStyleRounded、NSSegmentStyleTexturedRounded、NSSegmentStyleRoundRect、NSSegmentStyleTexturedSquare、NSSegmentStyleCapsule、NSSegmentStyleSmallSquare、NSSegmentStyleSeparated其余含未传一律落到NSSegmentStyleAutomaticmode的映射为multiple-NSSegmentSwitchTrackingSelectAnybuttons-NSSegmentSwitchTrackingMomentary其余 -NSSegmentSwitchTrackingSelectOne。selectedIndex的处理也很值得注意第 678680 行只有当0 selectedIndex segmentCount时才会真正调用control.selectedSegment 越界索引会被静默忽略而非报错——所以在动态增删segments后如果忘了同步selectedIndex选中态可能停留在旧值上这是维护控件状态时需要注意的一致性点。用户点击如何回流到 JS 的change回调NSSegmentedControl的 target-action 被设置为segmentedControlAction:第 598621 行 创建控件时绑定第 278290 行 实现回调。每次点击时原生层读取selectedSegment与isSelectedForSegment:打包成{ selectedIndex, isSelected }字典经NotifyTouchBarItemInteraction发回 JS 层。JS 侧的接住逻辑在 touch-bar.ts 第 283291 行onInteraction是一个ImmutableProperty构造时由change回调包装而成——ImmutablePropertyTouchBarSegmentedControl(({ change: onChange }, setInternalProp) typeof onChange function ? (details: { selectedIndex: number; isSelected: boolean }) { setInternalProp(selectedIndex, details.selectedIndex); onChange(details.selectedIndex, details.isSelected); } : null ) onInteraction!: Function | null;这段代码揭示了文档中「selectedIndex会随用户交互自动更新」的实现原生交互回来时先通过setInternalProp把新的selectedIndex写回隐藏属性注意这里绕过了LiveProperty的 setter因此用户点击不会引发一次多余的change事件回推窗口只有程序赋值才推再调用用户传入的change(selectedIndex, isSelected)。这里还解释了isSelected参数存在的意义在multiple模式下用户点击一个已选中的分段会取消选中此时selectedIndex指向被点击的项而isSelected为false在buttons模式下每次点击都会触发回调但控件从不保持选中态。完整实战示例模式切换器把上述机制串起来下面是一个可直接运行的完整示例保存为touchbar.js在装有 Electron 的 macOS 项目目录中用./node_modules/.bin/electron touchbar.js运行没有 Touch Bar 硬件时可用系统自带的 Touch Bar 模拟器查看const { app, BrowserWindow, TouchBar, nativeImage } require(electron) const { TouchBarSegmentedControl } TouchBar // 三种视图模式单选模式 圆角样式 const viewMode new TouchBarSegmentedControl({ mode: single, segmentStyle: rounded, selectedIndex: 0, segments: [ { label: List }, { label: Grid }, { icon: nativeImage.createEmpty(), enabled: true } // 演示纯图标分段可换成真实 NativeImage ], change: (selectedIndex, isSelected) { console.log(view mode - index ${selectedIndex}, selected${isSelected}) // 模拟「切换到第 2 个分段后临时禁用第 0 个」 viewMode.segments [ { label: List, enabled: selectedIndex ! 2 }, { label: Grid }, { icon: nativeImage.createEmpty(), enabled: true } ] } }) // 多选模式标签筛选器 const tagFilter new TouchBarSegmentedControl({ mode: multiple, segmentStyle: capsule, segments: [ { label: TODO }, { label: DONE }, { label: LATER } ], change: (selectedIndex, isSelected) { console.log(tag ${selectedIndex} now ${isSelected ? ON : OFF}) } }) app.whenReady().then(() { const win new BrowserWindow({ width: 400, height: 300 }) win.loadURL(about:blank) win.setTouchBar({ items: [viewMode, tagFilter] }) })要点回顾viewMode.segments [...]整体赋值触发刷新是修改任何SegmentedControlSegment字段的唯一可靠方式enabled: false的段分会被原生层setEnabled:NO灰化buttons/single/multiple三种mode决定了isSelected回调语义selectedIndex越界不会报错动态调整分段数量后请自行保持索引一致。小结与文档索引SegmentedControlSegment只有labelstring可选、iconNativeImage可选、enabledboolean默认true三个字段是 TouchBarSegmentedControl 的segments数组元素类型定义见 docs/api/structures/segmented-control-segment.md修改分段内容必须整体替换segments数组直接改数组元素属性不触发刷新根源在 lib/browser/api/touch-bar.ts 的LiveProperty只监听赋值不监听深层变更字段的原生消费、默认值与越界行为可在 shell/browser/ui/cocoa/electron_touch_bar.mm 中逐行核对change回调的selectedIndex/isSelected参数则由 第 278290 行 的原生 target-action 生成整个 TouchBar 的挂载方式new TouchBar({ items })window.setTouchBar与实验性警告参见 docs/api/touch-bar.md其余 TouchBar 组件按钮、滑块、刮擦条等各有独立文档结构上同属TouchBarItem体系。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考