资讯动态

OHIF v3 扩展开发指南:深入理解 Commands Module 与 CommandsManager 命令系统

发布时间:2026/9/19 1:32:08 来源:尧图企业网站定制
OHIF v3 扩展开发指南深入理解 Commands Module 与 CommandsManager 命令系统【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers导读本文聚焦 OHIF Viewers v3 扩展体系中的Commands Module命令模块系统讲解扩展如何通过getCommandsModule向CommandsManager注册按上下文context作用域划分的命名命令以及这些命令如何被工具栏按钮、快捷键和渲染设置调用。读完本文你将掌握命令定义的完整字段、上下文调度与合并执行的行为规则、CommandsManager的公共 API并结合仓库真实源码如cornerstone-dicom-sr扩展获得可直接落地的命令模块编写范式。本文以 platform/docs/versioned_docs/version-3.11/platform/extensions/modules/commands.md 为骨架并结合 platform/core/src/classes/CommandsManager.ts 与 platform/core/src/extensions/ExtensionManager.ts 的源码实现进行纵深展开。Commands Module 概览CommandsModule本质上是一组任意函数arbitrary functions的清单。这些函数可以激活某个工具activate tools、与服务器通信communicate with a server、弹出模态框open a modal等——命令的语义完全由扩展作者定义。OHIF v2 与 v3 在命令体系上有一个显著区别在 v3 中mode 负责定义自己的工具栏toolbar并且每个工具在toolDefinition中声明它调用的是哪些命令。也就是说命令的接线工作从扩展内部转移到了 mode 配置层面扩展只负责把命令注册好具体何时调用、绑定到哪个 UI 由 mode 决定。扩展通过定义getCommandsModule方法来注册一个 Commands Module。该方法返回的对象允许我们注册一个或多个作用域scoped到特定 context 的命令。命令具备几个使其极其强大的独特特性同一个命令名可以拥有多个实现multiple implementations具体执行哪个实现取决于应用当前的context只有正确的实现会被运行命令可被**快捷键hotkeys、工具栏按钮toolbar buttons和渲染设置render settings**复用。一个最简单的命令模块示例const getCommandsModule () ({ definitions: { exampleActionDef: { commandFn: ({ param1 }) { console.log(param1s value is: ${param1}); }, // storeContexts: [viewports], options: { param1: param1 }, context: VIEWER, // optional }, }, defaultContext: ACTIVE_VIEWPORT::DICOMSR, });由 Commands Module 返回的每个definition都会被注册到ExtensionManager持有的CommandsManager上。当扩展对象中包含getCommandsModule方法时ExtensionManager.registerExtension会在注册流程中命中MODULE_TYPES.COMMANDS分支调用_initCommandsModule完成注册见 ExtensionManager.ts 与 #L640-L671。v3 的重要变更storeContexts已在 OHIF-v3 中被移除。现在所有模块都可以访问全部命令和全部服务这一改变为用户自定义注册的服务user-registered services提供了支持。命令定义Command Definitions命令定义由两部分组成一个命名命令下例中的exampleActionDef和其commandFn。命令名用于调用命令commandFn则是真正被执行的那个命令动作。exampleActionDef: { commandFn: ({ param1, options }) { }, options: { param1: measurement }, context: DEFAULT, }属性类型描述commandFnfunc命令被运行时调用的函数。接收options和storeContextsv3 中为合并后的参数对象。optionsobject可选在调用时传给commandFn的参数。contextstring[] 或 string可选覆盖defaultContext用于告知系统该命令当前是否可运行。定义与注册时的几种写法从 CommandsManager.ts 与 ExtensionManager.ts 的源码可以看到注册环节做了多处兼容处理定义可以是函数如果某个definitions[name]直接是一个函数ExtensionManager会将其包装为{ commandFn: commandDefinition }注册时的函数简写CommandsManager.registerCommand在收到纯函数定义时也会自动包装为{ commandFn: definition, options: {} }context 不存在时自动创建若某个定义显式声明了context而该 context 尚未创建_initCommandsModule会调用createContext先创建它默认 context 兜底定义未声明context时使用模块级defaultContext模块也未声明defaultContext时最终兜底为字符串VIEWER见 ExtensionManager.ts。另外registerCommand内部对contextName与commandName做了__proto__、constructor、prototype等危险键的校验见 CommandsManager.ts以防止原型链污染prototype pollution这是安全层面的一个细节。命令执行行为Command Behavior命令是按上下文查找并执行的因此会出现两种典型边界情况当应用当前活跃上下文中存在多个有效命令时行为所有命令都会被运行all commands are run适用场景例如一个clearData命令希望同时清理多个扩展的各自状态——每个扩展在各自的 context 中注册同名命令触发一次即可全局清理。当应用当前活跃上下文中没有任何有效命令时行为控制台会打印一条警告a warning is printed to the console适用场景例如快捷键invert反色在 PDF 或 HTML 视口下没有意义——当前上下文没有注册该命令系统仅告警而不崩溃。对应到源码runCommand中命令未找到会执行log.warn(Command ${commandName} not found in current context)未定义 commandFn则告警No commandFn was defined for command ...随后静默返回见 CommandsManager.ts。命令参数是如何合并的runCommand执行前会进行参数合并const commandParams Object.assign( {}, definition.options || {}, // 命令配置定义时声明的静态参数 options // 调用时刻调用方传入的动态参数如鼠标事件 );这意味着调用时传入的options会覆盖定义中的同名静态options。这是定义时配置默认值、调用时按需覆盖这一设计的关键实现见 CommandsManager.ts。CommandsManager 公共 API如果希望在消费方应用consuming app或扩展中运行某个命令可以直接使用CommandsManager.runCommand(commandName, options {}, contextName)。// 返回指定上下文下的所有命令定义 commandsManager.getContext(string); // 运行一个命令将运行所有上下文中名为 speak 的命令 commandsManager.runCommand(speak, { command: hello }); // 指定在 DEFAULT 上下文中运行命令 commandsManager.runCommand(speak, { command: hello }, [DEFAULT]);供 ExtensionManager 内部使用的方法ExtensionManager负责注册命令与创建上下文因此大部分消费方并不需要直接调用下面的方法。官方文档也提醒如果你发现自己正在使用这些方法请反问自己为什么我不能通过扩展来注册这些命令// 供 ExtensionManager 注册新命令 commandsManager.registerCommand(context, name, commandDefinition); // 创建新上下文若已存在则清空该上下文 commandsManager.createContext(string);批量命令执行run与runAsync除了逐条runCommandCommandsManager还提供了批量执行入口run(input, options)与异步版本runAsync(input, options)。input的形态非常灵活见 CommandsManager.ts 中的convertCommands与validate单个字符串updateMeasurement复杂命令对象{ commandName: displayWhatever }数组混合[updateMeasurement, { commandName: displayWhatever }]命令集包装{ commands: updateMeasurement }或{ commands: [updateMeasurement, { commandName: displayWhatever }] }这些写法可以自由混用极大简化了一组命令的声明。run会依次执行所有命令并返回最后一条命令的结果单条时直接返回该结果。对应的类型定义见 platform/core/src/types/Command.tsSimpleCommand字符串、ComplexCommand{ commandName, commandOptions?, context? }以及Commands{ commands: RunCommand }。实际项目中commandsManager.run(initializeSegmentLabelTool, { tools })这类批量调用就出现在 modes/basic/src/initToolGroups.ts 中。模式级命令注册registerCommandsModule除了扩展注册路径ExtensionManager还暴露了registerCommandsModule(commandsModule, defaultContext VIEWER)用于注册脱离扩展注册流程产生的命令模块——典型场景是 mode 自身提供的getCommandsModule在应用初始化worklist 阶段时注册使其命令在任何 mode 路由进入之前就可被使用见 ExtensionManager.ts。上下文Contexts由谁定义上下文定义存在哪些可能的上下文、哪些当前处于活跃状态是消费方应用consuming app的职责。扩展重度依赖这些上下文。常见的上下文示例见 extensions/index.md路由RouteROUTE:VIEWER、ROUTE:STUDY_LIST活跃视口Active ViewportACTIVE_VIEWPORT:CORNERSTONE、ACTIVE_VIEWPORT:VTK扩展模块可以利用这些上下文表达诸如仅当当前活跃视口是 Cornerstone 视口时才显示该工具栏按钮的诉求从而让 UI 与行为随当前上下文动态切换。同名命令、不同实现例如旋转活跃视口这一行为每个支持该行为的 Viewport 模块都可以注册同名的命令但作用域到各自的上下文。当命令被触发时系统依据活跃上下文选择恰当的旋转实现——这就是多实现调度multiple implementations的典型用法。从源码结构看CommandsManager内部用contexts对象按名字存放各上下文并用contextOrder数组记录上下文创建的逆序作为未显式指定上下文时的默认查找顺序见 CommandsManager.ts。官方指引由于扩展对上下文依赖很深OHIF 团队表示后续会发布关于如何创建上下文、如何覆盖扩展定义的上下文的更多指引。如果你想讨论上下文工作方式的潜在改进可以通过创建 GitHub issue 的方式参与讨论详见原文档的说明。实战剖析cornerstone-dicom-sr 扩展的命令模块我们以仓库中真实维护的 extensions/cornerstone-dicom-sr/src/commandsModule.ts 为例看一个完整的生产级 Commands Module 长什么样const commandsModule (props) { const { servicesManager, extensionManager, commandsManager } props; const { customizationService } servicesManager.services; const actions { storeMeasurements: async ({ measurementData, dataSource, additionalFindingTypes, options {} }) { // 生成并存储结构化报告SR的完整业务逻辑 const storeFn commandsManager.runCommand(createStoreFunction, { dataSource, defaultFileName: dicom-sr.dcm, }); // ... }, hydrateStructuredReport: ({ displaySetInstanceUID }) { return hydrateStructuredReport( { servicesManager, extensionManager, commandsManager }, displaySetInstanceUID ); }, }; const definitions { storeMeasurements: actions.storeMeasurements, hydrateStructuredReport: actions.hydrateStructuredReport, }; return { actions, definitions, defaultContext: CORNERSTONE_STRUCTURED_REPORT, }; };这个示例揭示了几个关键实践getCommandsModule可以是函数它接收{ servicesManager, extensionManager, commandsManager }等运行时依赖见 ExtensionManager.ts 中调用getCommandsModule的方式从而在命令内访问服务definitions与actions分离actions存放实现definitions对外暴露命令名到实现的映射结构清晰命令可以调用其他命令storeMeasurements内部通过commandsManager.runCommand(createStoreFunction, ...)复用数据源扩展提供的命令展示了命令间组合显式声明defaultContext: CORNERSTONE_STRUCTURED_REPORT把整组 SR 相关命令作用域到结构化报告上下文。该扩展在 extensions/cornerstone-dicom-sr/src/index.tsx 中通过getCommandsModule,直接挂载到扩展对象上。类似的模式还可见于tmtv扩展——它在 extensions/tmtv/src/index.tsx 中把servicesManager、commandsManager、extensionManager作为参数传入commandsModule工厂函数。命令与工具栏、快捷键的联动理解命令模块后还需知道命令在哪里被消费工具栏按钮mode 的工具栏定义toolbar buttons中通过commands字段引用命令。当按钮被点击时ToolbarService调用commandsManager.run(...)执行对应命令快捷键HotkeysManager将按键与commandName绑定按键触发即执行对应命令工具定义v3 中每个工具在toolDefinition里声明其调用的命令这也是 v3 与 v2 架构差异的直接体现。CommandsManager在应用启动时由ExtensionManager构造注入见 ExtensionManager.ts并且会在onModeEnter/onModeExit生命周期中随扩展一起被传入供扩展在 mode 切换时初始化或清理与命令相关的状态。小结Commands Module 是 OHIF v3 扩展体系中连接功能实现与UI 触发的枢纽扩展通过getCommandsModule返回{ definitions, defaultContext }注册命令命令定义由commandFn执行函数与options默认参数构成context决定其作用域同一个命令名可在不同上下文注册多个实现执行时按当前活跃上下文选择并执行全部匹配实现调用方通过CommandsManager.runCommand、run、runAsync触发命令定义中的options与调用时传入的options会合并后者优先上下文如ROUTE:VIEWER、ACTIVE_VIEWPORT:CORNERSTONE由消费方应用维护是命令调度的核心机制。无论是编写自己的扩展命令模块、在 mode 中接线工具栏按钮还是理解 OHIF 快捷键与工具调用的底层链路本文所涉及的 commands.md、CommandsManager.ts 与 ExtensionManager.ts 都是最直接的参考入口。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价