14. QT主窗口:QMainWindow、菜单栏、工具栏、状态栏、QDockWidget、中心部件

好,咱们今天聊聊QT里最常用的主窗口框架——QMainWindow。说实话,这东西我用了快十年,每次新项目都离不开它。你想想看,一个桌面应用,总得有个像样的壳子吧?菜单、工具栏、状态栏、侧边面板、中间内容区……这些就是QMainWindow帮我们搭好的骨架。

14.1 QMainWindow 的整体结构

QMainWindow不是普通的QWidget,它自带一套布局系统。我刚开始学QT时,直接在QMainWindow上放了个按钮,结果按钮跑到了奇怪的位置……后来才明白,它把窗口分成了几个固定区域。

核心区域就这几个:

  • 菜单栏(Menu Bar)—— 顶部,放各种菜单项
  • 工具栏(Tool Bar)—— 可拖拽,放常用操作按钮
  • 中心部件(Central Widget)—— 主内容区,占最大面积
  • 停靠窗口(QDockWidget)—— 可浮动、可停靠的侧边面板
  • 状态栏(Status Bar)—— 底部,显示提示信息

我画了张图,帮你理清它们的位置关系:

菜单栏 (Menu Bar) 工具栏 (Tool Bar) QDockWidget (左侧停靠) 中心部件 (Central Widget) 主内容区 QDockWidget (右侧停靠) 状态栏 (Status Bar)

核心要点:QMainWindow 自己管理布局,你只需要往对应区域塞部件就行。千万别手动设置它的布局管理器,否则会乱套。

14.2 菜单栏——应用的「门面」

菜单栏是用户最先看到的东西。我个人习惯把最常用的功能放在菜单里,比如「文件」、「编辑」、「帮助」这些。

创建菜单栏很简单:

// 获取菜单栏(QMainWindow 自带,不用 new)
QMenuBar *menuBar = this->menuBar();

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

// 添加菜单项
QAction *openAction = fileMenu->addAction("打开(&O)");
QAction *saveAction = fileMenu->addAction("保存(&S)");

// 添加分隔线
fileMenu->addSeparator();

QAction *exitAction = fileMenu->addAction("退出(&X)");

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

这里有个小细节:&F 表示快捷键 Alt+F 可以展开这个菜单。我在项目中遇到过用户抱怨「怎么没有快捷键」,其实就是忘了加这个。

我的习惯:每个菜单项都配上图标,视觉上更友好。用 openAction->setIcon(QIcon(":/icons/open.png")); 就行。

14.3 工具栏——高频操作的快捷入口

工具栏说白了就是菜单项的「快捷版」。用户不用点开菜单,直接点工具栏按钮就行。

// 创建工具栏
QToolBar *toolBar = this->addToolBar("主工具栏");

// 设置可移动、可浮动
toolBar->setMovable(true);
toolBar->setFloatable(true);

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

// 添加分隔
toolBar->addSeparator();

// 添加自定义按钮
QAction *printAction = toolBar->addAction(QIcon(":/icons/print.png"), "打印");
connect(printAction, &QAction::triggered, this, &MainWindow::onPrint);

嗯,这里要注意:工具栏默认是可以右键隐藏的。如果你不想让用户关掉某个关键工具栏,可以设置 toolBar->setContextMenuPolicy(Qt::PreventContextMenu);。不过我个人建议别这么做,尊重用户习惯。

14.4 状态栏——给用户的「悄悄话」

状态栏在窗口最底部,用来显示一些临时信息。比如「正在保存...」、「加载完成」之类的。

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

// 显示临时消息(5秒后自动消失)
statusBar->showMessage("准备就绪", 5000);

// 显示永久消息
QLabel *statusLabel = new QLabel("当前用户: 张三");
statusBar->addPermanentWidget(statusLabel);

// 左侧添加控件
QProgressBar *progressBar = new QProgressBar();
progressBar->setRange(0, 100);
progressBar->setValue(50);
statusBar->addWidget(progressBar);

我曾经在做一个数据导入工具时,忘了在耗时操作前后更新状态栏。结果用户以为程序卡死了,直接强关……后来我养成了习惯:任何超过2秒的操作,都在状态栏显示进度。

避坑指南:状态栏的 showMessage() 是临时消息,会被后续操作覆盖。如果你需要一直显示某条信息,用 addPermanentWidget()addWidget() 添加一个永久的 QLabel。

14.5 QDockWidget——灵活的侧边面板

QDockWidget 是我最喜欢的部件之一。它可以停靠在主窗口的四个边,也可以拖出来变成独立窗口。很多 IDE 的侧边栏就是这么做的。

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

// 往里面放内容
QTreeWidget *treeWidget = new QTreeWidget();
treeWidget->setHeaderLabel("文件列表");
dockWidget->setWidget(treeWidget);

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

// 设置初始可见
dockWidget->setVisible(true);

你想想看,如果用户想把面板拖到右边,或者干脆浮在外面,QDockWidget 天然支持。我做过一个图像处理软件,把「图层」、「颜色」、「历史记录」都做成了停靠窗口,用户可以自由排列,体验非常好。

关键属性:

  • setAllowedAreas() —— 限制可停靠的区域
  • setFeatures() —— 控制是否可关闭、可浮动、可移动
  • setFloating() —— 初始是否浮动

14.6 中心部件——应用的核心

中心部件是主窗口里面积最大的区域,通常放应用的核心功能。比如文本编辑器放 QTextEdit,画图软件放 QGraphicsView

// 设置中心部件
QTextEdit *textEdit = new QTextEdit(this);
this->setCentralWidget(textEdit);

// 或者用 QSplitter 分割多个部件
QSplitter *splitter = new QSplitter(Qt::Horizontal, this);
QTextEdit *leftEditor = new QTextEdit();
QTextEdit *rightEditor = new QTextEdit();
splitter->addWidget(leftEditor);
splitter->addWidget(rightEditor);
this->setCentralWidget(splitter);

中心部件只能有一个。如果你需要多个区域,就用 QSplitterQTabWidget 把它们包起来,再设为中心部件。我早期犯过一个错:直接调了两次 setCentralWidget(),结果前一个部件被替换掉了,找了好久才发现。

14.7 完整示例:组装一个主窗口

咱们把上面这些拼起来,看看一个完整的主窗口长什么样:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 1. 菜单栏
    QMenuBar *mb = menuBar();
    QMenu *fileMenu = mb->addMenu("文件(&F)");
    QAction *openAct = fileMenu->addAction("打开");
    QAction *exitAct = fileMenu->addAction("退出");

    // 2. 工具栏
    QToolBar *tb = addToolBar("主工具栏");
    tb->addAction(openAct);

    // 3. 中心部件
    QTextEdit *editor = new QTextEdit(this);
    setCentralWidget(editor);

    // 4. 停靠窗口
    QDockWidget *dock = new QDockWidget("导航", this);
    QListWidget *list = new QListWidget();
    dock->setWidget(list);
    addDockWidget(Qt::LeftDockWidgetArea, dock);

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

    // 6. 连接信号
    connect(openAct, &QAction::triggered, [this]() {
        statusBar()->showMessage("打开文件...");
    });
    connect(exitAct, &QAction::triggered, this, &QWidget::close);
}

这段代码跑起来,你就能看到一个带菜单、工具栏、侧边栏、编辑区和状态栏的完整窗口了。说白了,QT 把最常用的桌面应用骨架都封装好了,你只需要往里面填内容。

调试小技巧:如果你发现某个工具栏或停靠窗口不见了,先检查 setVisible(true) 有没有调用。我经常在重构代码时漏掉这个,然后一脸懵。

14.8 布局与尺寸策略

主窗口的各个区域有默认的尺寸策略。比如工具栏会根据内容自动调整高度,状态栏固定底部。但有时候你需要微调:

区域 默认行为 常用调整
菜单栏 固定高度,自动宽度 一般不改
工具栏 可拖拽,可折叠 setIconSize() 控制图标大小
停靠窗口 可浮动,可关闭 setMinimumWidth() 防止拖太小
中心部件 占据剩余空间 setSizePolicy() 控制伸缩
状态栏 固定底部,自动高度 setSizeGripEnabled() 控制右下角拖拽柄

我记得有一次,用户反馈说侧边栏拖得太窄,内容都看不全。后来我加了 dockWidget->setMinimumWidth(200);,问题就解决了。这些小细节,往往决定了用户体验的好坏。

好了,关于 QMainWindow 的核心内容就这些。你动手写一个试试,把菜单、工具栏、停靠窗口都搭起来,很快就能上手。


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