
简介一套基于VS2019与Qt5.15.2环境编写的QDockWidget实例Demo面向刚接触Qt界面开发的初学者重点演示QDockWidget的创建、停靠、浮动、关闭及恢复等常用操作配合博客中的图文说明可以帮助学习者快速理解和上手这一常用窗口部件进而掌握主窗口与停靠面板的交互布局方式。压缩包共包含81个文件既有cpp、h、ui、qrc等完整源码与界面定义也有大量png图片资源用于直观展示运行效果同时附带了sln、vcxproj等工程文件和可直接运行的exe程序整个资源包仅4.16MB下载后无需复杂配置即可对照学习。目录结构清晰源码中保留了必要注释便于按步骤阅读或自行修改扩展配套博客还提供了运行效果截图和易错点说明适合边看边练。目前已有2518人学习或下载内容聚焦于单个典型控件的实际应用非常适合希望快速建立Qt界面开发信心的初学者作为第一份上手资料。 很多做Qt界面的朋友最后都会被同一个问题追着跑主窗口左边要放文件树右边要放属性面板底部还要塞一个日志输出窗口。如果这些全靠自己用布局器硬写光是拖拽分隔条、控制显隐就能把人写崩溃。这个时候QDockWidget就是最顺手的那把刀。它可以让你用非常少的代码做出和主流IDE一样灵活的停靠面板能拖动、能浮动、能关闭、能自动排列用户还能按自己的喜好重新布局。这篇文章我没打算只贴一段“能跑就行”的代码而是把QDockWidget实例Demo从设计思路、逐步实现到状态保存、踩坑排查完整过一遍。已经有一定Qt基础但没系统用过Dock的可以直接照着抄刚入门的看完也能明白Dock机制到底是怎么回事。1. 先搞清楚QDockWidget到底解决什么问题1.1 一个典型的界面拆分场景假设你要做的是一个数据可视化工具。主界面中间是图表左侧是图层列表右侧是属性面板下面是日志输出。这种布局大家应该都不陌生。如果不用QDockWidget你要自己写QSplitter嵌套控制每个面板的显示和隐藏还要处理面板大小比例用户想关掉一个面板重新打开你还得自己去维护状态。QDockWidget这套机制把这些麻烦事全包了。它把每一个工具面板封装成一个“可停靠窗口”由QMainWindow统一管理停靠区域。用户可以把面板拖到左边、右边、顶部、底部也可以让它悬浮在最上层还可以把几个面板拖到一起变成Tab页。开发者要做的只是创建面板、设置属性和初始停靠位置剩下的交互都交给框架。1.2 Dock机制背后的三个基础概念第一次用QDockWidget建议先记住三个东西QDockWidget本身、QMainWindow的停靠区、还有Dock面板里的内容控件。我习惯用一个比喻把QMainWindow想成一张办公桌桌面中央区域放你最重要的工作内容桌子的四条边左、右、上、下就是停靠区。QDockWidget就像抽屉柜可以推到桌子左边、右边也可以拉出来放在旁边浮动甚至可以几个抽屉叠在一起变成一组Tab。抽屉里具体放什么是文件列表、属性表格还是日志文本框都由你决定——通过setWidget()放进去的任意QWidget。注意QDockWidget不能脱离QMainWindow单独“停靠”。它可以独立存在但只有放进QMainWindow时吸附、拖拽、浮动这些能力才是完整的。单纯用QWidget当容器Dock机制是起不来的。2. 实例Demo怎么搭一个能跑的Dock程序2.1 环境准备和项目结构这个Demo我用的Qt 5.15.2 Qt Creator QMake。Qt 5.15是LTS版本很多公司还在用它网上能找到的坑和资料也最多。编译器建议MSVC2019 64位或MinGW 64位都行下面的代码不涉及平台相关API随便哪个套件都能编译。创建工程时直接选择“Qt Widgets Application”基类选QMainWindow。很多新手犯的第一个错就是选了QWidget结果发现没有addDockWidget()方法然后到处找替代方案。记住QDockWidget的停靠管理是QMainWindow专属能力基类选错后面全是坑。2.2 第一步主窗口和中央区域的“压舱石”在主窗口构造函数里我习惯先设置一个中央控件。哪怕你的程序暂时没有真正的中央内容也建议用一个占位控件顶住。MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle(QDockWidget Demo); resize(900, 600); QTextEdit *center new QTextEdit(this); center-setPlaceholderText(这是中央区域); setCentralWidget(center); ... }为什么要先放中央区域因为QDockWidget是停靠在“中央区域四周”的。如果没有中央控件Dock会填满整个主窗口看起来会很奇怪而且之后你想在中间放图表或编辑区时布局会乱。所以中央区域相当于一个“压舱石”。2.3 第二步创建文件面板、属性面板和日志面板接下来创建三个QDockWidget分别停靠到左、右、底部。每个面板都设置一个唯一的objectName这个习惯在保存布局时非常重要后面我详细说。QDockWidget *fileDock new QDockWidget(文件, this); fileDock-setObjectName(fileDock); QListWidget *fileList new QListWidget(fileDock); fileList-addItems(QStringList() main.cpp mainwindow.cpp dockwidget.cpp); fileDock-setWidget(fileList); addDockWidget(Qt::LeftDockWidgetArea, fileDock); QDockWidget *propDock new QDockWidget(属性, this); propDock-setObjectName(propDock); QTreeWidget *propTree new QTreeWidget(propDock); propTree-setHeaderLabel(属性); propTree-addTopLevelItem(new QTreeWidgetItem(QStringList() 位置)); propTree-addTopLevelItem(new QTreeWidgetItem(QStringList() 大小)); propDock-setWidget(propTree); addDockWidget(Qt::RightDockWidgetArea, propDock); QDockWidget *logDock new QDockWidget(日志, this); logDock-setObjectName(logDock); QPlainTextEdit *logEdit new QPlainTextEdit(logDock); logEdit-setReadOnly(true); logDock-setWidget(logEdit); addDockWidget(Qt::BottomDockWidgetArea, logDock);到这里程序已经能跑三个面板会出现在左、右、底部用户可以自由拖动它们。但如果你追求更高的完成度还需要加上菜单控制否则用户把面板关了之后就没有入口能重新打开它。2.4 第三步用“视图”菜单控制面板显隐QDockWidget自带一个toggleViewAction()这个QAction可以自动跟着面板状态更新面板显示时菜单项是勾选状态面板关闭时菜单项取消勾选。点一下菜单项面板就会在显示和隐藏之间切换。把菜单、快捷键、复选框状态绑定到一起一行代码就能搞定。QMenu *viewMenu menuBar()-addMenu(视图(V)); viewMenu-addAction(fileDock-toggleViewAction()); viewMenu-addAction(propDock-toggleViewAction()); viewMenu-addAction(logDock-toggleViewAction());我个人强烈建议给视图菜单加一个“重置布局”功能把面板恢复到这个代码里的初始位置。为什么需要因为用户可能把窗口拖得乱七八糟又不知道怎么恢复。等看完第4节的状态保存你就能自己实现这个功能了。3. 让面板变得“好用”QDockWidget的细节控制3.1 AllowedAreas和Features怎么选很多初学者会觉得既然Dock能拖那就让它随便拖。实际上“哪里都可以停靠”不一定是好事属性面板拖到左边去会显得很别扭。setAllowedAreas()可以限制停靠区域。fileDock-setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); propDock-setAllowedAreas(Qt::RightDockWidgetArea);setFeatures()则控制面板自身的行为能力常用的组合有这几种Features值效果适用场景QDockWidget::AllDockWidgetFeatures可移动、可浮动、可关闭通用工具面板自由度最高QDockWidget::DockWidgetMovable | DockWidgetFloatable可拖动、可浮动但不能关闭不允许用户关闭的重要面板QDockWidget::NoDockWidgetFeatures完全固定不能拖动、不能关闭强制显示的面板比如登录状态我踩过的一个坑是把面板设成DockWidgetClosable | DockWidgetMovable忘记加Floatable结果面板能拖到别处但无法单独浮出来用户反馈“这窗口是不是卡住了”。所以features和allowedAreas一定要搭配着设先想清楚你的面板需要哪些交互。3.2 用tabifyDockWidget和splitDockWidget控制面板排布默认情况下往同一区域addDockWidget新面板会把旧面板挤到旁边形成上下或左右并排。如果你希望几个面板像IDE那样放在同一个区域里有时候需要合并成Tab页有时候需要分成两栏。tabifyDockWidget(fileDock, propDock);可以把两个面板合并成一个Tab页。这一步很容易被忽略如果你想执行tabify两个面板必须都在同一个停靠区否则函数无效。如果想让某个区域上下分栏就用splitDockWidget(propDock, logDock, Qt::Vertical);。splitDockWidget和addDockWidget的区别在于它会根据已有Dock的相对位置做切分而不是简单地把新窗口塞到区域边缘。注意调用顺序不要反。先addDockWidget把两个面板都加进来再tabifyDockWidget或splitDockWidget。反过来操作效果可能不稳定。3.3 自定义标题栏让面板外观更统一默认的Dock标题栏比较朴素想做出专业软件的质感一般会用setTitleBarWidget()替换成自定义QWidget。一个很常见的做法是在标题栏里放一个带图标的QLabel和一个关闭按钮。但这里面有个坑如果自定义标题栏没有设置合理的setSizePolicy把它放进Dock后可能会显示不出来或者宽度异常。我的经验是标题栏控件建议这样设置QWidget *titleBar new QWidget(fileDock); titleBar-setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // 再往titleBar里加布局放名称和按钮 fileDock-setTitleBarWidget(titleBar);设置完自定义标题栏后Dock自带的拖拽手柄可能会失效。这是因为你替换了标题栏拖拽行为要自己实现。我一般是在标题栏的mousePressEvent和mouseMoveEvent里手动调用Dock的浮动、移动逻辑或者干脆保留默认标题栏只改颜色。除非界面要求特别高不建议新手轻易自定义标题栏。4. 记忆用户习惯状态保存与恢复4.1 saveGeometry与saveState的实际用法做工具软件用户最在意的体验之一就是我调好的布局关掉软件再打开还是那个样子。QDockWidget的布局状态保存主要靠QMainWindow::saveState()和restoreState()。保存的时机选在closeEvent里因为用户关窗口时布局通常是最后的完整状态。void MainWindow::closeEvent(QCloseEvent *event) { QSettings settings(MyCompany, DockDemo); settings.setValue(geometry, saveGeometry()); settings.setValue(windowState, saveState()); QMainWindow::closeEvent(event); }启动时恢复放在窗口显示前调用void MainWindow::readSettings() { QSettings settings(MyCompany, DockDemo); restoreGeometry(settings.value(geometry).toByteArray()); restoreState(settings.value(windowState).toByteArray()); }QSettings很适合干这件事它会自动把配置写到注册表或配置文件里。如果不想用QSettings也可以直接读写文件本质是一样的。需要注意区分saveGeometry()和saveState()前者保存主窗口大小和位置后者才保存Dock的停靠、浮动和显隐状态两个都要存才完整。4.2 版本号不是拍脑袋定的saveState()和restoreState()还有第二个参数是version。我建议从一开始就用一个固定值比如saveState(20240401); restoreState(settings.value(windowState).toByteArray(), 20240401);version的作用是让restoreState判断当前保存的布局和程序的Dock定义是否匹配。如果你的程序更新后新增或删除了某个Dock版本号不变也基本能兼容但一旦布局数据结构发生改变restoreState找不到对应的Dock就静默失败布局恢复不到预期位置。遇到这种问题先把version改掉或者清一下注册表里的旧配置。我自己的习惯是每次调整Dock的创建逻辑就把版本号加一次避免线上用户出现“布局恢复不了”的反馈。5. 实例Demo运行中的常见问题与排查5.1 启动崩溃先检查这三个地方QDockWidget本身不算复杂但和它一起出现的崩溃通常有规律。我见过最多的三类第一同一个QDockWidget对象重复addDockWidget或者同时加到了多个QMainWindow里Qt会报警告严重时直接崩。每个Dock只能属于一个主窗口。第二Dock里的widget在Dock销毁前就被delete了。听过一句话谁创建谁释放。如果setWidget时传了一个栈上对象或者已经delete的指针运行时悬空指针就是灾难。第三在构造函数里还没有初始化完某个Dock就调用了tabifyDockWidget等操作。建议所有Dock创建完之后再统一处理合并和分栏而不是边创建边操作。5.2 面板拖不动或停靠不到预期区域如果用户反馈“面板怎么拖都拖不到右边”先看setAllowedAreas里有没有允许右边。如果允许了还拖不过去检查是不是有其他Dock占满了那块区域。在拖拽到达边缘时系统会按区域分配一定比例给新面板如果原来那个面板设置的是固定大小且不能被压缩拖拽就会被“怼”回来。还有一种情况是面板被setFeatures(QDockWidget::NoDockWidgetFeatures)锁住了。这个设置会让面板完全不能拖离当前位置看起来就像系统卡住了一样。5.3 发布到别人电脑上打不开这个和QDockWidget没有直接关系但每次Demo交付总会遇到。你在自己电脑上跑得好好的打包发给别人双击提示“windows no qt platform plugin could be initialized”。十有八九是Qt的平台插件没打到exe目录下也就是platforms文件夹缺失。解决办法很简单用Qt自带的部署工具例如windeployqt自动复制依赖。以Qt 5.15.2 MSVC版为例打完release后打开“Qt 5.15.2 (MSVC 2019 64-bit)”命令行切到exe所在目录执行windeployqt DockDemo.exe运行完检查一下目录里是否生成了platforms/qwindows.dll。没有这个文件程序在别人机器上就是起不来。此外如果用MDI或者第三方库比如qcustomplot、FFTWwindeployqt不一定能自动识别需要手动把对应DLL复制过去。5.4 问题速查表现象可能原因解决办法面板没有出现在预期位置addDockWidget的区域和allowedAreas冲突统一设置给面板明确允许区域面板无法浮动features中缺少DockWidgetFloatable加上Floatable或直接用AllDockWidgetFeatures关掉后菜单里也能勾选但面板不显示toggleViewAction没有和Dock关联用dock-toggleViewAction()创建Action别自己new QAction自定义标题栏后拖不动替换标题栏导致原拖拽逻辑失效不自定义标题栏或者在自定义控件里实现拖拽restoreState后布局乱保存和恢复的版本号不一致使用同一个固定版本号发布后exe双击没反应或报错缺少Qt插件和运行库用windeployqt部署最后再说一个我个人的小习惯所有Dock在创建时都设置objectName。不要小看这一行代码saveState在恢复布局时是根据objectName来匹配Dock的。如果两个Dock的名字都是默认的空字符串恢复时很可能把A面板的状态套到B面板上布局一团糟。给每个面板起一个稳定、不随语言变化的内部英文名是QDockWidget项目里非常值得坚持的规范。这个Demo做出来之后哪怕界面还很朴素底子的交互能力已经和不小商业软件看齐了。你后面要做图像处理工具、日志分析器或者数据看板都可以在这套骨架上一层层加东西。Dock这块投入的时间绝对值。本文还有配套的精品资源点击获取