Qt QMainWindow实战:菜单、工具栏、状态栏、浮动窗口与对话框全解析 上个月帮一个刚开始学Qt的朋友调界面他问了个很典型的问题为什么我的按钮、文本框都会用一往主窗口里塞菜单、工具栏、状态栏、浮动窗口和对话框就乱套其实这几个组件的用法本身都不难难就难在它们共享同一个核心对象——QAction以及主窗口对布局、停靠、显隐有一套默认管理逻辑搞懂这套逻辑之后Qt窗口开发基本就通了一半。这篇文章我就按Qt Widgets开发的主线把菜单、工具栏、状态栏、浮动窗口、对话框这几块逐一拆开讲清楚。内容主要面向正在学Qt Widgets、准备用QMainWindow做桌面工具或编辑器的朋友我会把每个组件“为什么这么设计”“实际代码怎么写”“常见的坑在哪里”一次说透最后用一个完整的骨架示例把它们串起来。1. 为什么主窗口要用QMainWindow五个区域的分工逻辑很多新手分不清QWidget和QMainWindow的区别甚至有人直接用QWidget开个窗口就往上面堆控件。这事本身不违法但你一旦需要菜单栏、状态栏、可停靠面板QWidget就完全管不过来了。QMainWindow之所以成为桌面应用主窗口的标准选择是因为它在内部替你规划好了五个区域区域对应类管理入口典型用途菜单栏QMenuBarmenuBar() / setMenuBar()文件、编辑、视图等顶层菜单工具栏QToolBaraddToolBar()常用动作的快捷入口状态栏QStatusBarstatusBar()提示信息、坐标、进度反馈浮动窗口QDockWidgetaddDockWidget()素材列表、图层面板、属性面板中央部件QWidget 及其子类setCentralWidget()主内容区比如画布、列表、编辑器1.1 QMainWindow帮你省掉哪些事假如不用QMainWindow纯用QWidget加布局你得亲手拼一个“顶部菜单条 工具栏 中间内容 底部状态栏”的垂直布局。界面静态时看着还行一旦用户想把工具栏拖出来、把面板停靠到左边、或者把状态栏的某个控件隐藏所有这些重排逻辑都得自己写。而QMainWindow把这些都内建好了菜单栏固定在顶部、状态栏固定在底部、工具栏可以拖到四周或悬浮、QDockWidget可以被用户自由拖拽成独立窗口。它天生就是“桌面应用主窗口”的完整骨架所以不要拿QWidget硬扛。1.2 中央部件的正确打开方式QMainWindow本身不是一个可布置控件的容器你必须通过setCentralWidget()指定主内容区。常见错误是试图给QMainWindow直接调用setLayout()比如// 错误示范 auto *layout new QVBoxLayout(this); // this是QMainWindow setLayout(layout);Qt会直接报错因为QMainWindow不允许你再给它套一个顶层布局。正确的做法是新建一个QWidget作为中央部件在它内部做布局再把整个部件塞进去auto *central new QWidget(this); auto *layout new QVBoxLayout(central); layout-addWidget(someContent); setCentralWidget(central);如果不设置任何中央部件窗口中央会是一片空白而且整个窗口缩小到一定程度后工具栏或浮动窗口很容易把中央区域挤成一条细缝。就算你暂时不打算放主内容也建议先放一个QLabel占位并给它一个合适的字符内容或最小尺寸别让它空着。1.3 一个容易忽略的初始化顺序在构造函数里创建这些东西建议按照“动作对象 → 菜单栏 → 工具栏 → 状态栏 → 中央部件 → 浮动窗口”的节奏来。这个顺序不是强制的但你先定义好所有QAction后面不管菜单还是工具栏都只是“把动作挂进去”代码读起来非常顺。如果反过来先建菜单再定义动作你会发现addAction需要传一个还不存在的对象要么写成new QAction(...)临时创建要么回头再改很别扭。2. 菜单栏与QAction动作对象才是真正的幕后主角菜单栏本身的结构很简单QMenuBar下挂QMenuQMenu下挂QAction。但真正值得你花时间理解的是QAction。它不只是“一个菜单项”而是整个界面动作的中枢文本、图标、快捷键、状态提示、可勾选状态、触发信号全部打包在一个对象里。2.1 三行代码搭出菜单树QMenuBar *bar menuBar(); QMenu *fileMenu bar-addMenu(QStringLiteral(文件(F))); fileMenu-addAction(openAction);这里menuBar()会自动创建主窗口的菜单栏你不需要手动new。addMenu返回的是QMenu指针addAction挂上的是QAction。每一个顶层菜单、每一个下拉项背后都是一个QMenu/QAction的组合。菜单文本里的“(F)”是Qt的访问键语法显示时会出现带下划线的F用户按AltF就能展开这个菜单。中文软件里很常见注意别乱写一个顶层菜单里最好只保留一个明确的访问键字符。2.2 集中配置动作的细节QAction最有价值的一点是“集中配置、多处复用”。一个打开动作可以这样定义openAction new QAction(QStringLiteral(打开(O)), this); openAction-setIcon(QIcon::fromTheme(QStringLiteral(document-open))); openAction-setShortcut(QKeySequence::Open); openAction-setStatusTip(QStringLiteral(打开一个图像文件)); connect(openAction, QAction::triggered, this, MainWindow::openFile);其中setShortcut不要自己写硬编码按键字符串“CtrlO”直接用QKeySequence::Open这类标准语义名称。好处是跨平台默认键位正确Windows和Linux上是CtrlOmocOS上通常是CommandO而且菜单右侧会自动显示这个快捷键不需要额外代码。statusTip是个很妙的机制只要给QAction设了状态提示当鼠标悬停在菜单项或者工具栏按钮上时主窗口的状态栏会自动显示这段文字。你不需要手动捕获悬停事件也不需要自己调用状态栏显示函数框架全给你接好了。2.3 checkable菜单项的一个易错点菜单里如果放“显示网格”“锁定选区”这类开关可以给QAction设置setCheckable(true)点击后菜单项前面会自动出现勾选标记。这里面有个小坑这一类动作的勾选状态由框架自动维护你的槽函数只需要根据isChecked()去执行逻辑不要在槽里再手动setChecked()去设置状态。我之前调试一个Demo时在槽里写了句action-setChecked(action-isChecked());看起来毫无意义但实际因为信号触发时机在状态翻转之后这种多余操作有时会把状态再翻回去导致界面显示和实际状态不一致。如果你确实需要在某个时机改变勾选状态用setChecked没问题但要搞清楚触发顺序别在triggered槽里反复改写。另外一个常见需求是右键菜单。它和菜单栏没直接关系但用的也是QAction和QMenuconnect(tree, QWidget::customContextMenuRequested, this, [](const QPoint pos) { QMenu menu(this); menu.addAction(addAction); menu.addAction(removeAction); menu.exec(tree-mapToGlobal(pos)); });使用exec()弹出时菜单会进入一个本地事件循环。在菜单打开期间主窗口的其它交互仍然可以继续所以弹窗里的动作槽函数正常执行不需要担心阻塞问题。唯一要注意的是exec()返回后pos可能已经失效不要保存mapToGlobal之后再用直接当场使用即可。3. 工具栏同一个动作在多个入口复用的技术与雷区工具栏和菜单的区别在于展示形态菜单是文字列表工具栏是按钮集合。但两者背后都是QAction所以你做一次动作定义可以同时出现在菜单和工具栏里触发同一个槽函数状态同步更新这是Qt里很上瘾的设计模式。3.1 创建工具栏并设置显示风格QToolBar *toolBar addToolBar(QStringLiteral(文件工具)); toolBar-setObjectName(QStringLiteral(fileToolBar)); toolBar-setMovable(true); toolBar-setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); toolBar-setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBar-setIconSize(QSize(24, 24)); toolBar-addAction(openAction);这里特别提醒一下setObjectName。如果你希望窗口布局状态能保存和恢复工具栏和浮动窗口都必须有唯一的objectName因为saveState()和restoreState()就是靠它识别各个部件的。很多人在保存布局时发现还原不了就是这个原因。toolBar-setToolButtonStyle控制按钮显示方式常用的有只显示图标、只显示文字、图标旁边显示文字。实际桌面软件里纯图标更省空间但需要配statusTip提示文字文字旁边加图标对新手更友好看按钮就知道干什么用的调试阶段我习惯用Qt::ToolButtonTextBesideIcon跑通后界面要精简再换成纯图标。3.2 一个QAction挂两个入口的收益你可能会想菜单里已经有“打开”了工具栏再放一个是不是要把同一个槽函数connect两次完全不需要。QAction只要connect过一次你在菜单和工具栏都执行addAction同一个动作的所有触发信号都会走到同一个槽函数fileMenu-addAction(openAction); // 菜单入口 toolBar-addAction(openAction); // 工具栏入口这种复用带来的好处是“一处改、多处动”。比如某段时间文件列表为空你想禁用打开功能只需要openAction-setEnabled(false)菜单项和工具栏按钮同时变灰。再比如打开功能正在处理一个文件你想让所有入口同时显示忙碌状态也依然是只操作这一个动作对象。这就是为什么我强烈建议把每个核心动作先定义为成员变量而不是在菜单和工具栏里各自new一个QAction。3.3 工具栏右键菜单和控件尺寸的坑工具栏默认自带一个右键菜单里面是所有工具栏的显隐列表。这个行为对用户很友好但如果你又给ToolBar设置了自定义的context menu两者会叠加可能出现弹两个菜单的现象。我的做法是工具栏的右键菜单保持默认不在工具栏上额外设置customContextMenuRequested自定义右键菜单只放在文档列表、画布这类内容控件上。另一个细节更容易踩。QToolBar可以直接addWidget普通控件比如把QSpinBox放进工具栏做缩放比例QSpinBox *zoomSpin new QSpinBox(toolBar); zoomSpin-setRange(10, 800); toolBar-addWidget(zoomSpin);实际运行后你可能会发现这个SpinBox变得很窄数字都显示不全。原因是QToolBar对普通控件不会主动给宽松的宽度策略需要你手动设置minimumWidth或sizePolicy。我一般在addWidget之后补一句zoomSpin-setMinimumWidth(90)避免它在某些平台、某些拖拽布局下被压扁。4. 状态栏临时提示、常驻信息和进度反馈三类打法状态栏看似不起眼却是主窗口里“存在感”最强的部分。用户不一定会看但一旦你需要给用户一个位置感、进度感或操作反馈状态栏是最合适的位置。QStatusBar支持三类内容用法完全不同不要混在一起。4.1 showMessage与statusTip的关系showMessage是最基础的临时消息接口statusBar()-showMessage(QStringLiteral(正在保存文件...), 2000);第二个参数是显示时长单位毫秒2秒后自动清空。如果把时长写成0消息会一直显示到下次showMessage被调用。这个特性可以用来显示“就绪”这类长期状态。很多新手在初始化时写statusBar()-showMessage(QStringLiteral(就绪))却不给时长以为它会一直挂在那里。实际上不带时长默认是0也就是一直显示直到被新消息覆盖这个行为完全没问题。另外要记住菜单或工具栏悬停提示由QAction::statusTip自动驱动它内部也会调用状态栏的临时显示逻辑。所以你在槽函数里写showMessage时不要在2秒后马上又被控件的悬停事件清掉——我自己通常遵循一个约定悬停类的提示交给statusTip不要额外写代码而点击动作完成后的反馈用showMessage(成功, 2000)两者各管各的不会冲突。4.2 addWidget与addPermanentWidget的区别除了临时文字状态栏还可以放常驻控件最常见的例子是图片查看器左下角的坐标、右下角的缩放比例QStatusBar *sb statusBar(); QLabel *coordLabel new QLabel(QStringLiteral(0, 0), this); sb-addWidget(coordLabel); QLabel *zoomLabel new QLabel(QStringLiteral(100%), this); sb-addPermanentWidget(zoomLabel);addWidget添加的控件靠左排列会按添加顺序依次排在已有内容后面addPermanentWidget添加的控件靠右排列在窗口宽度不够时应优先被压缩或省略。理解“永久”不是“一直存在”而是“位置固定在右侧远端”。如果你的左侧控件和右侧控件都想常驻注意别把窗口拉到太窄否则右侧还是可能被挤出可见区域。对QLabel这种控件默认的sizePolicy一般够用。但如果你放的是QProgressBar、QComboBox这类有最小尺寸要求的控件建议手动设置一次最小宽度避免在不同系统下出现显示不全。4.3 状态栏放进度条线程归宿要注意后台任务往状态栏塞一个进度条是常见做法QProgressBar *progress new QProgressBar(this); progress-setRange(0, 100); progress-setValue(0); statusBar()-addPermanentWidget(progress);关键问题是进度条更新千万不能在工作线程里直接调用setValue。QWidget不是线程安全的在工作线程里直接操作界面控件轻则界面不同步重则崩溃。标准方案是让工作线程发信号、主线程槽函数更新进度条connect(worker, Worker::progressUpdated, progress, QProgressBar::setValue);信号槽机制检测到发信号的线程和接收对象所在的线程不同会自动走队列连接槽函数会安全地切回主线程执行。这里不需要额外加线程锁例外情况是你在工作线程里自定义了lambda去捕获进度条指针并调用那就要封装干净不要裸操作控件。5. 浮动窗口可停靠面板的布局规则与用户习惯标题里的“浮动窗口”对应Qt里的QDockWidget。这个东西中文名容易误解它的核心能力不只是“浮起来”而是“可停靠、可拖动、可关闭、可重新组合面板”。素材列表、图层面板、属性编辑器甚至聊天窗口都是典型场景。5.1 创建Dock部件并控制停靠区域auto *layerDock new QDockWidget(QStringLiteral(图层), this); layerDock-setObjectName(QStringLiteral(layerDock)); QListWidget *list new QListWidget(layerDock); list-addItem(QStringLiteral(背景层)); list-addItem(QStringLiteral(文字层)); layerDock-setWidget(list); layerDock-setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, layerDock);setAllowedAreas控制用户能把面板拖到哪些区域停靠。这里我只允许左右两侧是因为对“图层”这种纵向列表来说停靠在顶部或底部非常反直觉占空间又不好看。setAlloweAreas是面向用户的拖拽约束而addDockWidget的第一个参数是初始停靠位置。setFeatures是另一个值得关注的属性由这些标志组合DockWidgetClosable允许用户关闭面板DockWidgetMovable允许用户拖动它DockWidgetFloatable允许用户把它拖成独立浮动窗口默认三个全开用户体验最好。如果你把Closable去掉用户就没法通过面板右上角的X按钮关闭有些初学者想让“面板不能关”就这么做但要注意即使关了X用户还能在“视图”菜单里通过toggleViewAction切换显示没有真正的“锁死”一说。5.2 toggleViewAction把面板显隐统一交给视图菜单QDockWidget有一个很省心的接口toggleViewAction()它会返回一个QAction这个动作的勾选状态和面板实际显示状态自动同步。你只需要把它挂到“视图”菜单QAction *toggleLayerAction layerDock-toggleViewAction(); toggleLayerAction-setText(QStringLiteral(图层)); viewMenu-addAction(toggleLayerAction);这个设计的好处是你不用自己维护面板显隐状态用户在菜单里勾选、或在面板上点X关闭、或拖动后浮动所有状态的改变都会反映到同一个QAction上。工具栏也可以加toggleViewAction效果一样比如在你想让某个工具栏可显隐时。多面板应用建议统一收集所有dock和toolbar的toggleViewAction全部放到视图菜单里。这是桌面软件极为通用的约定用户已经习惯了。5.3 浮动状态的细节和经典翻车点先注意splitDockWidget的使用。如果你把两个Dock都addDockWidget到同一个区域默认行为是它们交错堆叠而不是并排分割。实际画面经常是你添加了“图层”和“素材库”两个面板都放在右边结果它们相互挤压每个都只显示一截。想要它们上下分割用addDockWidget(Qt::RightDockWidgetArea, layerDock); addDockWidget(Qt::RightDockWidgetArea, materialDock); splitDockWidget(layerDock, materialDock, Qt::Vertical);splitDockWidget把第二个参数指定的部件放到第一个参数的指定方向这样两个面板就能并排分割而不是堆叠视觉整齐很多。另一个坑是Dock内嵌控件的尺寸策略。我实际跑过一个DemoDock里放了一个带固定宽度的QListWidget设置setFixedWidth(260)面板初始看起来没问题。一旦用户把它拖成浮动窗口再停靠到另一边整体宽度被固定用户想拖边框调整窗口大小却发现面板变得特别别扭。后来我把固定宽度改成minimumWidth让Dock内部的Widget可以根据可用空间自由伸缩问题迎刃而解。原则是Dock内部尽量让布局和sizePolicy去管理尺寸不要硬编码固定宽高。6. 对话框模态/非模态、标准弹窗和自定义表单的生命周期对话框和前面几个组件不太一样它通常不是常驻在主窗口里的而是“按需弹出”。按交互方式可以分为模态和非模态按来源可以分为标准对话框和自定义对话框。先把标准对话框用熟再自己写自定义弹窗能覆盖绝大多数场景。6.1 消息框和文件选择的正确姿势QMessageBox是最常用的标准对话框auto ret QMessageBox::question(this, QStringLiteral(确认), QStringLiteral(确定要删除这个图层吗), QMessageBox::Yes | QMessageBox::No); if (ret QMessageBox::Yes) { // 执行删除 }注意返回值判断用的是QMessageBox::Yes不是布尔值。判断错误是新手常犯的问题写if(ret)这种代码不会报错但逻辑很可能不对。文件对话框同样简单QString filePath QFileDialog::getOpenFileName(this, QStringLiteral(选择图片), QStringLiteral(), QStringLiteral(图片文件 (*.png *.jpg *.bmp))); if (filePath.isEmpty()) return; // 用户点了取消QFileDialog的过滤条件里显示名和通配符用空格连接格式是“说明 (*.后缀)”多个后缀用分号分隔。这个字符串写不对会导致文件过滤选项空白。另外文件对话框返回的QString一旦为空就代表用户取消了选择千万别在空路径上继续操作。6.2 自定义对话框表单布局与返回值获取自定义对话框本质是一个QDialog子类在构造函数里用布局和控件搭出界面通过accept/reject决定返回值。我给图片处理Demo写的一个模糊参数对话框结构如下class FilterDialog : public QDialog { Q_OBJECT public: explicit FilterDialog(QWidget *parent nullptr); double radius() const; private: QDoubleSpinBox *radiusSpin; QDialogButtonBox *buttonBox; }; FilterDialog::FilterDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(QStringLiteral(模糊参数)); auto *form new QFormLayout(this); radiusSpin new QDoubleSpinBox(this); radiusSpin-setRange(0.1, 100.0); radiusSpin-setValue(3.0); form-addRow(QStringLiteral(半径), radiusSpin); buttonBox new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, QDialogButtonBox::accepted, this, QDialog::accept); connect(buttonBox, QDialogButtonBox::rejected, this, QDialog::reject); form-addRow(buttonBox); }调用侧FilterDialog dlg(this); if (dlg.exec() QDialog::Accepted) { double r dlg.radius(); applyBlur(r); }这个模式很常用也很干净。exec()会阻塞直到对话框关闭然后根据返回值判断用户是确认还是取消。因为dlg是栈对象函数结束自动析构不存在内存泄漏问题。6.3 栈对象还是堆对象对话框生命周期的取舍多数情况直接用栈对象构造对话框就能搞定但有些场景必须用堆对象。比如你想在主窗口里多次打开同一个对话框而不是每次重新构造或者对话框里有异步操作你希望关闭后再做别的处理。这时常见的错误是auto *dlg new FilterDialog(this); dlg-show(); // 错误示范既不模态又没设置自动销毁这样每次打开都会new一个对象对话框关闭后对象没被delete内存越用越多。正确的堆对象用法是配合deleteOnClose和finished信号auto *dlg new FilterDialog(this); dlg-setAttribute(Qt::WA_DeleteOnClose); connect(dlg, QDialog::finished, this, [this, dlg](int result) { if (result QDialog::Accepted) { double r dlg-radius(); applyBlur(r); } }); dlg-show();带WA_DeleteOnClose后对话框关闭时自动delete自己信号槽里访问dlg指针仍是安全的因为finished信号在销毁前发出。这样既避免泄漏又能做非模态弹窗。还要强调模态不等于冻结。exec()启动的是本地事件循环期间主窗口仍然可以重绘仍能响应部分事件但用户无法操作同级其它顶层窗口。如果槽函数里执行耗时操作那个操作的代码是“卡在原地等待对话框返回”不是界面卡死但如果你在对话框里触发了另一个模态对话框就会形成嵌套对话框栈。嵌套本身能正常工作但一旦管理不好会出现用户莫名其妙点不动主窗口的局面所以尽量保持对话框层级简单。7. 组装实例菜单、工具栏、浮动窗口和对话框如何协作前面把这些组件单独拆开了现在把它们放进一个完整的QMainWindow骨架里。我以一个简单的“图片浏览 图层面板”模拟项目X为例展示构造函数里的一次性装配过程。7.1 构造函数里的一次性装配MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle(QStringLiteral(窗口组件演示)); // 1. 先定义动作 QAction *openAction new QAction(QStringLiteral(打开(O)), this); openAction-setShortcut(QKeySequence::Open); openAction-setStatusTip(QStringLiteral(打开图像文件)); connect(openAction, QAction::triggered, this, MainWindow::openImage); QAction *exitAction new QAction(QStringLiteral(退出(X)), this); exitAction-setShortcut(QKeySequence::Quit); connect(exitAction, QAction::triggered, this, MainWindow::close); // 2. 菜单栏 QMenu *fileMenu menuBar()-addMenu(QStringLiteral(文件(F))); fileMenu-addAction(openAction); fileMenu-addSeparator(); fileMenu-addAction(exitAction); QMenu *viewMenu menuBar()-addMenu(QStringLiteral(视图(V))); // 3. 工具栏 QToolBar *toolBar addToolBar(QStringLiteral(文件工具)); toolBar-setObjectName(QStringLiteral(fileToolBar)); toolBar-setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolBar-addAction(openAction); viewMenu-addAction(toolBar-toggleViewAction()); // 4. 状态栏 statusBar()-showMessage(QStringLiteral(就绪)); // 5. 中央部件 auto *centralLabel new QLabel(QStringLiteral(图片显示区), this); centralLabel-setAlignment(Qt::AlignCenter); centralLabel-setMinimumSize(400, 300); setCentralWidget(centralLabel); // 6. 浮动窗口 auto *layerDock new QDockWidget(QStringLiteral(图层), this); layerDock-setObjectName(QStringLiteral(layerDock)); auto *layerList new QListWidget(layerDock); layerDock-setWidget(layerList); layerDock-setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, layerDock); viewMenu-addAction(layerDock-toggleViewAction()); }这个骨架虽然简单但把前面讲到的所有组件都串在了一起。菜单和工具栏共用同一个openAction状态栏由statusTip自动驱动提示视图菜单统一控制工具栏和图层面板的显隐。7.2 信号连接上的两个高频翻车点第一个是同一个QAction被connect了两次。有些程序员先建菜单时connect一次后面建工具栏时又把触发信号connect一次结果点一下按钮执行两次打开逻辑。QAction被addAction到多个容器只负责展示信号连接只做一次。建议把QAction定义为成员变量在构造函数一开始统一connect。第二个是状态栏提示重复管理。既然QAction已设置了statusTip你又在自己写的触发槽函数里调用statusBar()-showMessage两者不是同一个时机会冲突而是意义不同悬停提示告诉用户“这个按钮干什么”点击后的提示告诉用户“操作结果成功或失败”两者都该有但别写成互相覆盖的逻辑。我自己的习惯是初始化“就绪”、点击反馈、悬停提示三者分别用不同的机制去实现不在槽函数里无条件覆盖状态栏长期内容。7.3 我实际跑这个骨架时调整了什么跑这个模拟项目时两个细节让我印象很深。第一次运行我发现把“图层”面板拖到左侧后想拖回右侧却拖不回去后来一查是创建时写了setAllowedAreas(Qt::RightDockWidgetArea)只允许右侧。改成Left和Right两个区域问题消失。这提醒我写约束是为了防止用户做不合理拖拽不是把所有可停靠区域全部砍掉。另一个是添加了第二个Dock面板后两个面板全堆在右侧各自占一块区域显示非常局促。用splitDockWidget竖直分割后好了很多但如果你想让面板以标签页方式切换还需要进一步调整才行。多面板程序建议一开始就设计好分区策略是左右并列、上下分割还是标签合并而不是等用户拖乱了再补救。如果让我重新搭一遍这个骨架我会直接建立一个“动作中心”的初始化函数把open、save、exit、togglePanel这类QAction全部集中创建和connect再让菜单、工具栏、快捷键、状态栏提示都指向这个中心。Qt这套窗口组件的精髓就在这里动作驱动界面而不是界面各管各的。搞懂这一点后面不管是做编辑器、图像工具还是企业级管理系统窗口骨架这块就再也不会手忙脚乱了。