Qt 三大窗口基类——QMainWindow、QWidget、QDialog 全解析
写给所有刚入门 Qt 的 C++ 同学
一、一个让初学者抓狂的问题
我刚学 Qt 的时候,复制了一段代码,信心满满按下运行:
#include <QApplication>
#include <QWidget>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QLabel label("Hello, Qt!", &w);
w.show();
return a.exec();
}
窗口出来了,文字也显示出来了。
但是!我想加个菜单栏「文件 → 打开」,加了一行 w.menuBar()——没有这个方法。
上网搜,有人说「你得用 QMainWindow」,换了之后菜单栏就有了。
为什么?
因为 Qt 窗口家族里有三个最核心的"祖宗类":QWidget、QMainWindow、QDialog。它们三个长得像、用法像,但职责完全不同。
选错父类,要么缺功能,要么多了一堆你用不上的东西。
今天我们就一次性把它们讲清楚。
二、类继承关系总览
先上一张全家福,把"谁继承谁"看清楚:

Qt 窗口类的根源是 QObject(提供对象树、信号槽机制),往上走到 QWidget 就具备了「能在屏幕上画、能接受用户操作」的能力。再往上的 QMainWindow 和 QDialog,则是在 QWidget 的基础上针对不同使用场景做的特化。
用一句话概括:
QWidget是最基础的窗口单元QMainWindow是完整的应用主窗口QDialog是用户交互的临时窗口
三、QWidget:所有窗口的祖宗
3.1 它是什么
QWidget 是 Qt 所有可视组件的基类。屏幕上能看到、能点的东西,几乎都直接或间接继承自它。
它提供的能力包括:
- 绘制自身(
paintEvent) - 接收鼠标 / 键盘事件
- 窗口的显示 / 隐藏 / 移动 / 缩放
- 作为容器容纳子控件(对象树)
但是!它是个"裸"的窗口:没有菜单栏、没有工具栏、没有状态栏。如果你用 QWidget 做一个"主窗口",会显得很寒酸。
3.2 什么时候用
- 作为最外层窗口(自定义主窗口框架)
- 作为容器承载子控件
- 作为自定义控件的基类(继承它去画自己的东西)
3.3 常用子类
| 子类 | 用途 |
|---|---|
QFrame | 带边框的容器(QLabel 的爹) |
QLabel | 显示文字 / 图片 |
QPushButton | 按钮 |
QLineEdit | 单行输入框 |
QTextEdit / QPlainTextEdit | 多行文本框 |
QComboBox | 下拉框 |
QCheckBox / QRadioButton | 复选框 / 单选框 |
QSlider / QSpinBox | 滑动条 / 数字框 |
QProgressBar | 进度条 |
QListWidget / QTreeWidget / QTableWidget | 列表 / 树 / 表格 |
QTabWidget | 选项卡 |
QStackedWidget | 堆栈容器(一次只显示一个) |
QScrollArea | 滚动区域 |
几乎是「Qt 组件全家桶」,所有这些类都直接继承自
QWidget。
3.4 一个示例
QWidget w;
QVBoxLayout* layout = new QVBoxLayout(&w);
layout->addWidget(new QLabel("用户名:"));
layout->addWidget(new QLineEdit);
layout->addWidget(new QPushButton("登录"));
w.show();
这就是最朴素的"裸窗口"——什么装饰都没有。
四、QMainWindow:主窗口专用
4.1 它是什么
QMainWindow 是 Qt 为「应用程序主窗口」量身定做的窗口类。
和 QWidget 最大的区别是:它自带 5 大组件框架,开箱即用。
4.2 五大组件解剖

这 5 个位置是预留给特定组件的,由 QMainWindow 自己管理:
| 组件 | API | 说明 |
|---|---|---|
| 菜单栏 Menu Bar | menuBar() | 顶部横条,"文件 / 编辑 / 帮助"那种 |
| 工具栏 Tool Bar | addToolBar() | 菜单栏下方,可拖拽的快捷按钮区 |
| 中心部件 Central Widget | setCentralWidget() | 中间最大的区域,只能有一个 |
| 停靠窗口 Dock Widget | addDockWidget() | 侧边可拖拽的面板(IDE 那种) |
| 状态栏 Status Bar | statusBar() | 底部显示临时信息 |
这 5 个位置只能由
QMainWindow提供,普通QWidget是没有的。
4.3 什么时候用
- 写一个完整的桌面应用(记事本、IDE、音乐播放器……)
- 需要菜单栏、工具栏、状态栏、侧边面板的复杂界面
4.4 一个示例
QMainWindow w;
// 1. 菜单栏
QMenu* fileMenu = w.menuBar()->addMenu("文件");
fileMenu->addAction("打开");
fileMenu->addAction("保存");
fileMenu->addSeparator();
fileMenu->addAction("退出");
// 2. 工具栏
QToolBar* toolBar = w.addToolBar("主工具栏");
toolBar->addAction("新建");
toolBar->addAction("保存");
// 3. 中心部件(只能有一个)
QTextEdit* textEdit = new QTextEdit;
w.setCentralWidget(textEdit);
// 4. 状态栏
w.statusBar()->showMessage("就绪");
w.show();
跑起来就是一个有模有样的「记事本」骨架。
4.5 关于 QMainWindow 的子类
QMainWindow 本身已经非常完整,所以几乎没有人去继承它。99% 的情况都是直接使用它,再通过 setCentralWidget 把你的中心部件塞进去。
但如果你想做一个"有自己特色的主窗口框架"(比如 VS Code 那种极简风),继承它自定义也是可以的。
五、QDialog:对话框专用
5.1 它是什么
QDialog 是为「短时间交互」设计的窗口类。最常见的场景是弹一个窗口问用户几个问题:
- 打开文件 → 弹一个选文件的窗口
- 提示错误 → 弹一个"确定 / 取消"
- 修改设置 → 弹一个设置面板
和 QMainWindow 比,它没有菜单栏 / 工具栏 / 状态栏 / 停靠窗口。
和 QWidget 比,它多了一些对话框特有的能力。
5.2 模态 vs 非模态
QDialog 最重要的概念是模态(Modal):
// 模态对话框:弹出后阻塞父窗口,必须先关掉它才能动父窗口
QDialog dlg(this);
dlg.exec(); // 阻塞在这里,关闭后才返回
// 非模态对话框:弹出后父子窗口都能动
QDialog* dlg = new QDialog(this);
dlg->show(); // 不阻塞
exec():模态,会阻塞事件循环,关闭后返回值是用户点了哪个按钮show():非模态,不阻塞,行为类似普通窗口
重要区别:模态对话框关闭后
exec()会返回QDialog::Accepted或QDialog::Rejected,可以用来判断用户做了什么选择。
5.3 常用子类
Qt 帮你实现了一大堆"开箱即用"的对话框:
| 子类 | 用途 |
|---|---|
QMessageBox | 信息提示 / 警告 / 错误 / 确认 |
QFileDialog | 打开 / 保存文件 |
QColorDialog | 选颜色 |
QFontDialog | 选字体 |
QInputDialog | 输入单行文本 / 数字 |
QProgressDialog | 带进度条的对话框 |
QWizard | 向导式对话框(多步引导) |
QErrorMessage | 错误信息框 |
QFindDialog / QReplaceDialog | 查找 / 替换(其实用 QDialog 自己写) |
5.4 一个示例
QPushButton* btn = new QPushButton("点我", this);
connect(btn, &QPushButton::clicked, [=]() {
QMessageBox::information(this, "提示", "你点了一下");
});
一行 QMessageBox::information 就完成了"弹个提示框"的工作,完全不用自己 new QDialog。
5.5 自定义对话框
如果 Qt 自带的对话框都不够用,可以自己继承 QDialog:
class MyDialog : public QDialog {
Q_OBJECT
public:
MyDialog(QWidget* parent = nullptr) : QDialog(parent) {
setWindowTitle("我的对话框");
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(new QLabel("请输入姓名:"));
layout->addWidget(new QLineEdit);
QDialogButtonBox* btnBox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(btnBox);
connect(btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
};
注意:自定义对话框通常继承 QDialog,因为这样天然支持 exec() 模态和 Accepted/Rejected 返回值。
六、实战对比与选型指南
6.1 三大类对比
| 维度 | QWidget | QMainWindow | QDialog |
|---|---|---|---|
| 菜单栏 | ❌ | ✅ | ❌ |
| 工具栏 | ❌ | ✅ | ❌ |
| 状态栏 | ❌ | ✅ | ❌ |
| 停靠窗口 | ❌ | ✅ | ❌ |
| 模态阻塞 | ❌ | ❌ | ✅(exec) |
| 返回值 | — | — | Accepted / Rejected |
| 适合场景 | 自定义控件 / 容器 | 应用主窗口 | 临时交互 |
6.2 选型决策树

快速判断:
- 要做完整应用(菜单/工具栏/状态栏)→
QMainWindow - 要弹个窗口问问题 →
QDialog - 其他所有普通控件 / 容器 →
QWidget
七、总结:一张图记住三大类

口诀:
QMainWindow= 主窗口的"毛坯房",5 大组件框架给你留好QWidget= 万能基类,所有控件都从它来QDialog= 弹窗专用,自带exec()模态机制
记住这三句话,以后写 Qt 看到父类就知道为什么这么选了。
更多推荐



所有评论(0)