
OHIF 3.11 ToolbarService 迁移指南从 ViewportActionCornersService 到统一工具栏体系【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers导读本文是 OHIF v3.10 → v3.11 迁移指南的核心章节聚焦ToolbarService的重构与升级旧版ViewportActionCornersService及其配套 Provider/Hook 已被彻底移除视口角落Viewport Corner的功能项全面并入ToolbarService与标准工具栏组件体系。读完本文你将掌握register()/updateSection()新 API、props.buttonSection两种关联模式、增强版useToolbarHook 的状态管理能力、hideWhenDisabled自动隐藏逻辑以及IconPresentationProvider图标尺寸标准化方案从而顺利完成自定义扩展与模式的迁移。迁移背景为什么 ToolbarService 成为唯一入口在 OHIF 3.11 中工具栏体系经历了一次架构收敛。旧的ViewportActionCornersService、ViewportActionCornersProvider与useViewportActionCornersHook 被整体移除视口角落的 UI 元素不再由独立服务管理而是统一作为普通工具栏按钮纳入ToolbarService渲染时交由标准的Toolbar组件完成参考 viewport-action-menu.md 的并行说明。这一收敛的核心意义在于整个应用主工具栏 视口角落现在共享同一套按钮注册、分区、求值与状态刷新机制扩展开发者只需学习一套 API 即可覆盖全部工具栏场景。从源码实现看ToolbarService的状态结构十分清晰见 ToolbarService.tsstate: { // 全部按钮及其 props buttons: Recordstring, Button; // 按分区section组织的按钮 id 列表 buttonSections: Recordstring, string[]; }所有按钮先注册进buttons再通过分区 ID 关联进buttonSections组件按分区名拉取按钮列表渲染。预定义分区toolbarService.sections与TOOLBAR_SECTIONSToolbarService新增了sections属性提供预定义分区名的自动补全支持。其数据源是源码中导出的TOOLBAR_SECTIONS常量见 ToolbarService.ts分区类别分区名说明主工具栏primary/secondary顶部主/次工具栏视口角落viewportActionMenu.topLeft/.topRight/.bottomLeft/.bottomRight/.topMiddle/.bottomMiddle/.leftMiddle/.rightMiddle视口四角及四边中点共 8 个位置模式专属labelMapSegmentationToolbox、contourSegmentationToolbox、labelMapSegmentationUtilities、contourSegmentationUtilities、dynamic-toolbox、ROIThresholdToolbox由具体模式使用访问方式示例toolbarService.sections.viewportActionMenu.topLeft // viewportActionMenu.topLeft视口角落的位置同样映射到ButtonLocation枚举TopLeft到BottomRight共 8 个取值ToolbarService.getAlignAndSide()会根据角落位置返回{ align, side }用于菜单弹出方向定位见 ToolbarService.ts。核心 API 更名register 与 updateSectionToolbarService的两个核心方法更名语义更精确地反映其真实行为旧方法新方法说明addButtons(buttons)register(buttons, replace?)注册按钮定义replacetrue时可覆盖同名按钮createButtonSection(key, buttons)updateSection(key, buttons)更新已有分区若分区不存在则直接创建若存在则去重追加按钮 id旧方法目前仍可用但会在控制台打印弃用警告未来版本将移除源码中addButtons与createButtonSection均已标注deprecated并调用新方法见 ToolbarService.ts。迁移时应立即替换// Before - toolbarService.addButtons(toolbarButtons); - toolbarService.createButtonSection(primary, [Zoom, Pan]); // After toolbarService.register(toolbarButtons); toolbarService.updateSection(primary, [Zoom, Pan]);updateSection的去重逻辑在源码中明确实现如果分区已存在会过滤掉已包含的按钮 id 再追加见 ToolbarService.ts因此多次调用不会产生重复按钮。迁移视口角落项定义为标准工具栏按钮旧代码中通过viewportActionCornersService.addComponent({ viewportId, id, component, location })注册角落组件的做法已废弃。新做法是把角落项如方位菜单、窗宽窗位菜单、数据叠加菜单、模态加载徽标等定义为标准工具栏按钮注册后放入专用视口动作菜单分区。// BeforeviewportActionMenuCustomizations.ts 或直接使用 ViewportActionCornersService - viewportActionCornersService.addComponent({ - viewportId, id: orientationMenu, component: MyOrientationMenu, location: topLeft - }); // After在模式的 onModeEnter 或等价初始化逻辑中 const myViewportCornerButtons [ { id: orientationMenu, uiType: ohif.orientationMenu, // 或注册为 UI Type 的自定义组件 props: { /* ...props for your component... */ } }, // ...其他角落按钮 ]; toolbarService.register(myViewportCornerButtons); toolbarService.updateSection( toolbarService.sections.viewportActionMenu.topLeft, [orientationMenu, /* 其他按钮 id */] );OHIFViewportActionCorners 内部如何工作OHIFViewportActionCorners.tsx组件见 OHIFViewportActionCorners.tsx现在对每个角落内部使用Toolbar组件渲染并传入对应的分区名与ButtonLocationViewportActionCorners.TopLeft Toolbar buttonSectionviewportActionMenu.topLeft viewportId{viewportId} location{ButtonLocation.TopLeft} / /ViewportActionCorners.TopLeft值得注意的是该组件还使用了两个新增能力useViewportHover(viewportId)Hook 返回{ isHovered, isActive }只有视口被悬停或处于激活状态时才渲染角落工具栏shouldShowCorners isHovered || isActive实现悬停/激活才显示角落工具的交互Hook 实现见 useViewportHover.ts用IconPresentationProvider sizemedium IconContainer{ToolButton} containerProps{...}包裹全部角落工具栏保证图标尺寸与样式统一。自定义菜单类组件接收 Toolbar 下发的状态 props对于弹出层Popover类自定义组件无需再自行管理开关状态。Toolbar组件会从useToolbarHook 获取并下发isOpen、onOpen、onClose三个 props// Before自定义组件自行管理 open 状态 - const [isMenuOpen, setIsMenuOpen] useState(false); - const handleOpenChange (open) setIsMenuOpen(open); // After自定义组件接收 Toolbar 下发的 isOpen / onOpen / onClose function MyCustomMenuButton({ isOpen, onOpen, onClose, ...rest }) { const handleOpenChange (openState: boolean) { if (openState) { onOpen?.(); } else { onClose?.(); } }; return ( Popover open{isOpen} onOpenChange{handleOpenChange} {/* PopoverTrigger 与 PopoverContent */} /Popover ); }按钮与分区关联props.buttonSection的两种用法3.11 中groupIdprop如ohif.toolButtonList、ohif.toolBoxButtonGroup使用的普遍被直接使用buttonSection替代。register()源码对buttonSection有一个关键处理当它为布尔值true时会被自动改写为按钮自身的id见 ToolbarService.ts// 源码关键逻辑 if (button.props.buttonSection true) { button.props.buttonSection button.id; }由此衍生出两种关联方式方式 AbuttonSection: true隐式使用按钮自身 id 作为分区名// toolbarButtons.ts 中 ToolButtonList 组件的定义 { id: MeasurementTools, // 该 ToolButtonList 组件的 id uiType: ohif.toolButtonList, props: { buttonSection: true // 该列表渲染名为 MeasurementTools 的分区 } }之后即可用如下代码填充该列表toolbarService.updateSection(MeasurementTools, [Length, Bidirectional, /* ... */]);方式 BbuttonSection: customSectionName显式指定分区名当分区名需要与按钮 id 不同或偏好显式命名时使用// ToolButtonList 组件的定义 { id: MySpecialToolList, uiType: ohif.toolButtonList, props: { buttonSection: toolsForAdvancedUsers, // 该列表渲染 toolsForAdvancedUsers 分区 } }分区按钮的求值逻辑从源码看带buttonSection的按钮嵌套按钮在refreshToolbarState()中有专门的求值路径见 ToolbarService.ts先对分组本身执行evaluate可控制分组的disabled/disabledText再遍历分区内每个按钮逐一求值通过evaluationResults缓存避免重复求值保证工具栏状态一致性。增强版 useToolbar Hook完整状态管理 APIuseToolbarHook 现在返回完整的状态管理函数集覆盖三类需求实现见 useToolbar.tsx类别方法行为说明展开/关闭openItem/closeItem/isItemOpen管理下拉菜单/弹出层开关状态。openItem会先关闭其他所有项再打开目标项打开状态保存在本地 state避免每次交互都重算工具栏锁定lockItem/unlockItem/toggleLock/isItemLocked通过改写按钮 props 中的isLocked并触发refreshToolbarState生效显隐showItem/hideItem/toggleVisibility/isItemVisible通过改写按钮 props 中的isVisible控制显隐isItemVisible仅在isVisible false时返回 false交互onInteraction({ itemId, viewportId, event })现在接收itemId与viewportId。内部调用toolbarService.getButtonProps(itemId)聚合命令后交给recordInteraction执行同时触发refreshToolbarState刷新求值所有*Item方法的第二参数viewportId均可选缺省时自动取viewportGridService.getActiveViewportId()。状态刷新与事件订阅useToolbar内部订阅了TOOL_BAR_MODIFIED与TOOL_BAR_STATE_MODIFIED两个事件以刷新按钮列表同时订阅viewportGridService的ACTIVE_VIEWPORT_ID_CHANGED、VIEWPORTS_READY、LAYOUT_CHANGED事件——当活动视口变化或布局改变时自动调用toolbarService.refreshToolbarState({ viewportId })重新执行所有按钮的evaluate函数。这也是按钮可见性、禁用态能随视口切换实时更新的底层机制。测试佐证useToolbar的交互链路有单元测试覆盖见 useToolbar.test.ts测试验证了onInteraction会先调用event.stopPropagation()、通过getButtonProps(itemId)获取按钮命令并聚合选项命令、最终将命令与{ refreshProps: { viewportId } }一并交给toolbarService.recordInteraction执行同时保证按钮原始 props 不被修改两次交互后buttonProps.commands长度不变。evaluate 增强hideWhenDisabled自动隐藏按钮evaluate函数现在可以利用evaluateProps.hideWhenDisabled在按钮被禁用时自动隐藏。源码实现中refreshToolbarState的求值逻辑如下见 ToolbarService.tsconst hideWhenDisabled evaluateProps?.hideWhenDisabled || props.hideWhenDisabled; // 可见性优先级evaluate 返回的 visible hideWhenDisabled 与 disabled 的组合判断 const visible evaluated?.visible undefined ? hideWhenDisabled evaluated?.disabled ? false : true : evaluated?.visible;配置方式{ id: someTool, uiType: ohif.toolButton, props: { commands: /* ... */, evaluate: evaluate.someTool, // 求值函数字符串名、数组或对象形式均可 evaluateProps: { hideWhenDisabled: true } // 禁用时自动隐藏 } }注意evaluate支持三种形式——函数、注册名的字符串/对象形式{ name, ...options }、以及数组形式多个求值器合并任一返回disabled则整体禁用见 ToolbarService.ts。包装组件适配onInteraction 与 idToolBoxButtonGroupWrapper、ToolButtonListWrapper等包装组件需要同步更新groupIdprop 被替换为id即包装组件自身的按钮 id这些包装组件中的onInteraction回调现在提供id包装组件 id而非groupId。这与useToolbar的onInteraction({ itemId, viewportId })签名保持一致——按钮交互统一以itemId标识目标按钮。IconPresentationProvider统一图标尺寸与样式为统一应用中工具栏及相关组件的图标尺寸与样式ohif/ui-next新增了IconPresentationProvider与useIconPresentationHook实现见 IconPresentationProvider.tsx。支持的size取值映射取值像素值tiny16small20medium24默认large28任意数字直接作为像素值Provider 用法包裹高层组件如 Header 或布局组件// 在 App.tsx 或 Header.tsx 中 import { IconPresentationProvider, ToolButton } from ohif/ui-next; // ... IconPresentationProvider sizelarge // 或 medium / small / tiny / 数字 IconContainer{ToolButton} // 可选默认 Button containerProps{{ variant: primary, className: custom-container-class }} // 可选 {/* 包含 Toolbar 的 Header 内容 */} /IconPresentationProvider自定义工具按钮中使用 Hookimport { useIconPresentation, Icons } from ohif/ui-next; function MyCustomToolButton({ iconName }) { const { className: iconClassName } useIconPresentation(); return buttonIcons.ByName name{iconName} className{iconClassName} //button; }Provider 默认将IconContainer设为Buttonvariantghost、sizeicon可通过containerProps覆盖默认size为medium。该机制在OHIFViewportActionCorners中已被实际采用sizemediumIconContainer{ToolButton}可参照其用法。移除遗留组件ToolbarSplitButtonWithServicesLegacy与ToolbarButtonGroupWithServicesLegacy已从代码库中移除。任何使用它们的代码都应迁移到新模式将各组员定义为独立按钮直接使用ohif/ui-next的ToolButtonList或ButtonGroup由useToolbar驱动渲染。推荐迁移顺序Checklist替换方法调用全局替换addButtons→register、createButtonSection→updateSection消除控制台弃用警告移除旧服务引用删除所有ViewportActionCornersService、useViewportActionCorners、ViewportActionCornersProvider的使用转换角落项将角落自定义组件注册为标准工具栏按钮register通过updateSection放入toolbarService.sections.viewportActionMenu.location分区菜单类组件改为消费Toolbar下发的isOpen/onOpen/onClose调整按钮配置将groupId改为props.buttonSection布尔true或显式字符串分区名需要时在按钮定义中加入evaluateProps.hideWhenDisabled同步更新ToolBoxButtonGroupWrapper/ToolButtonListWrapper的id与onInteraction接入图标体系可选但推荐在高层组件包裹IconPresentationProvider自定义图标按钮改用useIconPresentation清理遗留组件替换ToolbarSplitButtonWithServicesLegacy/ToolbarButtonGroupWithServicesLegacy的使用。完成上述步骤后你的扩展与模式将完全适配 OHIF 3.11 的统一工具栏体系。如需了解视口角落配套组件ModalityLoadBadge、TrackingStatus、NavigationComponent等的迁移细节可继续阅读 viewport-action-menu.md 以及迁移指南的 索引页。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考