Serial Studio 仪表盘 Widget 标题覆盖与冻结标题模式实战解析Spec 0013【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本篇技术指南围绕 Serial Studio 开源遥测仪表盘项目中的0013-widget-title-overrides规格展开完整讲解「每个仪表盘 Widget 独立显示标题display-title override」与「冻结模式下标题栏可见性freeze-titlebar visibility」的设计动机、两级作用域解析规则、三态冻结标题模型、源码实现路径、持久化格式与 API 用法。读完本文你将能掌握如何在不改动数据集/分组规范标题的前提下为单个 Widget 或整个数据集/分组定制操作员可读的显示名称如何让冻结模式下的仪表、柱状图等仪表型 Widget 按需显示或隐藏标题以及如何通过project.*API 以编程方式完成同样的操作。背景与动机规范标题不是显示标题Serial Studio 仪表盘上每一个 Widget 所显示的名称——包括窗口标题栏、冻结模式freeze mode见规格 0007下的面板标题栏以及 Bar、Gauge、Meter 等仪表型 Widget 在仪器表面绘制painted的标题条——都直接、实时地读取项目中的规范canonical数据集或分组标题。换言之历史上若想改变某个 Widget 窗口被称呼的名字唯一的途径是重命名数据集或分组本身。问题在于规范标题同时是多处身份标识的唯一来源CSV/MDF4 导出的列头、API 响应、变换脚本transform script查找、项目编辑器树等全部以它为准。当一个操作员屏幕需要显示 Chamber Pressure而仪器通道的工程名称是 PT-01-RAW 时用户没有任何办法在不污染导出与脚本的前提下获得一个可读的显示名。冻结模式同样存在一个硬编码缺口仪表盘冻结后窗口标题隐藏取而代之的是一个面板风格的冻结标题栏——唯独 Bar、Gauge、Meter 这三种在 Widget 类型层面选择退出因为它们自己绘制标题。用户无法干预任何一种行为冻结的 Gauge 永远无法显示面板标题栏冻结的 Plot 永远无法隐藏它两者也都只能显示规范标题。在密集的仪表面板场景冻结模式正是为此设计的下「每个 Widget 自主决定是否显示标题栏、显示什么文字」正是可读操作员屏幕与杂乱屏幕之间的分水岭。核心设计两级覆盖作用域与解析规则规格 R1 定义了标题覆盖的两个作用域scope项目文件会持久化这两级映射作用域含义键格式典型场景Widget 级widget-level特定 Widget 类型 特定数据集/分组例如某个数据集的 FFT 视图widgetType:uniqueId同一数据集同时以 Plot FFT Waterfall 展示时为每个视图单独命名实体级entity-level某个数据集/分组的所有WidgetuniqueId数据集所有展示统一改名一处生效解析优先级Widget 级优先 → 实体级次之 → 规范标题兜底即今天的行为。WidgetMapBuilder.cpp中的applyDisplayTitles()用一段 lambda 链精确实现该规则见 WidgetMapBuilder.cppconst auto widgetOverride { return overrides.value(token QLatin1Char(:) QString::number(uniqueId)).toString(); }; const auto entityResolve { const auto over overrides.value(QString::number(uniqueId)).toString(); if (!over.isEmpty()) return over; return canonical.value(uniqueId, current); }; const auto resolve { const auto scoped widgetOverride(token, uniqueId); return scoped.isEmpty() ? entityResolve(uniqueId, current) : scoped; };两个补充语义值得注意空字符串覆盖等价于「不存在」stageDisplayTitle()在写入空标题时会直接从映射中删除该键见 ProjectPresentation.cpp因此「清空」是移除覆盖、恢复规范标题的标准操作。扩展 Widget 的特例扩展extensionWidget 共享同一个枚举值无法用纯数字键区分因此使用ext:packageId:uniqueId形式作为 Widget 级键package id 取自实体自身的 widget 字符串见 ProjectPresentation.cpp 的extension_scope_key()。冻结标题模式三态显式模型规格 R4 引入每个 Widget 持久化的冻结标题模式freeze-title mode只允许三种显式取值不存在 auto模式含义默认适用bar冻结时显示面板式标题栏除 Bar/Gauge/Meter 外的所有 Widget 类型painted标题绘制在仪表仪器表面仅 Bar/Gauge/Meter且为其默认值hidden冻结时任何位置都不显示标题无选择 Widget 自身的类型默认值等价于删除其存储条目映射保持干净。写入非法模式会被setFreezeTitleMode()直接拒绝见 ProjectPresentation.cpp只有bar、hidden、以及对会绘制标题的仪器才允许的painted三种值可被接受。R5 的名称「最多出现一次」约束对 Bar、Gauge、Meter 而言该模式同时约束其自身绘制的标题hidden产生完全无标签的仪器表面bar抑制绘制标题任何模式下同一个 Widget 的可能被覆盖的名称最多出现一次绝不同时出现在面板标题栏和仪器绘制条上。QML 侧的门控见 Bar.qmlreadonly property bool titleFrozenOut: windowRoot windowRoot.frozen true windowRoot.effectiveFreezeTitle ! painted readonly property string displayTitle: windowRoot windowRoot.title ? windowRoot.title : model.titleWidget 委托WidgetDelegate.qml解析生效模式并据此计算冻结标题栏可见性WidgetDelegate.qmlreadonly property string effectiveFreezeTitle: freezeTitleMode ! ? freezeTitleMode : (paintsOwnTitle ? painted : bar) readonly property bool frozenHeaderVisible: root.frozen root.effectiveFreezeTitle bar root.title.length 0这里 QML 层的回退默认值只覆盖 QuickPlot 等无项目场景一旦项目模型可用freezeTitleMode由Cpp_JSON_ProjectModel.freezeTitleMode(widgetType, uniqueId)解析并监听widgetDisplayChanged信号实时刷新。显示表面 vs 非显示表面覆盖的边界标题覆盖是**纯展示层presentation-only**的规格用 R2/R3 明确划定了生效边界R2 — 遵循覆盖的显示表面Widget 窗口标题栏、冻结模式面板标题栏、Bar/Gauge/Meter 的绘制标题条与表面内标题、任务栏条目、仪表盘 Widget 搜索、以及弹出popped-out的外部 Widget 窗口标题。R3 — 忽略覆盖的非显示表面CSV/MDF4/Session 导出、API 命令响应、变换脚本与数据表查找、控制台输出、项目编辑器树全部继续使用规范标题。这是 R3 契约它保证了既有消费者工具以标题为身份键的导出、脚本查找、别名、API 身份不受任何影响。集成测试test_canonical_titles_unaffected正是从导出 JSON 和dashboard.getData实时帧中双重断言规范标题未被污染见 test_widget_display.py。源码实现路径ProjectPresentation 与 WidgetMapBuilder覆盖功能的核心实现在ProjectPresentation类中ProjectPresentation.h它持有四个与文档核心无关、位于撤销历史之外的「展示 blob」其中与本文相关的是m_widgetDisplaydisplay 覆盖 blob方法作用displayTitle(uniqueId)读取实体级覆盖widgetDisplayTitle(widgetType, uniqueId)读取 Widget 级覆盖freezeTitleMode(widgetType, uniqueId)读取冻结标题模式未设置时按类型回退默认值setDisplayTitle/setWidgetDisplayTitle写入两级标题覆盖setFreezeTitleMode写入三态冻结模式promptRenameWidget弹出重命名对话框并落为 Widget 级覆盖ProjectModel将这些方法以Q_INVOKABLE暴露给 QML 与 API 层ProjectModel.h。解析的编排时机标题解析只发生在 reconfigure/绑定时刻绝不在帧路径上这是「No per-frame cost」不变量。仪表盘构建时Dashboard调用m_widgetMapBuilder.applyDisplayTitles()当用户在运行中编辑覆盖时refreshDisplayTitles()就地修补 Widget 副本、把新标题推入 WidgetRegistry 并发出displayTitlesChanged既不重建仪表盘也不中断数据流见 Dashboard.cpp。持久化与身份稳定性uniqueId 键控规格 R9 规定覆盖一律以数据集/分组的uniqueId为键绝不使用位置的groupId/datasetId或标题字符串。持久化时整个 display blob 以widgetDisplay键写入项目文件内含titles与freezeTitle两个子对象见 ProjectPersistence.cpp{ widgetDisplay: { titles: { 10: Engine Speed, 7:10: RPM Spectrum }, freezeTitle: { 11:10: hidden } } }加载侧在 ProjectLoader.cpp 读取该键旧项目无此键时正常加载携带覆盖的新项目被旧版 Serial Studio 打开时未知键被忽略、不会崩溃loader tolerance。由于键是uniqueId分组/数据集重排、移动到其他分组、保存-关闭-重开均不影响覆盖的归属删除数据集/分组只会使条目无害地成为孤儿不崩溃、不把标题误用到其他条目上而重新导入会分配全新uniqueId允许覆盖被丢弃。实战操作一Widget 窗口内编辑R7每个仪表盘 Widget 窗口的标题栏左缘都有一个可见的菜单按钮替代了外部窗口按钮与早期隐藏的右键菜单点击后弹出widgetMenu见 WidgetDelegate.qml包含三类入口Rename Widget…调用Cpp_JSON_ProjectModel.promptRenameWidget(widgetType, uniqueId, currentTitle)弹出的输入框留空即恢复原始标题Freeze Title子菜单三个可勾选项Title Bar/Painted Title仅对会绘制自身标题的仪器可见/Hidden当前生效模式被勾选Open in External Window吸收原外部窗口按钮的功能。运行时模式runtime mode下这些编辑入口会被自动移除。编辑立即通过setFreezeTitleMode/setWidgetDisplayTitle写入项目模型所有受影响显示表面即时更新无需重载项目也不打断正在进行的流数据R8。实战操作二项目编辑器工作区编辑R6在项目编辑器的工作区视图workspace editor中每一行 Widget 对应两个编辑控件见 WorkspaceView.qml显示标题输入框TextField的text绑定modelData.displayTitleplaceholderText显示fallbackTitle实体级覆盖或规范标题onEditingFinished时调用setWidgetDisplayTitle(widgetType, uniqueId, text)落库留空即恢复默认。冻结标题模式下拉框ComboBox的选项依 Widget 是否绘制自身标题而定——Bar/Gauge/Meter 为[Title Bar, Painted Title, Hidden]其余为[Title Bar, Hidden]。行数据的displayTitle、fallbackTitle、freezeTitleMode字段由 EditorSummaries.cpp 按引用逐行装配遵循既有工作区编辑生命周期暂存修改 → 随项目保存 → 加载恢复。API 与自动化R10 对等接口project.dashboard.*命名空间暴露三个与 UI 对等的命令实现见 DashboardHandler.cpp声明见 DashboardHandler.h命令参数说明project.dashboard.setWidgetTitleuniqueId必填、widgetType可选提供则为 Widget 级、title空串即清除设置或清除两级覆盖要求 ProjectFile 模式且唯一 id 存在project.dashboard.getWidgetTitles无返回全部覆盖每行附scope、widgetType、canonical注解project.dashboard.setWidgetFreezeTitlewidgetType、uniqueId、mode模式仅接受bar/painted/hiddenpainted仅对 Bar/Gauge/Meter 合法错误处理遵循既有约定缺少参数返回MissingParam非 ProjectFile 模式返回OperationFailed未知uniqueId或非法模式返回InvalidParam。setWidgetTitle的响应会回显previous、canonical、cleared与scope方便调用方做幂等与审计。JS 绑定见 SerialStudio.jsLua 绑定位于 SerialStudio.lua。任何新增的变更型命令都需要通过安全层级注册命令安全清单见 command_safety.json。一个完整的自动化示例对应集成测试中的流程// 实体级覆盖数据集 10 的所有 Widget 显示 Engine Speed project.dashboard.setWidgetTitle(10, { title: Engine Speed }); // Widget 级覆盖仅数据集 10 的 FFT(7) 视图显示 RPM Spectrum project.dashboard.setWidgetTitle(10, { widgetType: 7, title: RPM Spectrum }); // 查询当前覆盖表 project.dashboard.getWidgetTitles(); // 冻结模式下隐藏数据集 10 的 Gauge(11) 标题 project.dashboard.setWidgetFreezeTitle(11, 10, hidden); // 清空覆盖恢复规范标题 project.dashboard.setWidgetTitle(10, { title: });验证与测试验收标准与集成测试规格的八项验收标准AC1–AC8均已勾选完成其中 AC1、AC3、AC7 由集成测试 test_widget_display.py 覆盖该测试需要启动 Serial Studio 并在「设置 → 杂项 → 启用 API Server」后运行AC1R1/R9覆盖经 API 设置后保存-重载可往返分组重排project.group.move后覆盖仍按uniqueId归属正确删除目标数据集后项目仍可加载、孤儿条目保持惰性canonical为 null。AC3R3设置覆盖后project.exportJson导出的分组/数据集标题仍是规范值dashboard.getData实时帧同样返回规范标题覆盖不可见。AC7R10set/get/clear全链路行为、Widget 级覆盖独立清除、未知uniqueId与非法模式如sideways、非仪器用painted被拒绝均以断言锁定。AC2/AC4–AC6 为应用内观察项标题覆盖同时作用于窗口标题、冻结标题栏、绘制标题、任务栏与弹出窗口默认行为与现状一致Plot 显示冻结标题栏、Gauge 不显示翻转每个开关可反向生效冻结模式下任何 Widget 绝不同时出现两个标题工作区编辑器行编辑会标记项目已修改、可保存恢复数据流运行中重命名即时更新所有表面。AC8 确认--benchmark-hotpath门禁不变——标题解析只发生在 reconfigure/绑定时刻256 kHz 基准不得回退。约束与不变量速查无逐帧开销标题解析在重建/绑定阶段完成绝不在帧路径上。规范标题保持规范任何以标题为键的系统导出、脚本查找、别名、API 身份都观察不到覆盖。只用稳定身份覆盖键为uniqueId数据集或分组绝不用位置 id 或标题字符串。解冻安全可见性标志只在冻结模式下生效正常模式的标题栏与工具栏策略不变解冻后永远恢复正常外观与标志状态无关。两种布局模式均生效自动布局与手动/自由布局、以及外部 Widget 窗口均遵循覆盖。加载器容错旧项目无覆盖键正常加载新项目被旧版打开不崩溃。模式作用域覆盖存于项目文件故在 ProjectFile 模式下生效QuickPlot/DeviceDefined 仪表盘再生帧不在范围内除非条目恰好匹配合成的uniqueId。延伸阅读规格全文0013-widget-title-overrides/spec.md展示层数据模型ProjectPresentation.h 与 ProjectPresentation.cpp覆盖解析编排WidgetMapBuilder.cpp、Dashboard.cppAPI 处理DashboardHandler.cpp集成测试test_widget_display.py冻结模式spec 0007相关Freeze 目录下冻结与仪表盘相关规格文档【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考