20、Qt主窗口与菜单:QMainWindow、QMenuBar、QToolBar、QStatusBar、QDockWidget、QAction
说实话,Qt 里最常用的窗口框架就是 QMainWindow。你想想看,我们平时用的那些桌面软件——记事本、画图、甚至 Qt Creator 自己——它们的窗口结构其实都差不多:上面有菜单栏,下面有状态栏,中间是干活的地方,四周还能停靠各种小面板。这套布局,就是 QMainWindow 给我们搭好的骨架。
我个人习惯,每次新建一个 Qt 项目,第一件事就是把主窗口的骨架搭起来。别急着写业务逻辑,先把菜单、工具栏、状态栏这些基础设施摆好。为什么?因为后面加功能的时候,你只需要往骨架上挂肉就行了,代码结构会清晰很多。
20.1 QMainWindow 的整体布局
先看一张我画的布局图,帮你快速建立直观印象:
看到没?整个窗口被分成了五个区域。中间那个最大的紫色区域是中央部件,你主要的工作界面就放那里。四周的橙色区域是可选的停靠窗口,你可以拖来拖去。最上面是菜单栏和工具栏,最下面是状态栏。
20.2 QMenuBar 与 QAction
菜单栏是用户和程序交互的第一道门。我见过不少新手直接把菜单项的文字写死,结果后面要国际化的时候,改得想哭。其实 Qt 的菜单系统设计得很巧妙——它把「动作」和「显示」分开了。
这个「动作」就是 QAction。你可以把 QAction 理解成一个命令的载体:它包含了图标、文字、快捷键、提示信息,甚至还能关联一个槽函数。然后菜单栏和工具栏都可以共用同一个 QAction。
// 创建一个动作
QAction *openAction = new QAction(QIcon(":/icons/open.png"), "打开(&O)", this);
openAction->setShortcut(QKeySequence::Open);
openAction->setStatusTip("打开一个文件");
// 添加到菜单
QMenu *fileMenu = menuBar()->addMenu("文件(&F)");
fileMenu->addAction(openAction);
// 添加到工具栏
QToolBar *fileToolBar = addToolBar("文件");
fileToolBar->addAction(openAction);
// 连接信号槽
connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
这段代码里,openAction 只创建了一次,但既出现在菜单里,又出现在工具栏里。你想想看,如果以后要改快捷键,只需要改一处。这就是 QAction 的价值所在。
20.3 QToolBar 工具栏
工具栏说白了就是菜单的快捷方式集合。用户懒得点菜单,直接点工具栏上的图标按钮。但工具栏有个坑——它默认是可以拖动的。用户可以把工具栏拖到窗口的左边、右边,甚至悬浮起来。
我个人建议,如果你的工具栏不希望被拖动,可以设置:
QToolBar *toolbar = addToolBar("编辑");
toolbar->setMovable(false); // 禁止拖动
toolbar->setFloatable(false); // 禁止悬浮
不过话说回来,大部分用户其实不介意工具栏能不能拖动。我一般只在工具栏包含关键操作时才会锁定它,比如保存按钮——万一用户不小心把工具栏拖没了,找不到保存按钮,那可就麻烦了。
20.4 QStatusBar 状态栏
状态栏是窗口底部那条窄窄的条,用来显示一些临时信息。比如「文件已保存」、「正在加载...」、「第 3 行,第 12 列」之类的。
Qt 的状态栏有两种用法:
- 临时消息:调用
showMessage("消息内容", 3000),3 秒后自动消失。 - 永久部件:用
addWidget()或addPermanentWidget()添加一个 QLabel 或其他控件,一直显示。
// 临时消息
statusBar()->showMessage("文件加载完成", 2000);
// 永久部件
QLabel *statusLabel = new QLabel("就绪");
statusBar()->addPermanentWidget(statusLabel);
注意 addPermanentWidget 和 addWidget 的区别:前者从右往左排,后者从左往右排。我一般把重要的状态信息(比如连接状态、用户信息)放在右边永久区域,把操作反馈放在左边临时区域。
20.5 QDockWidget 停靠窗口
停靠窗口是 Qt 里比较灵活的一个组件。你可以把它想象成一个可以自由移动的小面板。比如 Qt Creator 里的「项目文件列表」、「输出窗口」、「调试变量监视」——这些都是 QDockWidget。
创建停靠窗口的步骤很简单:
QDockWidget *dock = new QDockWidget("文件浏览器", this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QTreeWidget *tree = new QTreeWidget;
dock->setWidget(tree);
addDockWidget(Qt::LeftDockWidgetArea, dock);
这里有个细节:setAllowedAreas 限制了停靠窗口可以停靠的位置。如果你不设置,默认四个方向都能停。我曾经做过一个项目,用户把调试窗口拖到了菜单栏上面,结果界面乱成一团。从那以后,我习惯给每个停靠窗口都明确设置允许区域。
dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable),把关闭功能去掉。
20.6 综合示例:搭建一个简易文本编辑器
说了这么多,不如直接看一个完整的例子。下面是一个简易文本编辑器的主窗口代码,把上面讲到的组件都用上了:
#include <QMainWindow>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QDockWidget>
#include <QAction>
#include <QTextEdit>
#include <QListWidget>
class TextEditor : public QMainWindow {
Q_OBJECT
public:
TextEditor(QWidget *parent = nullptr) : QMainWindow(parent) {
// 1. 中央部件
QTextEdit *editor = new QTextEdit(this);
setCentralWidget(editor);
// 2. 菜单栏
QMenu *fileMenu = menuBar()->addMenu("文件(&F)");
QAction *newAct = new QAction("新建(&N)", this);
newAct->setShortcut(QKeySequence::New);
fileMenu->addAction(newAct);
QAction *openAct = new QAction("打开(&O)", this);
openAct->setShortcut(QKeySequence::Open);
fileMenu->addAction(openAct);
// 3. 工具栏
QToolBar *toolbar = addToolBar("文件操作");
toolbar->addAction(newAct);
toolbar->addAction(openAct);
// 4. 状态栏
statusBar()->showMessage("就绪");
// 5. 停靠窗口
QDockWidget *dock = new QDockWidget("文件列表", this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QListWidget *fileList = new QListWidget;
fileList->addItem("文档1.txt");
fileList->addItem("文档2.txt");
dock->setWidget(fileList);
addDockWidget(Qt::RightDockWidgetArea, dock);
}
};
这段代码虽然简单,但五脏俱全。你运行一下就能看到:上面有菜单栏和工具栏,中间是编辑区,右边有个文件列表面板,底部显示「就绪」。这就是一个标准的主窗口骨架。
createActions()、createMenus()、createToolBars()。这样构造函数就不会太臃肿,代码也更容易维护。
20.7 避坑指南
最后分享几个我踩过的坑:
- QMainWindow 不能嵌套:千万别在一个 QMainWindow 里再放一个 QMainWindow。如果你需要多文档界面,用 QMdiArea。
- 工具栏图标大小:不同平台的图标风格不一样。Windows 上 24x24 合适,macOS 上 32x32 更协调。可以用
setIconSize(QSize(24,24))统一设置。 - 状态栏更新频率:不要在循环里频繁调用
showMessage,否则界面会卡顿。如果需要实时更新,用 QTimer 控制频率。 - 停靠窗口的 restoreState:如果你保存了窗口布局,记得在程序启动时调用
restoreState()。我曾经忘记这步,结果用户每次打开程序,停靠窗口都回到默认位置,被吐槽了好久。
嗯,以上就是 QMainWindow 及其周边组件的核心内容。这些东西看起来零散,但组合起来就是一套完整的主窗口框架。你只要把骨架搭好了,后面加功能就像往抽屉里放东西一样简单。