首页
/ Electron TouchBarColorPicker 详解:在 macOS Touch Bar 上构建原生色选择器的完整指南

Electron TouchBarColorPicker 详解:在 macOS Touch Bar 上构建原生色选择器的完整指南

2026-09-06 17:57:20作者:瞿蔚英Wynne

Touch Bar 是 Electron 为原生 macOS 应用提供的一套控件 API,其中 TouchBarColorPicker 负责在 Touch Bar(或 Touch Bar 模拟器)上呈现一个系统级颜色选择器,并能在用户选定颜色时回调主进程。本文以 TouchBarColorPicker 官方文档 为主体,结合 Electron 主进程 JS 层实现 touch-bar.ts 与 macOS 原生层实现 electron_touch_bar.mm,完整讲清构造参数、实例属性的更新机制、事件回调链路,以及如何把它挂到窗口上并在无 Touch Bar 硬件时通过模拟器调试。

需要特别说明两点适用前提:

  • TouchBarColorPicker 不从 'electron' 模块直接导出,只能通过 TouchBar.TouchBarColorPicker 静态属性获取,或者作为其他 API 的返回值使用;
  • 整个 Touch Bar API 目前仍标记为实验性(experimental),未来版本可能变更或移除。

一、类的定位:TouchBar 家族中的颜色选择器

Touch Bar API 的入口是 TouchBar 类,它通过一组静态属性暴露全部子控件类型,包括 TouchBarButtonTouchBarColorPickerTouchBarGroupTouchBarLabelTouchBarPopoverTouchBarScrubberTouchBarSegmentedControlTouchBarSliderTouchBarSpacerTouchBarOtherItemsProxy 等。这一点可以在源码中得到确认:

// lib/browser/api/touch-bar.ts
static TouchBarButton = TouchBarButton;
static TouchBarColorPicker = TouchBarColorPicker;
static TouchBarGroup = TouchBarGroup;
// ...

TouchBar 静态属性定义。这也是为什么文档明确写着该类 "not exported from the 'electron' module"——它是嵌套在 TouchBar 上的二级导出。典型用法是先解构:

const { TouchBar } = require('electron')
const { TouchBarColorPicker } = TouchBar

TouchBar 主文档 给出了完整的调用范式:创建一个 TouchBar 实例,把若干子控件放入 items 数组,最后通过 BrowserWindow.setTouchBar 把整个 touch bar 绑定到窗口。TouchBarColorPicker 就是 items 数组中允许出现的一种元素类型。

二、构造函数与完整参数

TouchBarColorPicker 的构造函数只有一个参数 options 对象:

new TouchBarColorPicker(options)

参数定义(与 官方文档 一致,此处补充源码层面行为说明):

参数 类型 是否必填 说明
availableColors string[] 可选 一组十六进制颜色字符串(如 '#ABCDEF'),将作为选择器中可挑选的候选颜色
selectedColor string 可选 选择器当前选中的十六进制颜色,格式如 '#ABCDEF'
change Function 可选 用户选定颜色时的回调,收到 colorstring)参数,即用户选择的十六进制颜色

一个最小但完整的实例化示例:

const { TouchBar } = require('electron')
const { TouchBarColorPicker } = TouchBar

const picker = new TouchBarColorPicker({
  availableColors: ['#FF0000', '#00FF00', '#0000FF', '#888888'],
  selectedColor: '#FF0000',
  change: (color) => {
    console.log('用户在 Touch Bar 上选择了颜色:', color)
  }
})

源码视角:参数在 JS 层如何落地

所有 Touch Bar 控件都继承自 TouchBarItem 抽象基类(定义见 touch-bar.ts),构造函数把传入的 options 存到私有字段 _config 中:

// lib/browser/api/touch-bar.ts
constructor(config: ConfigType) {
  super()
  this._config = this._config || config || ({} as ConfigType)
  (this as any)[hiddenProperties] = {}
  const hook = (this as any)._hook
  if (hook) hook.call(this)
  delete (this as any)._hook
}

TouchBarColorPicker 自身只声明了三类属性:

// lib/browser/api/touch-bar.ts
class TouchBarColorPicker
  extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions>
  implements Electron.TouchBarColorPicker
{
  @ImmutableProperty(() => 'colorpicker')
  type!: string;

  @LiveProperty<TouchBarColorPicker>((config) => config.availableColors)
  availableColors!: string[];

  @LiveProperty<TouchBarColorPicker>((config) => config.selectedColor)
  selectedColor!: string;

  @ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) =>
    typeof onChange === 'function'
      ? (details: { color: string }) => {
          setInternalProp('selectedColor', details.color);
          onChange(details.color);
        }
      : null
  )
  onInteraction!: Function | null;
}

TouchBarColorPicker 类实现。这里有三个值得注意的实现细节:

  1. type 被固定为 'colorpicker',且标记为 @ImmutableProperty——任何后续对 type 的赋值都会抛出 Cannot override property type 异常。这个类型字符串是 JS 层与原生层(C++/Objective-C)之间的协议标识,下文会看到原生代码正是靠它分派更新逻辑。
  2. availableColorsselectedColor 都是 @LiveProperty(活属性):构造时从 _config 中取值初始化,之后每次对外部赋值都会触发 onMutate 副作用并派发 change 事件。这正是文档所说 "Changing this value immediately updates the color picker in the touch bar" 的机制来源(见第四节)。
  3. change 回调被包装成了 onInteraction:注意包装逻辑里先执行 setInternalProp('selectedColor', details.color),再调用用户的 onChange。也就是说,原生端报回一个新颜色时,Electron 会自动把实例的 selectedColor 同步为新值,无需你在回调里手动再赋值。这一点在文档中并未明说,但源码可以确认。

回调签名与交互链路

change: (color: string) => voidcolor 参数来自原生端上报的 details.color。在 macOS 原生实现中,色选择器控件的动作处理函数把系统控件上的 NSColor 转换为 RGB 十六进制字符串后发回窗口:

// shell/browser/ui/cocoa/electron_touch_bar.mm
- (void)colorPickerAction:(id)sender {
  NSString* item_id = [self idFromIdentifier:identifier
                                  withPrefix:ColorPickerIdentifier];
  NSColor* color = ((NSColorPickerTouchBarItem*)sender).color;
  std::string hex_color =
      electron::ToRGBHex(skia::NSDeviceColorToSkColor(color));
  base::DictValue details;
  details.Set("color", hex_color);
  window_->NotifyTouchBarItemInteraction(base::SysNSStringToUTF8(item_id),
                                         std::move(details));
}

colorPickerAction 实现。因此 change 回调收到的 color 始终是形如 '#RRGGBB' 的十六进制字符串,即使你在 availableColors 中使用了带透明度或其他格式的颜色,回调返回值也会被归一化为 RGB hex。

JS 侧的完整分派路径是:窗口收到 -touch-bar-interaction 事件后,TouchBar_addToWindow 中注册的监听器按 itemID 找到对应实例并调用其 onInteraction

// lib/browser/api/touch-bar.ts
const interactionListener = (_: any, itemID: string, details: any) => {
  let item = this.items.get(itemID);
  // ...
  if (item != null && item.onInteraction != null) {
    item.onInteraction(details);
  }
};
window.on('-touch-bar-interaction', interactionListener);

交互事件监听。每个 TouchBarItem 构造时会分配自增 idnextItemID++),change 回调依赖的就是这套 ID 路由机制。

三、实例属性:availableColors 与 selectedColor

TouchBarColorPicker 实例上暴露两个可读写属性:

touchBarColorPicker.availableColors

string[],表示选择器可供选择的颜色列表。文档明确:修改该值会立即更新 Touch Bar 上的色选择器。

从 JS 层看它是 LiveProperty,赋值即触发 change 事件;从原生层看,刷新逻辑会读取 settings 中的 availableColors 数组,逐个解析为 NSColor 并构建 NSColorList 挂到控件上:

// shell/browser/ui/cocoa/electron_touch_bar.mm
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
             withSettings:(const gin_helper::PersistentDictionary&)settings {
  std::vector<std::string> colors;
  if (settings.Get("availableColors", &colors) && !colors.empty()) {
    NSColorList* color_list = [[NSColorList alloc] initWithName:@""];
    for (size_t i = 0; i < colors.size(); ++i) {
      [color_list insertColor:[self colorFromHexColorString:colors[i]]
                          key:base::SysUTF8ToNSString(colors[i])
                      atIndex:i];
    }
    item.colorList = color_list;
  }

  std::string selectedColor;
  if (settings.Get("selectedColor", &selectedColor)) {
    item.color = [self colorFromHexColorString:selectedColor];
  }
}

updateColorPicker 实现。由此可确认两个实操要点:

  • 只有当 availableColors 非空时才会设置 NSColorList;如果留空或不传,控件展示系统默认色板;
  • 每次刷新都会重建整个色板,因此把 availableColors 重新赋一个新数组就是标准的"动态换色板"方式。

touchBarColorPicker.selectedColor

string,十六进制颜色码,表示当前选中的颜色。同样支持随时赋值并立即生效。注意上一节提到的自动同步:当用户在 Touch Bar 上选色触发 change 时,内部已经帮你把 selectedColor 更新为新值,这保证了程序状态与 UI 呈现始终一致,避免手动同步出错。

四、更新机制:为什么赋值"立即生效"

理解 LiveProperty 的设计就能解释文档中反复出现的 "Changing this value immediately updates the color picker in the touch bar":

// lib/browser/api/touch-bar.ts
const LiveProperty = (...) =>
  (target: T, propertyKey: keyof T) => {
    // ...
    Object.defineProperty(target, propertyKey, {
      get: function () {
        return this[hiddenProperties][propertyKey];
      },
      set: function (value) {
        if (onMutate) onMutate(this as any, value);
        this[hiddenProperties][propertyKey] = value;
        this.emit('change', this);
      },
      enumerable: true
    });
  };

即属性赋值 → 触发实例 change 事件。而 TouchBar 在构造时给每个 item 注册了监听:

const registerItem = (item: TouchBarItem<any>) => {
  this.items.set(item.id, item);
  item.on('change', this.changeListener);
  // ...
};
// ...
private changeListener = (item: TouchBarItem<any>) => {
  this.emit('change', item.id, item.type);
};

change 事件链。当 touch bar 已绑定窗口时,_addToWindow 里注册的 changeListener 会进一步调用 window._refreshTouchBarItem(itemID),最终到达原生端的 refreshTouchBarItem:withType:withSettings: 分派——对 colorpicker 类型即调用上面的 updateColorPicker:。整条链路为:

picker.selectedColor = '#00FF00'
属性赋值(LiveProperty setter)
  → item.emit('change')
  → TouchBar 转发 'change'(携带 item.id 与 item.type)
  → 窗口 _refreshTouchBarItem(id)
  → 原生 refreshTouchBarItem → updateColorPicker(读取 availableColors / selectedColor 更新 NSColorPickerTouchBarItem)

这套机制对所有 Touch Bar 控件通用,也意味着在 TouchBarGroupTouchBarPopover 这类容器里嵌套的 color picker 同样会被刷新——原生端专门处理了父容器场景,会沿着 _parents(JS 层由 _addParent/_removeParent 维护)遍历 popover / group 的子 touch bar 递归刷新,见 refreshTouchBarItem 的父容器处理

五、实战:把色选择器挂到窗口上

TouchBarColorPicker 本身不能独立存在,必须作为 items 的一员放入 TouchBar,再用 window.setTouchBar 绑定到 BrowserWindow。下面给出一个可直接运行的完整示例(主进程):

const { app, BrowserWindow, TouchBar } = require('electron')

const { TouchBarColorPicker, TouchBarLabel } = TouchBar

// 主题色板:模拟一个"窗口背景色"选择器
const palette = ['#000000', '#FFFFFF', '#F00', '#0F0', '#00F',
                 '#FF0', '#0FF', '#F0F']

let current = '#0000FF'

const status = new TouchBarLabel({
  label: `当前颜色: ${current}`,
  textColor: current
})

const picker = new TouchBarColorPicker({
  availableColors: palette,
  selectedColor: current,
  change: (color) => {
    // 注意:picker.selectedColor 已被框架自动同步,这里只需更新 UI
    current = color
    status.label = `当前颜色: ${color}`
    status.textColor = color
  }
})

const touchBar = new TouchBar({
  items: [picker, status]
})

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 480, height: 320 })
  win.loadFile('index.html')
  win.setTouchBar(touchBar)
})

示例要点:

  • new TouchBar({ items: [...] })items 中不允许重复添加同一个 item 实例,也不允许一个 touch bar 里出现多个 OtherItemsProxyTouchBar 构造函数会做校验并抛错(见下文测试证据);
  • setTouchBar(null) 可以移除 touch bar;对一个窗口重复调用 setTouchBar 会用新的 touch bar 替换旧的,源码中 TouchBar._setOnWindow 会先把旧 touch bar _removeFromWindow,见 替换逻辑
  • change 回调中无需手动回写 picker.selectedColor,框架已替你完成(见第二节 onInteraction 包装逻辑)。

运行与调试:没有 Touch Bar 硬件怎么办

TouchBar 主文档 给出的运行步骤是:

  1. 将示例保存为 touchbar.js
  2. npm install electron
  3. 执行 ./node_modules/.bin/electron touchbar.js

对于没有配备 Touch Bar 的 MacBook,可以使用系统自带的 Touch Bar 模拟器:在"系统设置 → 键盘"中开启"在菜单栏中显示 Touch Bar 控制",或运行"控制条"(Control Strip)App,屏幕上就会显示 Touch Bar 的镜像,可以直接点击色选择器验证 change 回调。也可以在 Electron 开发者工具中以 F18(Control Strip)等快捷方式呼出。

测试用例中的标准用法

Electron 自身的集成测试展示了 TouchBarColorPicker 在完整 items 列表中的位置,可作为用法参照:

// spec/api-touch-bar-spec.ts
new TouchBarColorPicker({ selectedColor: '#F00', change: () => {} }),

api-touch-bar-spec.ts。同一测试文件还验证了 TouchBar 的构造约束,例如 new TouchBar() 不传参数会抛出 Must specify options object as first argument、items 中混入非法值会抛出 Each item must be an instance of TouchBarItem——编写自定义 items 时应保证每个元素都是 TouchBar 静态属性构造出的实例。

六、关键实现细节速查

主题 结论 依据
导出方式 不经 'electron' 顶层导出,用 TouchBar.TouchBarColorPicker touch-bar.ts 静态属性
控件类型标识 type 固定为 'colorpicker',只读 类定义
change 参数格式 十六进制 RGB 字符串('#RRGGBB'),由原生端 ToRGBHex 归一化 colorPickerAction
选中色自动同步 原生上报后框架先内部更新 selectedColor 再调用用户回调 onInteraction 包装
色板刷新 availableColors 非空时重建 NSColorList 并整体替换 updateColorPicker
更新触发点 任一 LiveProperty 赋值 → item change → 窗口刷新原生控件 LiveProperty 与 changeListener
平台 仅 macOS;整个 Touch Bar API 属实验性 API TouchBar 文档说明

七、小结

TouchBarColorPicker 的 API 面很小——一个 options 对象加两个实例属性——但把它放进 Electron 的整体 Touch Bar 架构里看,它完整演示了"JS 声明式状态 → 事件驱动刷新 → 原生控件更新 → 交互事件按 ID 路由回 JS"的双向数据流:

  • 写侧:availableColors / selectedColor 赋值经 LivePropertychange 事件 → window._refreshTouchBarItem → 原生 updateColorPicker:
  • 读侧:NSColorPickerTouchBarItemcolorPickerAction: 把用户选色转成 hex 字符串上报,按 item id 路由到 onInteraction,自动回写 selectedColor 后调用你的 change 回调。

掌握这条链路后,无论是动态更换色板、与 TouchBarLabel 组合展示当前值,还是在 TouchBarGroup / TouchBarPopover 容器中嵌套色选择器,都可以基于同一套机制平滑扩展。需要注意的是 Touch Bar API 仍为实验特性,且仅适用于 macOS 平台,跨平台产品应做好特性检测与降级方案。

登录后查看全文
热门项目推荐
相关项目推荐