写给所有刚入门 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 窗口家族里有三个最核心的"祖宗类":QWidgetQMainWindowQDialog。它们三个长得像、用法像,但职责完全不同

选错父类,要么缺功能,要么多了一堆你用不上的东西。

今天我们就一次性把它们讲清楚。


二、类继承关系总览

先上一张全家福,把"谁继承谁"看清楚:

在这里插入图片描述

Qt 窗口类的根源是 QObject(提供对象树、信号槽机制),往上走到 QWidget 就具备了「能在屏幕上画、能接受用户操作」的能力。再往上的 QMainWindowQDialog,则是在 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 BarmenuBar()顶部横条,"文件 / 编辑 / 帮助"那种
工具栏 Tool BaraddToolBar()菜单栏下方,可拖拽的快捷按钮区
中心部件 Central WidgetsetCentralWidget()中间最大的区域,只能有一个
停靠窗口 Dock WidgetaddDockWidget()侧边可拖拽的面板(IDE 那种)
状态栏 Status BarstatusBar()底部显示临时信息

这 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::AcceptedQDialog::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 三大类对比

维度QWidgetQMainWindowQDialog
菜单栏
工具栏
状态栏
停靠窗口
模态阻塞✅(exec)
返回值Accepted / Rejected
适合场景自定义控件 / 容器应用主窗口临时交互

6.2 选型决策树

在这里插入图片描述

快速判断:

  • 要做完整应用(菜单/工具栏/状态栏)→ QMainWindow
  • 弹个窗口问问题QDialog
  • 其他所有普通控件 / 容器 → QWidget

七、总结:一张图记住三大类

在这里插入图片描述

口诀

  • QMainWindow = 主窗口的"毛坯房",5 大组件框架给你留好
  • QWidget = 万能基类,所有控件都从它来
  • QDialog = 弹窗专用,自带 exec() 模态机制

记住这三句话,以后写 Qt 看到父类就知道为什么这么选了。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐