29、Qt主窗口:QMainWindow、菜单栏、工具栏、状态栏、QDockWidget

说实话,Qt 里最常用的窗口类就是 QMainWindow。你想想看,我们平时用的那些桌面软件——记事本、浏览器、IDE——它们的窗口结构其实都差不多:上面有菜单,下面有状态栏,四周可能还有可拖拽的面板。Qt 把这些东西都封装好了,你直接用就行。

我个人习惯,每次新建一个 Qt 项目,第一件事就是把 QMainWindow 的骨架搭起来。因为后面加功能、加界面,都是在骨架上添砖加瓦。今天我们就来拆解这个骨架。

29.1 QMainWindow 的整体结构

QMainWindow 本身就是一个布局容器。它把窗口分成了几个固定的区域:

  • 菜单栏(Menu Bar)—— 在窗口最顶部
  • 工具栏(Tool Bar)—— 默认在菜单栏下方
  • 中央部件(Central Widget)—— 窗口的核心区域
  • 停靠窗口(Dock Widget)—— 可浮动、可停靠在四周
  • 状态栏(Status Bar)—— 在窗口最底部

这个结构是固定的,你不能把菜单栏放到下面去,也不能把状态栏放到上面。但你可以控制哪些区域显示、哪些隐藏。

核心要点:QMainWindow 有自己的布局管理器,你不需要手动设置 layout。你只需要往对应的区域里塞东西就行。

QMainWindow 布局结构 菜单栏(Menu Bar) 工具栏(Tool Bar) 停靠窗口 (Dock) 中央部件(Central Widget) 停靠窗口 (Dock) 状态栏(Status Bar)

29.2 菜单栏:QMenuBar 与 QMenu

菜单栏是用户和程序交互的第一道门。我见过很多新手直接把所有功能塞到菜单里,结果菜单长得像购物清单。其实菜单的设计讲究「分层」——大类用 QMenu,具体操作用 QAction

来看一段最基础的代码:

// 创建菜单栏
QMenuBar *menuBar = this->menuBar();

// 创建菜单
QMenu *fileMenu = menuBar->addMenu("文件(&F)");
QMenu *editMenu = menuBar->addMenu("编辑(&E)");

// 创建动作
QAction *openAction = new QAction("打开(&O)", this);
QAction *saveAction = new QAction("保存(&S)", this);
QAction *exitAction = new QAction("退出(&X)", this);

// 添加动作到菜单
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addSeparator();  // 分隔线
fileMenu->addAction(exitAction);

// 连接信号槽
connect(openAction, &QAction::triggered, this, &MainWindow::onOpenFile);
connect(exitAction, &QAction::triggered, this, &QWidget::close);

小技巧:菜单文本里的 &F 表示快捷键 Alt+F。这在 Windows 上很常见,用户按 Alt 键就能看到下划线提示。我建议每个顶级菜单都加上这个,用户体验会好很多。

29.3 工具栏:QToolBar

工具栏就是把常用的操作放到显眼的位置。说白了,菜单是「仓库」,工具栏是「前台」。用户不需要翻菜单,点一下工具栏按钮就完事了。

创建工具栏也很简单:

// 创建工具栏
QToolBar *toolBar = this->addToolBar("文件操作");

// 设置工具栏属性
toolBar->setMovable(false);      // 禁止拖动
toolBar->setIconSize(QSize(24, 24));  // 图标大小

// 添加动作(复用菜单里的 QAction)
toolBar->addAction(openAction);
toolBar->addAction(saveAction);
toolBar->addSeparator();

// 添加自定义按钮
QAction *printAction = toolBar->addAction("打印");
printAction->setIcon(QIcon(":/icons/print.png"));

嗯,这里要注意:工具栏的动作和菜单的动作可以是同一个 QAction 对象。这样你只需要连接一次信号槽,菜单和工具栏都能响应。我在项目中经常这么干,省事又不会漏掉逻辑。

我曾经踩过的坑:工具栏的图标一定要用 PNG 或 SVG,不要用 JPG。JPG 有锯齿,在高 DPI 屏幕上特别难看。另外,图标尺寸建议统一,混用不同大小的图标会显得很业余。

29.4 状态栏:QStatusBar

状态栏用来显示一些临时信息,比如「正在保存...」、「已加载 100 条记录」。它不显眼,但很重要。用户操作完一个动作,总想看一眼有没有成功。

// 获取状态栏
QStatusBar *statusBar = this->statusBar();

// 显示临时消息(5秒后自动消失)
statusBar->showMessage("文件已保存", 5000);

// 显示永久消息
QLabel *statusLabel = new QLabel("就绪");
statusBar->addPermanentWidget(statusLabel);

showMessage 显示的是临时信息,过几秒就没了。addPermanentWidget 添加的是永久控件,会一直显示在状态栏右侧。我个人习惯把「行号」、「编码格式」、「连接状态」这类信息放在永久区域。

29.5 停靠窗口:QDockWidget

这个功能我特别喜欢。你可以把一些辅助面板做成可拖拽的浮动窗口,用户想放哪就放哪。比如 Qt Creator 的「项目文件树」、「输出窗口」都是 QDockWidget

// 创建停靠窗口
QDockWidget *dock = new QDockWidget("文件浏览器", this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

// 创建停靠窗口的内容
QTreeWidget *tree = new QTreeWidget;
tree->setHeaderLabel("文件列表");
dock->setWidget(tree);

// 添加到主窗口
this->addDockWidget(Qt::LeftDockWidgetArea, dock);

这里有几个关键点:

  • setAllowedAreas 限制停靠区域,防止用户把面板拖到奇怪的位置
  • setWidget 设置内容,可以是任何 QWidget
  • addDockWidget 指定初始停靠位置

避坑指南:我曾经做过一个项目,用户把停靠窗口拖出来之后关掉了,结果再也找不回来。后来我加了一个「视图」菜单,里面列出所有停靠窗口,点击可以重新显示。代码很简单:

QAction *toggleAction = dock->toggleViewAction();
viewMenu->addAction(toggleAction);

这样用户随时可以通过菜单恢复被关闭的面板。

29.6 综合示例:搭建一个完整的编辑器骨架

把上面这些知识点串起来,就是一个简单的文本编辑器骨架:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 1. 中央部件
    QTextEdit *editor = new QTextEdit;
    setCentralWidget(editor);

    // 2. 菜单栏
    QMenu *fileMenu = menuBar()->addMenu("文件(&F)");
    QAction *openAction = fileMenu->addAction("打开(&O)");
    QAction *saveAction = fileMenu->addAction("保存(&S)");
    fileMenu->addSeparator();
    QAction *exitAction = fileMenu->addAction("退出(&X)");

    // 3. 工具栏
    QToolBar *toolBar = addToolBar("文件");
    toolBar->addAction(openAction);
    toolBar->addAction(saveAction);

    // 4. 状态栏
    statusBar()->showMessage("就绪");

    // 5. 停靠窗口
    QDockWidget *dock = new QDockWidget("文件列表", this);
    QListWidget *list = new QListWidget;
    dock->setWidget(list);
    addDockWidget(Qt::LeftDockWidgetArea, dock);

    // 6. 视图菜单(恢复停靠窗口)
    QMenu *viewMenu = menuBar()->addMenu("视图(&V)");
    viewMenu->addAction(dock->toggleViewAction());
}

这段代码跑起来,你就能看到一个有模有样的桌面程序了。虽然它还不能真正打开文件,但骨架已经完整了。后面你要做的,就是在骨架里填充业务逻辑。

组件 类名 主要用途
菜单栏 QMenuBar / QMenu 组织功能分类,支持快捷键
工具栏 QToolBar 快速访问常用操作
状态栏 QStatusBar 显示状态信息和提示
停靠窗口 QDockWidget 可拖拽的辅助面板
中央部件 QWidget(任意子类) 程序的核心工作区

好了,这就是 QMainWindow 的五个核心组件。你想想看,其实大部分桌面软件都是这个套路。掌握了这个骨架,你就能快速搭建出专业的界面。剩下的,就是往里面填你的业务逻辑了。


公众号:蓝海资料掘金营,微信deep3321