简介本资源为基于 VS2019 与 Qt5.15.2 64 位环境编译的 KDDockWidgets 动态库及配套源码面向需要在 Qt 项目中实现 QML 窗口停靠、可拖拽布局的开发者尤其适合中高级 Qt 工程师直接集成使用。包内区分 Quick 与 QWidget 两套版本各自对应不同的 include 头文件不可混用example 目录下提供两个演示 demo每个 demo 均含 KDDockWidgets 文件夹可通过将 KDDockWidgets.pri 添加进自有工程快速接入。资源共 956 个文件以 h 头文件、cpp 源文件、png 图片、qml 界面文件、json 配置、dll 动态库与 lib 导入库等为主压缩包约 51.56MB目录结构完整涵盖源码、示例与编译产物。目前已有 789 人学习下载读者可借此省去自行编译与配置的繁琐过程直接参考示例工程理解停靠窗口的布局保存、标题栏定制与多窗口嵌套等实现思路快速落地到实际项目中。1. 拿到 KDDockWidgets 的 VS2019 动态库先搞清楚它到底解决什么如果你正在用 Qt 做桌面端工具尤其是那种需要多面板、可拖拽、可停靠、还能保存布局的工程软件那你大概率绕不开 KDDockWidgets 这个名字。它是一套基于 Qt 的窗口停靠框架支持 QWidget 和 QML 两套体系能实现类似 Visual Studio 那种「拖出来变浮动窗口、拖回去自动吸附、布局可序列化」的交互。这次拿到的资源是 VS2019 Qt5.15.2 64bit 编译好的动态库附带源码和两个演示 DemoQuick 版和 QWidget 版分开打包头文件也各自独立。适合谁适合不想从零编译、想直接集成进现有工程、又需要快速验证停靠效果的人。但注意32bit 环境用不了这是硬门槛。2. 动态库选型与工程集成Quick 版和 QWidget 版到底怎么选2.1 两套库的本质差异与选型依据KDDockWidgets 的 Quick 版和 QWidget 版不是简单的「接口不同」而是底层渲染路径和事件处理机制完全不一样。QWidget 版走的是传统 QWidget 体系停靠窗口本身就是 QWidget 的子类适合纯 Widget 工程或者 Widget 为主的混合工程。Quick 版则是基于 QQuickWindow 和 QQuickItem 实现的停靠区域、标题栏、浮动窗口都是 QML 场景里的 Item适合 QML 为主或者纯 QML 工程。选错了会怎样最直接的表现是编译能过但运行起来窗口不显示、拖拽无响应、或者直接崩溃。因为两套库依赖的 Qt 模块不同QWidget 版依赖 QtWidgetsQuick 版依赖 QtQuick 和 QtQml。如果你的工程只链接了 QtWidgets却硬塞 Quick 版的库链接阶段就会报一堆未定义符号。我一般会先看工程的主窗口类型。如果主窗口是 QMainWindow 或者 QWidget 派生类直接选 QWidget 版如果主窗口是 QQuickView 或者 QQmlApplicationEngine 加载的 QML 根对象那就选 Quick 版。混合工程要特别注意KDDockWidgets 官方文档里明确说了同一个进程里不要同时加载两套库否则符号冲突和事件循环混乱会让你查到头秃。2.2 把 KDDockWidgets.pri 挂进现有工程资源里的 example 目录下有两个 Demo每个 Demo 下都有一个 KDDockWidgets 文件夹里面包含 include、lib 和 KDDockWidgets.pri。最省事的集成方式就是把这个 pri 文件直接 include 到你的 .pro 工程里。# 在你的 .pro 文件末尾添加 include($$PWD/KDDockWidgets/KDDockWidgets.pri) # 如果你用的是 Quick 版确保工程里已经声明了 QT quick qml # 如果是 QWidget 版确保有 QT widgets这个 pri 文件内部做了几件事把 include 路径加到 INCLUDEPATH把 lib 路径加到 LIBS并且根据当前构建套件自动选择 debug 或 release 版本的库。逻辑说明pri 文件里通常会有 CONFIG(debug, debug|release) 判断分别链接带 d 后缀和不带 d 后缀的 dll。参数说明如果你的工程输出目录和 Demo 不一样可能需要手动改 pri 里的 LIB_PATH 变量指向你实际存放 dll 和 lib 的目录。提示pri 文件里的路径默认是相对路径如果你把 KDDockWidgets 文件夹挪到别的层级记得同步改 pri 里的相对路径否则 qmake 会报找不到库。2.3 头文件包含与命名空间约定Quick 版和 QWidget 版的头文件目录结构不同不能混用。QWidget 版的核心头文件是 KDDockWidgets/KDDockWidgets.hQuick 版则是 KDDockWidgets/Quick/Quick.h 之类的路径。实际写代码时我一般会先包含总头文件再按需包含具体类。// QWidget 版典型包含方式 #include KDDockWidgets/KDDockWidgets.h #include KDDockWidgets/DockWidget.h #include KDDockWidgets/MainWindow.h // Quick 版典型包含方式 #include KDDockWidgets/Quick/Quick.h #include KDDockWidgets/Quick/DockWidget.h逻辑说明总头文件里做了前置声明和宏定义具体类头文件里才是完整的类定义。参数说明如果你只包含具体类头文件而不包含总头文件某些宏比如 KDDOCKWIDGETS_QUICK可能没定义导致编译期条件判断走错分支。常见做法是总头文件必包含具体类头文件按需包含。3. 从 Demo 到自己的窗口停靠布局的初始化与序列化3.1 创建 MainWindow 和第一个 DockWidgetKDDockWidgets 的核心对象是 MainWindow 和 DockWidget。MainWindow 是停靠容器DockWidget 是可停靠的窗口单元。下面这段代码是 QWidget 版的最小可运行示例。#include QApplication #include KDDockWidgets/MainWindow.h #include KDDockWidgets/DockWidget.h int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建主窗口作为停靠容器 auto mainWindow new KDDockWidgets::MainWindow(QStringLiteral(MyMainWindow)); mainWindow-resize(1200, 800); // 创建一个 DockWidget标题为 面板A auto dockA new KDDockWidgets::DockWidget(QStringLiteral(PanelA)); dockA-setWidget(new QLabel(QStringLiteral(这是面板A的内容))); // 把 DockWidget 添加到主窗口的左侧区域 mainWindow-addDockWidget(dockA, KDDockWidgets::Location_OnLeft); mainWindow-show(); return app.exec(); }逻辑说明MainWindow 构造时传入一个唯一名称这个名称在序列化布局时作为标识。DockWidget 构造时也传入唯一名称setWidget 设置实际内容控件。addDockWidget 的第二个参数是停靠位置常见的有 Location_OnLeft、Location_OnRight、Location_OnTop、Location_OnBottom、Location_OnCenter。参数说明如果你想让多个 DockWidget 叠在同一个区域形成 Tab就多次调用 addDockWidget 并传入相同的位置参数KDDockWidgets 会自动把它们做成 Tab 页。3.2 布局的保存与恢复序列化成字节流工程软件里用户拖完布局下次打开要能恢复这是刚需。KDDockWidgets 提供了 saveLayout 和 restoreLayout 两个接口底层是 QByteArray 序列化。// 保存布局到文件 QByteArray layoutData mainWindow-saveLayout(); QFile file(QStringLiteral(layout.bin)); if (file.open(QIODevice::WriteOnly)) { file.write(layoutData); file.close(); } // 从文件恢复布局 QFile readFile(QStringLiteral(layout.bin)); if (readFile.open(QIODevice::ReadOnly)) { QByteArray saved readFile.readAll(); mainWindow-restoreLayout(saved); readFile.close(); }逻辑说明saveLayout 返回的 QByteArray 里包含了所有 DockWidget 的停靠位置、尺寸、浮动状态、Tab 顺序等信息。restoreLayout 会按照字节流里的描述重建布局。参数说明restoreLayout 必须在所有 DockWidget 都已经创建并注册到 MainWindow 之后调用否则会找不到对应的 DockWidget 而恢复失败。常见做法是在程序启动时先创建所有 DockWidget再读文件恢复布局。注意saveLayout 不会保存 DockWidget 里的业务数据只保存布局结构。业务数据需要你自己另外持久化。3.3 QML 版停靠窗口的声明式写法Quick 版的用法和 QWidget 版差异较大需要在 QML 里声明 DockWidget 和 MainWindow 对应的 QML 类型。import QtQuick 2.15 import QtQuick.Window 2.15 import KDDockWidgets 1.0 MainWindow { id: mainWindow width: 1200 height: 800 visible: true DockWidget { id: dockA title: 面板A Rectangle { color: #f0f0f0 Text { text: QML 面板A; anchors.centerIn: parent } } } Component.onCompleted: { mainWindow.addDockWidget(dockA, KDDockWidgets.Location_OnLeft) } }逻辑说明QML 版里 MainWindow 和 DockWidget 都是 QML 类型通过 import KDDockWidgets 1.0 引入。addDockWidget 在 Component.onCompleted 里调用确保所有子项都已创建完毕。参数说明QML 版的 Location 枚举和 C 版一致但写法上要用 KDDockWidgets.Location_OnLeft 这种带命名空间的形式。如果你在 QML 里直接写 Location_OnLeft会报未定义。4. 避坑与排查编译链接和运行时的五个血泪坑4.1 32bit 环境直接翻车现象在 32bit Qt 套件下编译链接阶段报大量未解析外部符号或者编译通过但运行时报「不是有效的 Win32 应用程序」。原因这份动态库是 64bit 编译的32bit 进程无法加载 64bit dll。解决确认你的 Qt 套件是 MSVC2019 64bit在 Qt Creator 的「项目」→「构建套件」里检查。如果必须用 32bit只能自己拿源码重新编译。4.2 Quick 版和 QWidget 版头文件混用现象编译时报「未定义的类型」或者「无法解析的符号」明明已经包含了头文件。原因Quick 版和 QWidget 版的头文件目录结构不同类名虽然相似但命名空间和宏定义不一样。解决检查你的 INCLUDEPATH 里是否同时存在两套 include 路径。如果是移除不需要的那套。在 .pro 里用 CONFIG 条件判断只挂载对应版本的 pri。4.3 Debug 和 Release 库混链现象Debug 模式下编译通过但运行到停靠窗口创建时直接崩溃堆栈指向 Qt 内部。原因Debug 版工程链接了 Release 版的 dll或者反过来。MSVC 的 Debug 和 Release 运行时库不兼容。解决检查 pri 文件里的 CONFIG 判断是否正确确保 Debug 构建链接带 d 后缀的库Release 构建链接不带 d 后缀的库。手动检查 LIBS 变量里实际链接的库文件名。4.4 restoreLayout 调用时机不对现象布局恢复后所有 DockWidget 都堆在中间或者直接消失。原因restoreLayout 在 DockWidget 创建之前调用了框架找不到对应的 DockWidget 名称只能忽略。解决确保所有 DockWidget 都 new 出来并 addDockWidget 之后再调用 restoreLayout。常见做法是在 MainWindow 的 showEvent 里做恢复或者在所有初始化代码的最后一步做。4.5 QML 引擎销毁顺序导致崩溃现象Quick 版程序退出时崩溃堆栈显示 QQuickWindow 析构相关。原因QML 引擎先于 KDDockWidgets 的 MainWindow 销毁导致 MainWindow 析构时访问了已经释放的 QML 场景对象。解决在 main 函数里手动控制销毁顺序先 delete MainWindow再让 QApplication 正常退出。或者把 MainWindow 的父对象设为 nullptr在 aboutToQuit 信号里手动 delete。5. 进阶技巧用自定义标题栏和浮动窗口策略提升交互质感5.1 自定义标题栏的两种接入方式KDDockWidgets 默认的标题栏是框架自绘的样式比较朴素。资源目录里有个 advancedCustomTitleBar 示例展示了怎么替换成自定义标题栏。QWidget 版的做法是继承 KDDockWidgets::TitleBar 并重写 paintEvent然后通过 DockWidget::setTitleBar 设置。Quick 版则是在 QML 里给 DockWidget 的 titleBar 属性赋值一个自定义 Component。// QWidget 版自定义标题栏示例 class MyTitleBar : public KDDockWidgets::TitleBar { public: explicit MyTitleBar(KDDockWidgets::DockWidget *dockWidget) : KDDockWidgets::TitleBar(dockWidget) {} protected: void paintEvent(QPaintEvent *event) override { QPainter painter(this); painter.fillRect(rect(), QColor(#2d2d2d)); painter.setPen(Qt::white); painter.drawText(rect(), Qt::AlignCenter, dockWidget()-title()); } }; // 使用自定义标题栏 dockA-setTitleBar(new MyTitleBar(dockA));逻辑说明TitleBar 的 paintEvent 里可以完全接管绘制逻辑实现任意风格的标题栏。参数说明dockWidget() 返回所属的 DockWidget 指针通过它可以拿到标题、图标等信息。注意自定义标题栏的高度要通过 sizeHint 返回否则布局会按默认高度算。5.2 浮动窗口的初始尺寸和位置控制默认情况下DockWidget 拖出来变成浮动窗口时尺寸和位置是框架自动算的。但有些场景下你需要指定浮动窗口的初始几何。KDDockWidgets 提供了 setFloatingGeometry 接口。// 设置浮动窗口的初始几何 dockA-setFloatingGeometry(QRect(100, 100, 600, 400)); // 如果想让 DockWidget 一开始就是浮动的 dockA-setFloating(true);逻辑说明setFloatingGeometry 只在 DockWidget 处于浮动状态时生效如果它当前是停靠状态调用后不会立即改变位置等它下次浮动时才会用这个几何。参数说明QRect 的坐标是屏幕坐标不是主窗口坐标。如果你想让浮动窗口出现在主窗口旁边需要先拿到主窗口的 geometry 再计算。5.3 布局持久化的版本兼容处理saveLayout 生成的字节流在不同版本的 KDDockWidgets 之间可能不兼容。如果你升级了库版本旧布局文件可能恢复失败。我一般会在布局文件头部加一个版本号恢复时先检查版本。// 保存时写入版本号 QByteArray layoutData mainWindow-saveLayout(); QDataStream stream(layoutData, QIODevice::WriteOnly | QIODevice::Append); stream QStringLiteral(1.5); // 恢复时检查版本号 QFile file(QStringLiteral(layout.bin)); if (file.open(QIODevice::ReadOnly)) { QByteArray data file.readAll(); QDataStream readStream(data, QIODevice::ReadOnly); QString version; readStream version; if (version QStringLiteral(1.5)) { mainWindow-restoreLayout(data.mid(readStream.device()-pos())); } else { // 版本不匹配走默认布局 } }逻辑说明QDataStream 的 Append 模式可以在已有字节流后面追加数据读取时先读版本号再读剩余布局数据。参数说明data.mid(readStream.device()-pos()) 拿到的是去掉版本号之后的纯布局字节流。这种做法在资源目录的 Changelog 里也有体现1.5 和 1.6 的布局格式有差异跨版本恢复大概率会出问题。从那以后我每次集成 KDDockWidgets 到新工程都强制走一遍「确认位数 → 确认版本 → 确认 pri 路径 → 先跑 Demo → 再改业务代码」的流程少一步都可能多花半天排查。希望帮到你。本文还有配套的精品资源点击获取