资讯详情

资讯详情

Qt QDockWidget 实例 Demo:多面板布局、浮动窗口与状态保存

简介这份资源是面向Qt初学者与界面开发者的QDockWidget实战示例工程针对停靠窗口的创建、布局与交互这一常见难点提供可直接编译运行的完整代码帮助读者快速理解浮动面板、停靠区域与主窗口的配合方式。压缩包共81个文件包含7个cpp源文件、6个头文件与6个ui界面文件另有qrc资源文件、sln解决方案与vcxproj工程文件并附带52张png截图与说明文档整体约4.16MB工程基于VS2019与Qt5.15.2环境搭建。目前已有2523人学习下载说明该示例在入门阶段具有较高的参考价值。读者可借助完整工程结构、界面布局文件与配套截图快速上手QDockWidget的常用用法并在此基础上扩展出符合自身项目需求的停靠窗口方案。1. QDockWidget 实例 Demo为什么它是桌面工具类软件绕不开的一环做桌面端工具软件尤其是那种需要同时看多个面板、边操作边看日志、边调参数边看预览的场景主窗口里塞一个中央控件往往不够用。QDockWidget 就是 Qt 给这类需求准备的答案可停靠、可浮动、可拖拽、可关闭重开还能保存布局状态。很多开发者第一次接触它是在模仿某类集成开发环境或图像处理 Demo 的界面时——左侧是工程树右侧是属性面板底部是输出窗口中间才是主画布。这个布局不是手写死的而是用 QDockWidget 拼出来的。这篇笔记围绕一个可运行的 QDockWidget 实例 Demo 展开把创建、停靠、浮动、布局保存、信号联动、样式定制这几件事讲透。适合已经会写 Qt 基础窗口、但一遇到多面板协作就靠固定布局硬撑的开发者。读完你能自己搭出一个可复现的多面板框架知道哪些参数必须调、哪些坑一踩就翻车。2. 从零搭一个 QDockWidget Demo主窗口、停靠区与浮动窗口2.1 先想清楚为什么用 QDockWidget 而不是 QSplitterQSplitter 也能做多面板但它只解决“分割”不解决“停靠、浮动、关闭、重开、保存布局”。QDockWidget 继承自 QWidget专门作为主窗口的附属面板存在必须配合 QMainWindow 使用。QMainWindow 自带四个停靠区域Qt::LeftDockWidgetArea、Qt::RightDockWidgetArea、Qt::TopDockWidgetArea、Qt::BottomDockWidgetArea外加一个中央控件区。QDockWidget 可以停在这四个区域也可以拖出来变成独立浮动窗口还能通过 setFloating(true) 用代码控制。选型上如果你的界面需要用户自己调整面板位置、隐藏不关心的面板、下次打开还保持上次布局QDockWidget 是标准方案。如果只是固定左右分栏QSplitter 更轻。常见做法是中央放核心视图四周用 QDockWidget 挂辅助面板。2.2 最小可运行 Demo一个主窗口加三个停靠面板下面这段代码可以直接编译运行展示一个中央文本编辑区左侧工程树、右侧属性表、底部输出窗口三个停靠面板。#include QApplication #include QMainWindow #include QDockWidget #include QTextEdit #include QTreeWidget #include QTableWidget #include QLabel int main(int argc, char *argv[]) { QApplication app(argc, argv); QMainWindow mainWindow; mainWindow.setWindowTitle(QDockWidget Demo); mainWindow.resize(1200, 800); // 中央控件核心编辑区 QTextEdit *centralEdit new QTextEdit; centralEdit-setPlaceholderText(中央编辑区); mainWindow.setCentralWidget(centralEdit); // 左侧停靠工程树 QDockWidget *leftDock new QDockWidget(工程树, mainWindow); QTreeWidget *tree new QTreeWidget; tree-setHeaderLabel(结构); leftDock-setWidget(tree); mainWindow.addDockWidget(Qt::LeftDockWidgetArea, leftDock); // 右侧停靠属性表 QDockWidget *rightDock new QDockWidget(属性, mainWindow); QTableWidget *table new QTableWidget(3, 2); table-setHorizontalHeaderLabels({参数, 值}); rightDock-setWidget(table); mainWindow.addDockWidget(Qt::RightDockWidgetArea, rightDock); // 底部停靠输出窗口 QDockWidget *bottomDock new QDockWidget(输出, mainWindow); QLabel *outputLabel new QLabel(运行日志...); bottomDock-setWidget(outputLabel); mainWindow.addDockWidget(Qt::BottomDockWidgetArea, bottomDock); mainWindow.show(); return app.exec(); }逻辑说明每个 QDockWidget 构造时传入标题和父窗口setWidget 设置内部控件addDockWidget 指定初始停靠区域。中央控件必须用 setCentralWidget 设置否则主窗口布局会异常。参数说明addDockWidget 的第一个参数决定初始位置后续用户可拖拽改变。QDockWidget 的标题会显示在停靠栏上也是浮动时的窗口标题。2.3 控制停靠行为允许区域、浮动与关闭默认情况下QDockWidget 可以被拖到任意区域。如果你希望某个面板只能停在左右两侧需要设置允许的停靠区域。// 只允许左右停靠 leftDock-setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); // 禁止关闭按钮 rightDock-setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); // 代码控制浮动 bottomDock-setFloating(true);setAllowedAreas 限制拖拽范围setFeatures 控制是否可移动、可浮动、可关闭。常见组合是工具面板允许移动和浮动但禁止关闭日志面板允许关闭但禁止浮动。setFloating(true) 会让面板立刻脱离主窗口适合做“弹出预览”功能。注意setFloating 在面板未显示时调用可能无效先 show() 再设置。3. 布局保存与恢复让用户下次打开还是原来的样子3.1 saveState 与 restoreState 的正确用法QDockWidget 的布局保存依赖 QMainWindow 的 saveState 和 restoreState。saveState 返回 QByteArray里面记录了每个停靠面板的位置、大小、浮动状态和可见性。restoreState 接收这个 QByteArray 并还原。// 保存布局到文件 QByteArray state mainWindow.saveState(); QFile file(layout.bin); if (file.open(QIODevice::WriteOnly)) { file.write(state); file.close(); } // 恢复布局 QFile readFile(layout.bin); if (readFile.open(QIODevice::ReadOnly)) { QByteArray savedState readFile.readAll(); mainWindow.restoreState(savedState); readFile.close(); }逻辑说明saveState 必须在所有 QDockWidget 创建并 addDockWidget 之后调用否则保存的状态不完整。restoreState 返回 bool失败时通常是因为状态数据损坏或版本不匹配。参数说明saveState 可以传版本号restoreState 对应传入相同版本号用于兼容旧布局。常见做法是存到 QSettings 而不是裸文件避免路径权限问题。3.2 用 QSettings 持久化布局跨平台且省心裸文件读写要处理路径、权限、清理QSettings 更省事。下面是把布局存到注册表或配置文件的写法。QSettings settings(MyCompany, DockDemo); // 保存 settings.setValue(mainWindow/layout, mainWindow.saveState()); settings.setValue(mainWindow/geometry, mainWindow.saveGeometry()); // 恢复 QByteArray layout settings.value(mainWindow/layout).toByteArray(); if (!layout.isEmpty()) { mainWindow.restoreState(layout); } QByteArray geometry settings.value(mainWindow/geometry).toByteArray(); if (!geometry.isEmpty()) { mainWindow.restoreGeometry(geometry); }逻辑说明saveGeometry 保存窗口整体位置和大小saveState 保存停靠面板布局两者配合才能完整还原。QSettings 在 Windows 写注册表在 Linux/macOS 写配置文件跨平台无需改代码。参数说明QSettings 构造参数是组织名和应用名建议用固定字符串。value 读取时给默认值避免首次运行返回空。3.3 恢复布局时的常见异常与处理restoreState 失败最常见的原因是保存时面板数量和恢复时不一致。比如你保存了三个面板后来代码里删了一个恢复时就会错位。另一个坑是 restoreState 调用时机太早面板还没 addDockWidget 完。处理方式先创建所有面板并 addDockWidget再 restoreState。如果面板数量会变给每个 QDockWidget 设置唯一的 objectNamerestoreState 靠 objectName 匹配。leftDock-setObjectName(leftDock); rightDock-setObjectName(rightDock); bottomDock-setObjectName(bottomDock);注意objectName 不设置saveState 可能无法正确匹配恢复后面板位置全乱。4. 面板联动与信号QDockWidget 不只是容器4.1 visibilityChanged 与 topLevelChanged 信号QDockWidget 自带两个实用信号visibilityChanged(bool) 在面板显示或隐藏时发出topLevelChanged(bool) 在面板浮动或停靠时发出。用它们可以同步菜单勾选状态、调整其他面板行为。connect(leftDock, QDockWidget::visibilityChanged, this, [](bool visible){ qDebug() 工程树面板可见性: visible; }); connect(bottomDock, QDockWidget::topLevelChanged, this, [](bool floating){ qDebug() 输出面板浮动状态: floating; });逻辑说明visibilityChanged 适合更新“视图”菜单里的勾选项topLevelChanged 适合在浮动时改变面板内部布局或样式。参数说明信号参数是 boolvisible 为 true 表示显示floating 为 true 表示已浮动。4.2 用 QAction 控制面板显示与隐藏QMainWindow 提供 toggleViewAction()直接返回一个可勾选的 QAction绑定到菜单或工具栏即可控制面板显隐。QMenu *viewMenu menuBar()-addMenu(视图); viewMenu-addAction(leftDock-toggleViewAction()); viewMenu-addAction(rightDock-toggleViewAction()); viewMenu-addAction(bottomDock-toggleViewAction());逻辑说明toggleViewAction 自动处理勾选状态和面板显隐的同步不需要手动 connect。参数说明返回的 QAction 文本默认是面板标题可以 setText 改。4.3 面板间通信中央控件与停靠面板的数据同步停靠面板里的控件和中央控件往往需要联动。比如点击工程树节点中央编辑区加载对应内容属性表修改后中央预览刷新。做法是面板内部控件发信号主窗口统一转发。// 工程树点击 - 中央编辑区显示节点名 connect(tree, QTreeWidget::itemClicked, this, [](QTreeWidgetItem *item, int){ centralEdit-setText(当前节点: item-text(0)); }); // 属性表修改 - 输出面板打印 connect(table, QTableWidget::cellChanged, this, [](int row, int col){ outputLabel-setText(QString(单元格(%1,%2)已修改).arg(row).arg(col)); });逻辑说明信号连接放在主窗口里面板之间不直接互相引用降低耦合。中央控件作为数据中枢停靠面板作为输入或输出端。参数说明itemClicked 的第二个参数是列号cellChanged 的 row/col 从 0 开始。5. 避坑与排查QDockWidget 最容易翻车的 5 个地方5.1 面板恢复后位置错乱或消失现象restoreState 后面板跑到奇怪位置或者直接不见了。原因保存布局时面板的 objectName 没设置或者恢复时面板还没创建完。解决给每个 QDockWidget 设置唯一 objectName确保 restoreState 在所有 addDockWidget 之后调用。5.2 浮动面板关闭后无法重新显示现象用户把面板拖出来关掉菜单里勾选也没反应。原因浮动窗口关闭时toggleViewAction 的状态可能不同步。解决在 visibilityChanged 信号里手动同步 QAction 的 checked 状态或者用 restoreState 恢复默认布局。5.3 setWidget 后控件不显示或大小异常现象面板里放了控件但显示空白或者挤成一条线。原因QDockWidget 的 setWidget 会接管控件所有权如果控件没有父对象或布局没设置尺寸策略会失效。解决给内部控件设置合适的布局和 minimumSize必要时调用 setMinimumSize 限制面板最小尺寸。5.4 样式表对 QDockWidget 标题栏不生效现象用 setStyleSheet 改面板背景标题栏还是系统默认样式。原因QDockWidget 的标题栏是内部私有控件样式表选择器需要精确匹配。解决用 QDockWidget::title 选择器或者直接定制 QDockWidget 的 style。QDockWidget { titlebar-close-icon: url(close.png); titlebar-normal-icon: url(float.png); } QDockWidget::title { background: #2d2d2d; padding: 4px; }5.5 多显示器下浮动面板位置丢失现象把面板拖到副屏重启后浮动面板回到主屏或消失。原因saveState 记录的是相对坐标多屏切换后坐标系变化。解决恢复布局后检查浮动面板的 geometry如果不在任何屏幕内手动移到主屏。if (bottomDock-isFloating()) { QRect screenRect QApplication::primaryScreen()-availableGeometry(); if (!screenRect.intersects(bottomDock-geometry())) { bottomDock-move(screenRect.center()); } }6. 进阶技巧让 QDockWidget 更贴近真实工具软件6.1 自定义标题栏按钮与双击浮动默认标题栏只有关闭和浮动按钮。如果你想要“最大化面板”“锁定布局”这类按钮需要替换标题栏控件。常见做法是创建一个 QWidget 作为自定义标题栏用 setTitleBarWidget 替换默认的。QWidget *customTitleBar new QWidget; QHBoxLayout *layout new QHBoxLayout(customTitleBar); layout-setContentsMargins(4, 2, 4, 2); QLabel *titleLabel new QLabel(工程树); QPushButton *lockBtn new QPushButton(锁); layout-addWidget(titleLabel); layout-addStretch(); layout-addWidget(lockBtn); leftDock-setTitleBarWidget(customTitleBar);逻辑说明setTitleBarWidget 会接管标题栏的绘制和事件双击浮动等默认行为需要自己实现。参数说明自定义标题栏后样式表选择器要相应调整否则可能不生效。6.2 用 QSS 统一面板视觉风格工具类软件通常有暗色主题。QDockWidget 的 QSS 需要同时处理标题栏、内容区、分隔条。QDockWidget { color: #cccccc; font-size: 12px; } QDockWidget::title { background: #3c3c3c; border-bottom: 1px solid #555; padding: 6px; text-align: left; } QDockWidget::close-button, QDockWidget::float-button { background: transparent; border: none; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background: #555; }逻辑说明QDockWidget::title 控制标题栏close-button 和 float-button 控制按钮。内容区样式由内部控件自己决定。参数说明暗色主题下按钮图标可能需要替换为浅色版本否则看不清。6.3 布局锁定禁止用户拖拽有些软件在“锁定布局”模式下禁止拖拽面板。实现方式是遍历所有 QDockWidget把 features 里的 DockWidgetMovable 去掉。void setLayoutLocked(bool locked) { QListQDockWidget* docks findChildrenQDockWidget*(); for (QDockWidget *dock : docks) { QDockWidget::DockWidgetFeatures features dock-features(); if (locked) { features ~QDockWidget::DockWidgetMovable; features ~QDockWidget::DockWidgetFloatable; } else { features | QDockWidget::DockWidgetMovable; features | QDockWidget::DockWidgetFloatable; } dock-setFeatures(features); } }逻辑说明findChildren 递归查找所有 QDockWidget按需增删 features 标志位。锁定后用户无法拖拽和浮动但关闭按钮仍可用。参数说明如果希望锁定后连关闭也禁止同时去掉 DockWidgetClosable。6.4 验证布局是否生效的简单方法改完布局代码后不要只看一次运行效果。正确验证步骤是运行程序拖拽面板到新位置关闭程序重新打开看布局是否还原。再测试浮动面板、关闭面板、多显示器切换。如果 restoreState 返回 false打印保存的 QByteArray 长度长度为 0 说明保存失败。我自己的习惯是每次改完布局相关代码先手动拖一遍所有面板再重启验证。这个流程能挡住八成以上的布局 bug。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →