#ifndef CUSTOMDOCKWIDGET_H#define CUSTOMDOCKWIDGET_H#include QDockWidget#include QStringListclass QStackedWidget;class CustomDockWidget : public QDockWidget{Q_OBJECTpublic:enum TabPosition {West 0, // 左侧停靠North 1, // 顶部停靠East 2, // 右侧停靠South 3 // 底部停靠};Q_ENUM(TabPosition)explicit CustomDockWidget(const QString title, TabPosition pos West, QWidget *parent nullptr);void addPage(QWidget *page, const QString label);void setCurrentIndex(int index);int currentIndex() const;protected:void paintEvent(QPaintEvent *) override;void mousePressEvent(QMouseEvent *) override;private:QRect tabRect(int i) const;int tabAt(const QPoint pos) const;QStackedWidget *m_stack nullptr;QStringList m_labels;int m_current 0;TabPosition m_pos West;static constexpr int m_tabBarThick 22;};#endif#include CustomDockWidget.h#include QStackedWidget#include QPainter#include QMouseEvent#include QHBoxLayoutCustomDockWidget::CustomDockWidget(const QString title, TabPosition pos, QWidget *parent): QDockWidget(title, parent), m_pos(pos){setObjectName(QStringLiteral(CustomDock_) title);setFeatures(DockWidgetMovable | DockWidgetClosable | DockWidgetFloatable);m_stack new QStackedWidget();auto *layout new QHBoxLayout();layout-setSpacing(0);switch (m_pos) {case West: layout-setContentsMargins(m_tabBarThick, 0, 0, 0); break;case East: layout-setContentsMargins(0, 0, m_tabBarThick, 0); break;case North: layout-setContentsMargins(0, m_tabBarThick, 0, 0); break;case South: layout-setContentsMargins(0, 0, 0, m_tabBarThick); break;}layout-addWidget(m_stack);auto *container new QWidget(this);container-setLayout(layout);setWidget(container);}void CustomDockWidget::addPage(QWidget *page, const QString label){m_stack-addWidget(page);m_labels label;update();}void CustomDockWidget::setCurrentIndex(int index){if (index 0 index m_stack-count()) {m_current index;m_stack-setCurrentIndex(index);update();}}int CustomDockWidget::currentIndex() const{return m_current;}QRect CustomDockWidget::tabRect(int i) const{QWidget *c QDockWidget::widget();int cx c ? c-x() : 0;int cy c ? c-y() : 0;int cw c ? c-width() : 0;int ch c ? c-height() : 0;QFont f font(); f.setPointSize(9);QFontMetrics fm(f);if (m_pos West || m_pos East) {int y cy;for (int j 0; j i; j)y fm.horizontalAdvance(m_labels.value(j)) 20;int tabH fm.horizontalAdvance(m_labels.value(i)) 20;if (m_pos West)return QRect(cx, y, m_tabBarThick, tabH);elsereturn QRect(cx cw - m_tabBarThick, y, m_tabBarThick, tabH);} else {int x cx;for (int j 0; j i; j)x fm.horizontalAdvance(m_labels.value(j)) 20;int tabW fm.horizontalAdvance(m_labels.value(i)) 20;if (m_pos North)return QRect(x, cy, tabW, m_tabBarThick);elsereturn QRect(x, cy ch - m_tabBarThick, tabW, m_tabBarThick);}}int CustomDockWidget::tabAt(const QPoint pos) const{for (int i 0; i m_labels.size(); i)if (tabRect(i).contains(pos)) return i;return -1;}void CustomDockWidget::paintEvent(QPaintEvent *event){QDockWidget::paintEvent(event);QPainter p(this);p.setRenderHint(QPainter::Antialiasing);QPalette pal palette();QColor borderColor(217, 217, 217);for (int i 0; i m_labels.size(); i) {QRect r tabRect(i);bool sel (i m_current);QRect drawR r;if (sel) {switch (m_pos) {case West: drawR r.adjusted(0, 0, 2, 0); break;case East: drawR r.adjusted(-2, 0, 0, 0); break;case North: drawR r.adjusted(0, 0, 0, 2); break;case South: drawR r.adjusted(0, -2, 0, 0); break;}}if (sel) {p.setBrush(pal.base());p.setPen(Qt::NoPen);p.drawRect(drawR);} else {p.setBrush(pal.window());p.setPen(QPen(borderColor, 1));p.drawRect(drawR);}QFont f font();f.setPointSize(9);p.setFont(f);p.setPen(sel ? pal.windowText().color() : pal.buttonText().color());if (m_pos West) {p.save();p.translate(r.center());p.rotate(-90);p.drawText(QRect(-r.height() / 2, -r.width() / 2,r.height(), r.width()),Qt::AlignCenter, m_labels[i]);p.restore();} else if (m_pos East) {p.save();p.translate(r.center());p.rotate(90);p.drawText(QRect(-r.height() / 2, -r.width() / 2,r.height(), r.width()),Qt::AlignCenter, m_labels[i]);p.restore();} else {p.drawText(r, Qt::AlignCenter, m_labels[i]);}}}void CustomDockWidget::mousePressEvent(QMouseEvent *event){int idx tabAt(event-pos());if (idx 0) {setCurrentIndex(idx);return;}QDockWidget::mousePressEvent(event);}#include MainWindow.h#include CustomDockWidget.h#include QDockWidget#include QTreeWidget#include QTextEdit#include QToolBox#include QStackedWidget#include QPushButton#include QButtonGroup#include QHBoxLayout#include QVBoxLayout#include QListWidget#include QSplitter#include QTabWidget#include QLabel#include QMenuBar#include QMenu#include QAction#include QStatusBar#include QApplication#include QStyle// ---------------------------------------------------------------------------// Construction// ---------------------------------------------------------------------------MainWindow::MainWindow(QWidget *parent): QMainWindow(parent){setWindowTitle(QStringLiteral(QDockWidget 多面板示例));resize(1200, 800);createDockPanels();// --- Central widget ---auto *centralLabel new QLabel(QStringLiteral(h2欢迎使用 QDockWidget 示例/h2p左侧面板使用 CustomDockWidgetWest 方向/pp右侧面板使用 QTabWidgetEast 方向/pp您可以用 CustomDockWidget 指定停靠按钮到 West / North / East / South 任意方向。/p),this);centralLabel-setAlignment(Qt::AlignCenter);setCentralWidget(centralLabel);// --- Menu ---auto *viewMenu menuBar()-addMenu(QStringLiteral(视图(V)));auto toggleAct [](QDockWidget *dw, const QString text) {auto *act dw-toggleViewAction();act-setText(text);viewMenu-addAction(act);};toggleAct(m_leftDock, QStringLiteral(左侧面板(L)));toggleAct(m_rightDock, QStringLiteral(右侧面板(R)));toggleAct(m_outputDock, QStringLiteral(输出窗口(O)));// --- Status bar ---statusBar()-showMessage(QStringLiteral(就绪 | 拖动面板可重新布局));}// ---------------------------------------------------------------------------// Dock panel factory// ---------------------------------------------------------------------------void MainWindow::createDockPanels(){createLeftPanel();createPropertiesDock();createOutputDock();createToolboxDock();// --- Right: QDockWidget 内嵌 QTabWidget停靠按钮在右侧 ---m_rightDock new QDockWidget(QStringLiteral(面板), this);m_rightDock-setObjectName(QStringLiteral(RightPanelDock));m_rightDock-setFeatures(QDockWidget::DockWidgetMovable |QDockWidget::DockWidgetClosable |QDockWidget::DockWidgetFloatable);m_rightTab new QTabWidget(m_rightDock);m_rightTab-setTabPosition(QTabWidget::East);m_rightTab-addTab(m_propTree, QStringLiteral(属性));m_rightTab-addTab(m_toolBox, QStringLiteral(工具箱));m_rightDock-setWidget(m_rightTab);// --- 布局 ---addDockWidget(Qt::LeftDockWidgetArea, m_leftDock);addDockWidget(Qt::RightDockWidgetArea, m_rightDock);addDockWidget(Qt::BottomDockWidgetArea, m_outputDock);}// ---------------------------------------------------------------------------// ① 左侧面板CustomDockWidget按钮在左侧West// ---------------------------------------------------------------------------void MainWindow::createLeftPanel(){m_fileTree new QTreeWidget();m_fileTree-setHeaderLabel(QStringLiteral(项目文件));m_fileTree-setAnimated(true);auto *src new QTreeWidgetItem(m_fileTree, {QStringLiteral(src)});src-addChild(new QTreeWidgetItem({QStringLiteral(main.cpp)}));src-addChild(new QTreeWidgetItem({QStringLiteral(mainwindow.cpp)}));src-addChild(new QTreeWidgetItem({QStringLiteral(mainwindow.h)}));auto *resources new QTreeWidgetItem(m_fileTree, {QStringLiteral(resources)});resources-addChild(new QTreeWidgetItem({QStringLiteral(icons)}));resources-addChild(new QTreeWidgetItem({QStringLiteral(styles.qss)}));m_fileTree-expandAll();m_favList new QListWidget();m_favList-addItem(QStringLiteral(常用头文件));m_favList-addItem(QStringLiteral(核心算法模块));m_favList-addItem(QStringLiteral(配置文件));m_favList-addItem(QStringLiteral(构建脚本));m_favList-addItem(QStringLiteral(文档资源));// CustomDockWidget 第二个参数指定停靠按钮方向// CustomDockWidget::West — 左侧默认// CustomDockWidget::North — 顶部// CustomDockWidget::East — 右侧// CustomDockWidget::South — 底部m_leftDock new CustomDockWidget(QStringLiteral(项目),CustomDockWidget::West, // ← 停靠按钮在左侧this);m_leftDock-addPage(m_fileTree, QStringLiteral(文件浏览器));m_leftDock-addPage(m_favList, QStringLiteral(收藏));}// ---------------------------------------------------------------------------// ② 属性面板嵌入右侧 QTabWidget// ---------------------------------------------------------------------------void MainWindow::createPropertiesDock(){m_propTree new QTreeWidget();m_propTree-setHeaderLabels({QStringLiteral(属性), QStringLiteral(值)});m_propTree-setAlternatingRowColors(true);auto addProp [](const QString cat, const QString key, const QString val) {auto *item new QTreeWidgetItem({key, val});if (!cat.isEmpty()) {auto *parent new QTreeWidgetItem(m_propTree, {cat});parent-addChild(item);parent-setExpanded(true);} else {m_propTree-addTopLevelItem(item);}};addProp(QString(), QStringLiteral(名称), QStringLiteral(MainWindow));addProp(QString(), QStringLiteral(类名), QStringLiteral(QMainWindow));addProp(QStringLiteral(几何), QStringLiteral(位置), QStringLiteral((0, 0)));addProp(QStringLiteral(几何), QStringLiteral(尺寸), QStringLiteral(1200 x 800));addProp(QStringLiteral(样式), QStringLiteral(主题), QStringLiteral(Fusion));}// ---------------------------------------------------------------------------// ③ 输出面板底部// ---------------------------------------------------------------------------void MainWindow::createOutputDock(){m_outputDock new QDockWidget(QStringLiteral(输出), this);m_outputDock-setObjectName(QStringLiteral(OutputDock));m_outputDock-setFeatures(QDockWidget::DockWidgetMovable |QDockWidget::DockWidgetClosable |QDockWidget::DockWidgetFloatable);m_outputEdit new QTextEdit(m_outputDock);m_outputEdit-setReadOnly(true);m_outputEdit-setFont(QFont(QStringLiteral(Consolas), 10));m_outputEdit-append(QStringLiteral([系统] 程序已启动));m_outputEdit-append(QStringLiteral([系统] 主窗口尺寸1200 x 800));m_outputEdit-append(QStringLiteral([系统] DockWidget 初始化完成));m_outputEdit-append(QStringLiteral([信息] 您可以拖动面板重新布局));m_outputDock-setWidget(m_outputEdit);}// ---------------------------------------------------------------------------// ④ 工具箱内容嵌入右侧 QTabWidget// ---------------------------------------------------------------------------void MainWindow::createToolboxDock(){m_toolBox new QToolBox();auto *page1 new QLabel(QStringLiteral( 选择工具\n 画笔\n 橡皮擦\n 填充\n 文本));page1-setAlignment(Qt::AlignTop | Qt::AlignLeft);page1-setMargin(12);m_toolBox-addItem(page1, QIcon(), QStringLiteral(常用工具));auto *page2 new QLabel(QStringLiteral( 矩形\n 椭圆\n 线条\n 贝塞尔曲线));page2-setAlignment(Qt::AlignTop | Qt::AlignLeft);page2-setMargin(12);m_toolBox-addItem(page2, QIcon(), QStringLiteral(形状工具));auto *page3 new QLabel(QStringLiteral( 取色器\n 标尺\n 放大镜));page3-setAlignment(Qt::AlignTop | Qt::AlignLeft);page3-setMargin(12);m_toolBox-addItem(page3, QIcon(), QStringLiteral(辅助工具));}