- 开发工具
- 调试器
- 图形学
- GPU
【免费下载链接】renderdoc
RenderDoc is a stand-alone graphics debugging tool.
ToolWindowManager 是 RenderDoc 的 qrenderdoc(Qt 图形界面)中使用的第三方停靠窗口管理组件。它基于 Qt 实现,为项目提供类似 QDockWidget 的停靠(docking)能力,但作为一个独立组件,它比 Qt 内建停靠系统更容易定制和扩展。本文以 qrenderdoc/3rdparty/toolwindowmanager/README.md 为主线,结合仓库内的完整源码(ToolWindowManager.h、ToolWindowManager.cpp 及配套的 Area / Wrapper / Splitter / TabBar 实现),深入讲解该组件的设计背景、架构组成、核心 API、拖放交互机制与状态持久化原理,并展示它在 RenderDoc 主界面(qrenderdoc/Windows/MainWindow.h、qrenderdoc/Code/CaptureContext.cpp)中的真实用法。
一、组件定位:为什么需要它
ToolWindowManager 提供"类似 Visual Studio 或 Eclipse 中工具窗口"的行为(见 ToolWindowManager.h 中的类注释):
- 用户可以将工具窗口排列成标签页(tabs);
- 可以停靠到任意边框,用垂直/水平分割条(splitter)进行拆分;
- 可以将多个窗口 tabify 在一起,也可以脱离为浮动窗口(floating windows)。
相较于 Qt 内建的 QDockWidget,ToolWindowManager 的最大价值在于独立、可定制、可扩展:它不依赖 QMainWindow 的停靠系统,布局结构由组件自身管理,因此 RenderDoc 能对其进行深度改造,满足图形调试器复杂的多窗口工作区需求。
二、Fork 背景与许可说明
README 明确说明了该组件的来源与许可状态:
- 它是从
riateche/toolwindowmanager项目 fork 而来,fork 自一个 MIT 许可明确的历史提交点;此后上游进行了重新实现,可能改为 LGPL 许可且作者未再澄清,因此 RenderDoc 选择在 MIT 许可明确的提交点上继续维护自己的分支。 - 仓库中所有源码文件顶部均保留了 MIT 许可声明,例如 ToolWindowManager.h(版权归属于 Pavel Strakhov,2014)与 ToolWindowManagerSplitter.h(版权归属于 Baldur Karlsson,2017,即 RenderDoc 作者),印证了"原实现 + RenderDoc 后续改进"的双作者结构。
- 仓库内的 LICENSE 文件即对应这份 MIT 许可。
三、RenderDoc fork 的核心改进亮点
README 总结了该 fork 相对上游原版的主要改进,这些改进都可以在源码中找到对应实现:
3.1 更强的可定制性
- 任意数据与保存状态关联:
saveState()/restoreState()基于QVariantMap序列化整个布局(ToolWindowManager.cpp),调用方可以把额外数据挂在保存的状态中一并持久化。 - 关闭前回调检查:
allowClose()会通过 Qt 元对象系统检查工具窗口是否实现了名为checkAllowClose()的槽(返回 bool),只有检查通过才允许关闭(ToolWindowManager.cpp)。 - 允许/禁止标签重排或浮动窗口:通过
ToolWindowProperty标志位精细控制(详见下文第五节的枚举)。
3.2 多个嵌套 TWM 的支持
修复了在多个嵌套 ToolWindowManager 实例共存时的归属判定问题。managerOf()与closeToolWindow()都使用findClosestParent<ToolWindowManager *>()沿父链向上查找"最近的"管理器,而不是假设全局只有一个实例(ToolWindowManager.cpp)。
3.3 拖放位置的预览叠加层
拖拽过程中会绘制半透明(setWindowOpacity(0.3))的预览框,指示窗口将要停靠的区域:
m_previewOverlay:显示目标区域的大致轮廓,例如左/右/上/下停靠时占据目标区域的一半,窗口侧停靠时占据整个 wrapper 的 5/6 或 3/4(ToolWindowManager.cpp);m_previewTabOverlay:在标签页场景下,精确指示标签将被插入到哪个 tab 位置(ToolWindowManager.cpp)。
3.4 基于热点图标与特定位置的拖放判定
弃用了上游"循环遍历建议"式的停靠选择方式,改为热点图标(drop hotspot)机制:拖拽悬停时,在目标区域中心、四边以及所属窗口(wrapper)的四边弹出热点图标,currentHotspot()通过判断鼠标位置是否落在某个热点图标的几何范围内来确定停靠类型(AddTo / LeftOf / RightOf / TopOf / BottomOf / 各 WindowSide 变体),见 ToolWindowManager.cpp。
3.5 整窗拖拽
允许把整个浮动窗口(wrapper)连同其中的所有工具窗口作为一个整体拖拽移动。startDrag()同时接受工具窗口列表与可选的 wrapper 指针(m_draggedWrapper),拖拽整窗时不显示"撕离预览"(因为它本身就是移动中的窗口),见 ToolWindowManager.cpp 与finishDrag()中对draggedWrapper的分支处理。
四、架构组成:五个核心类
从 qrenderdoc/3rdparty/toolwindowmanager 目录可以看出,组件由五个类构成,职责划分清晰:
| 类 | 基类 | 职责 |
|---|---|---|
ToolWindowManager | QWidget | 总控组件:管理所有工具窗口、区域、浮动窗口;处理拖放、热点与预览、布局序列化 |
ToolWindowManagerArea | QTabWidget | 承载工具窗口的标签页区域,实现 tab 拖拽、关闭、选择历史等 |
ToolWindowManagerWrapper | QWidget | 布局容器:一个非浮动的直接子节点 + 若干浮动顶层窗口,自绘标题栏与缩放手柄 |
ToolWindowManagerSplitter | QSplitter | 定制子项移除时剩余空间的分摊方式 |
ToolWindowManagerTabBar | QTabBar | 定制标签栏绘制,尤其是只有一个标签时的最小化显示,以及自定义关闭/固定按钮 |
4.1 布局树结构
ToolWindowManager构造时在其内部创建一个非浮动的ToolWindowManagerWrapper(去掉了Qt::Tool窗口标志)作为唯一直接子节点,见 ToolWindowManager.cpp。整个工作区可以抽象为一棵递归树:
ToolWindowManagerWrapper(窗口)→ 直接子节点;- 子节点要么是
ToolWindowManagerArea(标签页区域),要么是ToolWindowManagerSplitter(分割条); - splitter 的子节点又可以递归地是 area 或嵌套 splitter。
这套递归结构与saveSplitterState()/restoreSplitterState()的递归序列化方式一一对应(ToolWindowManager.cpp)。
五、核心 API 深入
5.1 添加与管理工具窗口
// 单个窗口:快捷方式,内部转发给 addToolWindows void addToolWindow(QWidget *toolWindow, const AreaReference &area, ToolWindowProperty properties = ToolWindowProperty(0)); // 批量添加;manager 接管窗口所有权,析构时统一删除 void addToolWindows(QList<QWidget *> toolWindows, const AreaReference &area, ToolWindowProperty properties = ToolWindowProperty(0)); void moveToolWindow(QWidget *toolWindow, AreaReference area); // 移动单个窗口 void moveToolWindows(QList<QWidget *> toolWindows, AreaReference area); ToolWindowManagerArea *areaOf(QWidget *toolWindow); // 查询所在区域(隐藏时为 0) void removeToolWindow(QWidget *toolWindow); // 移除并交还所有权 bool isFloating(QWidget *toolWindow); // 是否处于浮动窗口关键语义(ToolWindowManager.cpp):
- 添加时窗口会被
hide()并setParent(0)重新托管;窗口的windowIcon()与windowTitle()会被用作标签页的图标与标题; - 标签页标题会跟随
QWidget::windowTitleChanged信号自动更新(windowTitleChanged槽,ToolWindowManager.cpp); - 若要使用
saveState/restoreState,必须为每个工具窗口设置非空的唯一objectName()——这是状态恢复时识别窗口的唯一依据(头文件注释明确要求,ToolWindowManager.h)。
5.2 静态辅助方法
static ToolWindowManager *managerOf(QWidget *toolWindow); // 沿父链找到所属管理器 static void closeToolWindow(QWidget *toolWindow); // 关闭(先做 checkAllowClose 检查) static void raiseToolWindow(QWidget *toolWindow); // 在所在 area 中切到前台标签raiseToolWindow的实现会沿父链找到ToolWindowManagerArea并调用setCurrentWidget()激活目标标签(ToolWindowManager.cpp)。
5.3 AreaReference:指定停靠位置
AreaReference是移动/添加窗口时的"位置描述符",由AreaReferenceType枚举决定行为:
| 类型 | 含义 |
|---|---|
LastUsedArea | 最近一次添加过窗口的区域 |
NewFloatingArea | 放入新的浮动窗口 |
EmptySpace | 放入管理器内部空白(仅当还没有工具窗口时) |
NoArea | 隐藏窗口(hideToolWindow即moveToolWindow(w, NoArea)) |
AddTo | 加入指定的已有 area |
LeftOf/RightOf/TopOf/BottomOf | 在指定 area 的对应侧新建 area |
LeftWindowSide/RightWindowSide/TopWindowSide/BottomWindowSide | 在指定 area 所属窗口(wrapper)的对应侧新建 area |
构造时还可传入float percentage = 0.5f控制新建区域占用的空间比例(ToolWindowManager.h)。moveToolWindows中会将该比例转换为 splitter 的实际像素尺寸(setSizes({a, b})),见 ToolWindowManager.cpp。
AreaReference的校验逻辑值得注意:LastUsedArea/NewFloatingArea/NoArea/EmptySpace忽略 area 参数;AddTo只接受ToolWindowManagerArea*;其余类型接受 area 或 splitter(ToolWindowManager.cpp)。
5.4 可调属性(Q_PROPERTY)
ToolWindowManager暴露了三个可通过 Q_PROPERTY 系统访问/设置的属性(ToolWindowManager.h):
| 属性 | 默认值 | 作用 |
|---|---|---|
allowFloatingWindow | true | 是否允许创建浮动窗口 |
dropHotspotMargin | 4 | 拖放热点图标之间的间距(像素) |
dropHotspotDimension | 32 | 每个热点图标的宽高(像素) |
热点图标由drawHotspotPixmaps()用QPainter实时绘制(圆角底、箭头指示方向,侧边类型复用对应的四向图标),并支持通过setHotspotPixmap(AreaReferenceType, QPixmap)替换为自定义图标,见 ToolWindowManager.cpp。
5.5 窗口级属性(ToolWindowProperty)
每个工具窗口可以附带一组标志位(支持用operator|组合,ToolWindowManager.h):
| 标志 | 值 | 效果 |
|---|---|---|
DisallowUserDocking | 0x1 | 禁止用户拖拽停靠该窗口 |
HideCloseButton | 0x2 | 隐藏该窗口标签页上的关闭按钮 |
DisableDraggableTab | 0x4 | 禁止用户拖动标签重排 |
HideOnClose | 0x8 | 关闭时隐藏而不是移除(可再次恢复显示) |
DisallowFloatWindow | 0x10 | 不允许该窗口浮动 |
AlwaysDisplayFullTabs | 0x20 | 即使只有一个标签也始终显示完整标签栏 |
标志位在拖拽判定中被使用,例如startDrag()检查DisallowUserDocking、finishDrag()检查DisallowFloatWindow(ToolWindowManager.cpp)。属性通过setToolWindowProperties()/toolWindowProperties()读写。
5.6 状态保存与恢复
saveState()返回QVariantMap,结构如下(ToolWindowManager.cpp):
toolWindowManagerStateFormat = 1:格式版本号;mainWrapper:主窗口布局(递归的 splitter/area 状态);floatingWindows:所有浮动窗口的状态列表。
restoreState()会先清空现有布局,再重建主 wrapper 与各浮动窗口,并为最大化状态的浮动窗口恢复最大化(ToolWindowManager.cpp)。splitter 的几何尺寸通过QSplitter::saveState()的 Base64 形式保存。恢复过程中若遇到未注册的窗口,会调用CreateCallback(setToolWindowCreateCallback设置的回调,签名std::function<QWidget *(const QString &objectName)>)按 objectName 重建窗口。
5.7 拖放交互细节
- 拖拽开始:
ToolWindowManagerArea/ToolWindowManagerWrapper在鼠标移动越过阈值后调用startDrag()(后者还支持从自绘标题栏发起整窗拖拽,并有m_moveTimeout定时器辅助判定); - 拖拽过程:
ToolWindowManager通过安装应用级事件过滤器(qApp->installEventFilter(this))持续调用updateDragPosition(),计算悬停区域、定位并显示热点图标与预览框; - 中止:右键点击或按 Esc 触发
abortDrag()(ToolWindowManager.cpp); - 完成:松开左键触发
finishDrag(),根据当前热点类型调用moveToolWindows()执行实际布局变更(ToolWindowManager.cpp)。
每次布局变更后都会调用simplifyLayout()清理无用的结构:空 area 会被删除、单子节点的 splitter 会被折叠(其子节点提升到父级),避免布局树中残留"死节点"(ToolWindowManager.cpp)。
六、子组件的定制细节
6.1 ToolWindowManagerArea:标签区域
作为QTabWidget的子类,它额外实现了:
- 标签选择历史:
m_tabSelectOrder记录最近选中的顺序,关闭一个标签时自动选中历史中最近的一个(ToolWindowManagerArea.h); - 用户投放开关:
enableUserDrop()/disableUserDrop()控制该区域是否接受用户拖放(m_userCanDrop),配合拖放判定使用; - 单标签最小化:
useMinimalTabBar()决定是否使用极简标签栏(由AlwaysDisplayFullTabs属性决定)。
6.2 ToolWindowManagerWrapper:浮动窗口外壳
- 主 wrapper 是管理器内嵌的内容容器;其余 wrapper 均为顶层浮动窗口(ToolWindowManagerWrapper.h);
- 浮动窗口关闭时,
closeEvent会统一注册其中所有工具窗口为隐藏状态; - 自绘标题栏:浮动窗口不依赖系统标题栏,而是自己绘制标题(
titleRect)、关闭按钮(m_closeIcon)、并实现八方向缩放手柄(ResizeDirection枚举:NW/NE/SW/SE/N/E/S/W),见 ToolWindowManagerWrapper.h。
6.3 ToolWindowManagerSplitter:尺寸分配
childEvent重写:当某个子项被移除时,重新分配剩余空间的方式与 QSplitter 默认行为不同,避免尺寸跳跃,见 ToolWindowManagerSplitter.cpp。
6.4 ToolWindowManagerTabBar:标签栏绘制
- 单标签场景下
sizeHint()/minimumSizeHint()返回极小尺寸,配合useMinimalBar()实现"一个标签时不显示标签栏"的整洁效果; - 自定义绘制关闭按钮(
m_close)与固定按钮(m_pin)的 hover/click 状态,支持tabsClosable()开关(ToolWindowManagerTabBar.h)。
七、在 RenderDoc 中的实际应用
ToolWindowManager 被 qrenderdoc 的构建系统直接编译进 UI 工程(qrenderdoc/CMakeLists.txt 中3rdParty/toolwindowmanager/*.h与*.cpp),并在主窗口中深度使用:
7.1 主窗口对外暴露的接口
MainWindow声明了三个与 ToolWindowManager 直接相关的方法(qrenderdoc/Windows/MainWindow.h):
ToolWindowManager *mainToolManager(); // 获取主工作区的管理器 ToolWindowManager::AreaReference mainToolArea(); // 主工作区区域的引用 ToolWindowManager::AreaReference leftToolArea(); // 左侧区域(事件浏览器等)的引用7.2 窗口的添加、移动与定位
CaptureContext.cpp 是组件 API 的主要消费方,典型调用包括:
- 打开新窗口时按类型决定初始位置:
NewFloatingArea(浮动)、LastUsedArea(复用最近区域)、NoArea(隐藏)、EmptySpace(空白区域),见 CaptureContext.cpp; - 参照另一个已打开窗口的容器停靠:
manager->addToolWindow(newWindow, AreaReference(AddTo, manager->areaOf(cb)))(CaptureContext.cpp); - 主窗口旁侧停靠:
AreaReference(RightOf, main.area(), percentage),其中percentage由调用方传入以控制新窗占用宽度(CaptureContext.cpp); - 激活/前台化窗口:
ToolWindowManager::raiseToolWindow(dockWindow)(CaptureContext.cpp)。
这些调用印证了 README 所述"为 RenderDoc 而优化"的定位:RenderDoc 正是依靠这套组件实现了事件浏览器、纹理查看器、网格查看器、着色器查看器等数十个工具窗口的自由停靠、拆分、标签化与浮动,以及布局随会话保存/恢复。
八、总结
- 定位:ToolWindowManager 是一个 MIT 许可、独立于 QMainWindow 的 Qt 停靠窗口管理组件,RenderDoc 在其上维护了功能增强的 fork 分支;
- 能力:标签页化、四向停靠、分割拆分、浮动/整窗拖拽、拖放热点与半透明预览、窗口级属性控制、递归布局序列化;
- 使用要求:工具窗口需设置唯一
objectName()才能享受布局持久化;关闭前检查依赖窗口实现checkAllowClose()槽;组合标志位(ToolWindowProperty)可实现精细的交互约束; - 在 RenderDoc 中的角色:qrenderdoc 主工作区的停靠/布局基础设施,直接支撑了 CaptureContext.cpp 中全部工具窗口的定位与管理工作。
如果你需要在自有 Qt 项目中实现类似的可停靠工作区,可以直接复用本仓库 qrenderdoc/3rdparty/toolwindowmanager 下的五个类(连同LICENSE的 MIT 声明),并按上述 API 模式接入即可。
- 开发工具
- 调试器
- 图形学
- GPU
【免费下载链接】renderdoc
RenderDoc is a stand-alone graphics debugging tool.
相关推荐
KDDockWidgets 停靠窗口系统深度解析
KDDockWidgets 停靠窗口系统深度解析 KDDockWidgets 是由 KDAB 团队开发的现代化 Qt 停靠窗口框架,旨在为开发者提供超越原生 Q
UI组件桌面应用深入解析Phoenix:基于JavaScript的macOS窗口管理神器
深入解析Phoenix:基于JavaScript的macOS窗口管理神器 痛点:macOS窗口管理的效率瓶颈 你是否曾经在多个应用窗口间频繁切换,为寻找特定窗口
桌面应用开发工具Dear ImGui窗口管理系统:多窗口、停靠和标签页的高级用法
Dear ImGui窗口管理系统:多窗口、停靠和标签页的高级用法 引言:为什么需要专业的窗口管理? 你是否曾经在开发工具软件、游戏编辑器或数据可视化应用时,面临
UI组件前端桌面应用图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考