Qt入门01 Qt简介 —— 大丙+Gemini
Qt入门01 Qt简介——大丙+Gemini
https://www.bilibili.com/video/BV1Jp4y167R9
1.7 创建第一个Qt项目
以下代码是Qt自动生成的,理解即可
1.7.1 项目文件(.pro)
在创建的Qt项目中自动生成了一个后缀为 .pro 的项目文件,该文件中记录着项目的一些属性信息,具体信息如下:
# 在项目文件中, 注释需要使用 井号(#)
# 项目编译的时候需要加载哪些底层模块(写在后面)
QT += core gui
# 如果当前Qt版本大于4, 会添加一个额外的模块: widgets
# Qt 5中对gui模块进行了拆分, 将 widgets 独立出来了
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# 使用c++11新特性
CONFIG += c++11
#如果在项目中调用了废弃的函数, 项目编译的时候会有警告的提示
DEFINES += QT_DEPRECATED_WARNINGS
# 项目中的源文件
SOURCES += \
main.cpp \
mainwindow.cpp
# 项目中的头文件
HEADERS += \
mainwindow.h
# 项目中的窗口界面文件
FORMS += \
mainwindow.ui
# 后面的内容用不上就不用管
1.7.2 main.cpp
在这个源文件中有程序的入口函数 main(),下面给大家介绍下这个文件中自动生成的几行代码:
#include "mainwindow.h" // 生成的窗口类头文件
#include <QApplication> // 应用程序类头文件
int main(int argc, char *argv[])
{
// 创建应用程序对象, 在一个Qt项目中实例对象有且仅有一个
// 类的作用: 检测触发的事件, 进行事件循环并处理
QApplication a(argc, argv);
// 创建窗口类对象
MainWindow w;
// 显示窗口
w.show();
// 应用程序对象开始事件循环, 保证应用程序不退出
return a.exec();
}
1.7.3 mainwindow.ui
在Qt中每一个窗口都对应一个可编辑的可视化界面(*.ui), 这个界面对应的是一个xml格式的文件, 一般情况下不需要在xml格式下对这个文件进行编辑, 关于这个文件结构了解即可。
<!-- 双击这个文件看到的是一个窗口界面, 如果使用文本编辑器打开看到的是一个XML格式的文件 -->
<!-- 看不懂这种格式没关系, 我们不需要在这种模式下操作这个文件。 -->
<!-- 这里只是给大家介绍这个文件的本质 -->
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<!-- 这个是Ui::MainWindow,不是外面那个 -->
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar"/>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
1.7.4 mainwindow.h
这个文件是窗口界面对应的类的头文件。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow> // Qt标准窗口类头文件
// mainwindow.ui 文件中也有一个类叫 MainWindow, 将这个类放到命名空间 Ui 中
// 这个和外面的MainWindow是没有任何关联的,需要通过创建指针关联
// 因为此时还没声明MainWindow,所以先放进去Ui命名空间,写在MainWindow声明后会报错!
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
// 自定义的MainWindow类继承了Qt的标准窗口类
class MainWindow : public QMainWindow
{
Q_OBJECT // 这个宏是为了能够使用Qt中的信号槽机制
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
// 定义指针指向Ui::MainWindow对象,进而关联两个类
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
1.7.5 mainwindow.cpp
这个文件是窗口界面对应的类的源文件(也就是自定义的,外面的MainWindow)。
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
// 基于mainwindow.ui创建一个实例对象(Ui::MainWindow的)
, ui(new Ui::MainWindow)
{
// ui是指向Ui::MainWindow的指针(mainwindow.ui的实例对象)
// this是当前类的对象(MainWindow)
// 将 mainwindow.ui 的实例对象和 当前类的对象进行关联
// 这样同名的连个类对象就产生了关联, 合二为一了
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
// 析构释放堆区的Ui::MainWindow
delete ui;
}
补充:Qt中的窗口类
在 Qt Creator 中创建新窗口类的路径:
-
文件 --> 新建文件或项目 --> Qt(文件和类)--> Qt设计师界面类 --> choose
-
下拉菜单中有三个选项, 分别为:
QMainWindow、QDialog、QWidget:
补充 1. 基础窗口类
常用的窗口类有3个:QWidget, QDialog, QMainWindow
在创建 Qt 窗口时,需要让自己的窗口类继承上述三个基类中的其中一个。以下是这三大基础窗口类的核心对比:
| 窗口类 | 核心定位 | 是否可以内嵌 | 典型应用场景 |
|---|---|---|---|
QWidget |
万能白纸。所有可视化控件的基类。 | 可以作为独立窗口,也可内嵌作为其他窗口的子控件。 | 自定义控件、不需要复杂菜单栏的简单独立窗口。 |
QDialog |
交互弹窗。用于短期的人机交互。 | 永远无法内嵌,只能是顶级窗口。 | 登录框、设置面板、确认/警告提示框。 |
QMainWindow |
重型主界面。自带标准桌面软件骨架。 | 必须是独立顶级窗口。 | 软件的主入口界面(如 Word、Photoshop、VS2019 主界面)。 |
1.8 QWidget类型窗口的特点与父子机制
1.8.0 前期准备:
新建了一个基于 QWidget 的自定义窗口类 TestWidget(包含 TestWidget.h, TestWidget.cpp, TestWidget.ui)。
在 Qt 中,窗口的显示行为和内存管理高度依赖于是否为其指定了父对象(Parent)。主要分为以下两种情况:
1.8.1 独立窗口(未指定父对象)
-
创建方式:
TestWidget* w = new TestWidget;或者TestWidget* w = new TestWidget(nullptr); -
显示特点:它是一个独立的顶级窗口,拥有自己的标题栏和边框。必须手动调用
w->show()才能显示在屏幕上。 -
⚠️ 内存注意:因为没有父对象,Qt 的对象树机制无法自动管理它的内存。如果仅在局部函数中
new出来,关闭窗口时会导致内存泄漏。- 解决方案:通常需要为其设置
Qt::WA_DeleteOnClose属性,让其在关闭时自动销毁释放内存。
- 解决方案:通常需要为其设置
1.8.2 内嵌/子窗口(指定了父对象)
- 创建方式:
TestWidget* w = new TestWidget(this); - 显示特点:它不再是独立窗口,而是作为一个部件(Widget)内嵌到父窗口(如 MainWindow)的客户区中。它没有独立的边框和标题栏。
- 生命周期:无需手动调用
show(),当父窗口显示时,它会被一并显示出来;当父窗口被销毁时,它也会被自动释放(依托于 Qt 的对象树机制),绝对安全。
1.8.3 示例代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "TestWidget.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 一般在 Qt 的构造函数中进行初始化操作(如实例化子窗口、绑定数据与信号槽等)
// 【场景演示】:通过宏定义切换窗口的不同显示形态
//#if 0
#if 1
/* ---------------------------------------------------------
* 模式一:创建独立窗口(无父对象)
* --------------------------------------------------------- */
// 对应构造函数:explicit TestWidget(QWidget *parent = nullptr);
TestWidget* tsw1 = new TestWidget; // 等价于 new TestWidget(nullptr)
tsw1->setAttribute(Qt::WA_DeleteOnClose); // 防止内存泄漏
// 独立窗口必须手动调用show()才能弹窗显示
tsw1->show();
#else
/* ---------------------------------------------------------
* 模式二:创建内嵌子窗口(指定当前 MainWindow 为父对象)
* --------------------------------------------------------- */
// 对应构造函数:explicit TestWidget(QWidget *parent = nullptr);
TestWidget* tsw2 = new TestWidget(this);
// 由于指定了this为父对象,它变成了内嵌控件(无边框)
// 此时不需要调用w->show(),他会随着MainWindow的显示而一起显示
// 且生命周期由MainWindow接管,父窗口销毁时它会自动析构
#endif
}
MainWindow::~MainWindow()
{
delete ui;
}
1.9 QDialog 类型的窗口特点与显示方式
1.9.0 前期准备:
新建了一个基于 QDialog 的自定义对话框类 TestDialog(包含 TestDialog.h, TestDialog.cpp, TestDialog.ui)。
QDialog 是 Qt 中专门用于设计对话框的类。与普通的 QWidget 相比,它有以下几个核心特点:
1.9.1 QDialog永远是独立窗口(不会被内嵌)
对于 QWidget,如果指定了父对象,它会无边框内嵌到父窗口中。
但对于 QDialog,不管是否指定父对象,它都不会被内嵌到主窗口中,永远带有自己的边框和标题栏。
- 指定父对象的好处:虽然不内嵌,但它会在显示层级上始终“浮”在父窗口之上,并且在父窗口销毁时会被 Qt 对象树自动释放。
1.9.2 两种显示方式:模态 (Modal) vs 非模态 (Modeless)
“模态/非模态 (Modal/Modeless)” 是 GUI (图形用户界面) 交互设计的概念。它描述的是窗口之间的交互逻辑。
- 非模态显示 (
show()):- 特点:窗口弹出后,程序不会阻塞。焦点可以在主窗口和对话框之间自由切换。
- 适用场景:悬浮工具箱、查找替换窗口等。
- 模态显示 (
exec()):- 特点:窗口弹出后,会阻塞当前程序的执行(开启局部事件循环)。用户必须关闭该对话框后,代码才会继续往下走,在此期间其他窗口无法被点击。
- 适用场景:确认删除提示、密码输入框、文件选择框等必须立刻响应的场景。
1.9.3 示例代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "TestWidget.h"
#include "TestDialog.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 创建一个1.8的独立的QWidget对比
TestWidget* tsw = new TestWidget;
tsw->setAttribute(Qt::WA_DeleteOnClose); // 防止内存泄漏
tsw->show();
// ==========================================
// 演示 QDialog 的两种显示方式
// ==========================================
//#if 1
#if 0
// 非模态对话框通常需要在堆区 (new) 创建,否则函数结束时局部变量销毁,窗口就一闪而过了
// 不管是指定父对象,还是不指定父对象,QDialog对象都不会内嵌到主窗口中
TestDialog* tsd1 = new TestDialog(this); // 和new TestDialog 效果一样
tsd1->setAttribute(Qt::WA_DeleteOnClose); // 设置关闭时自动销毁
// show() 方法执行后瞬间返回,焦点可在各个窗口间自由切换,不会阻塞程序
tsd1->show();
#else
/* ---------------------------------------------------------
* 模式二:模态显示 (Modal) -> 使用 exec()
* --------------------------------------------------------- */
// 模态对话框因为会阻塞代码,可以在「栈区」创建。
TestDialog tsd2; // 和tsd2(this) 效果一样
// exec() 会开启局部事件循环。
// 构造函数被阻塞,焦点被锁定在此对话框,必须关闭该对话框,后面的代码才会执行。
// 此时MainWindow对象没有构造完成,因为构造函数没有执行完
tsd2.exec();
#endif
}
MainWindow::~MainWindow()
{
delete ui;
}
1.10 QMainWindow
1.10.0 前期准备:
在前两节(1.8 和 1.9)中,当我们通过 Qt Creator 或向导新建项目时,默认生成的 MainWindow 类就是继承自 QMainWindow。
QMainWindow 是一个为构建复杂桌面应用程序而量身定制的顶级窗口类。它不仅仅是一个空窗口,而是自带了一套严格的预设布局(UI 框架)。
1.10.1 窗口的底层划分:客户区与非客户区
在理解 QMainWindow 的内部结构前,需要明确操作系统对桌面独立窗口的底层划分机制:
- 非客户区 (Non-Client Area):由操作系统(如 Windows)直接负责绘制和管理。它包含了标题栏(含最小化、最大化、关闭按钮)以及最外层的边框。
- 客户区 (Client Area):边框以内的内部空间。这部分由 Qt 引擎全权接管,
QMainWindow的所有布局和控件都在客户区内展开。
1.10.2 核心特点:自带五大标准区域
与 QWidget 这个“光杆司令”不同,QMainWindow 在底层已经为你划分好了排版区域。一个标准的 QMainWindow 包含以下五个部分(按从上到下、从外到内的顺序):
- 菜单栏 (Menu Bar):位于窗口最上方(只能有一个)。包含“文件”、“编辑”、“帮助”等下拉菜单。
- 工具栏 (Toolbars):通常紧贴在菜单栏下方(可以有多个,可停靠在上下左右四边)。用于放置高频操作的快捷图标按钮。
- 停靠窗口/浮动窗口 (Dock Widgets):可以围绕中心部件停靠,也可以拖拽出来作为独立小窗口悬浮(例如开发工具侧边的“项目资源管理器”)。
- 中心部件 (Central Widget):窗口的绝对核心区域(必须且只能有一个)。通常用于放置主要的工作区控件,如文本编辑器、画布或视频播放区。
- 状态栏 (Status Bar):位于客户区最底部(只能有一个)。用于显示程序的当前运行状态、永久信息或临时提示。
1.10.3 示例代码:不使用.ui文件的新项目
虽然实际开发中通常通过 .ui 文件拖拽来设计 QMainWindow,但纯代码实现能清晰展示这五大区域的构建过程:
#include "mainwindowwithoutui.h"
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QTextEdit>
#include <QDockWidget>
MainWindowWithoutUI::MainWindowWithoutUI(QWidget *parent)
: QMainWindow(parent)
{
// 0. 设置窗口基本信息(修改的是操作系统的非客户区属性)
this->resize(800, 600);
this->setWindowTitle("QMainWindow 核心结构演示");
// 1. 菜单栏(只能有一个)
QMenuBar* mBar = menuBar();
this->setMenuBar(mBar);
QMenu* fileMenu = mBar->addMenu("file");
fileMenu->addAction("new file");
// 2. 工具栏(可以有很多个)
QToolBar* tBar = new QToolBar(this);
this->addToolBar(Qt::TopToolBarArea, tBar);
tBar->addAction("save");
// 3. 中心部件(核心,必须有)
QTextEdit* textEdit = new QTextEdit(this);
this->setCentralWidget(textEdit);
// 4. 停靠窗口/浮动窗口
QDockWidget* dock = new QDockWidget("sidebar侧边栏", this);
this->addDockWidget(Qt::LeftDockWidgetArea, dock);
// 5. 状态栏(只能有一个)
QStatusBar* sBar = statusBar();
this->setStatusBar(sBar);
// 状态栏显示提示信息,5秒后自动消失
sBar->showMessage("我是状态栏,显示5s", 5000);
}
MainWindowWithoutUI::~MainWindowWithoutUI()
{
}
1.11 Qt中的坐标体系
1.11.1 窗口的坐标原点
所有坐标的定位都需要先确立原点。Qt 的二维坐标系与我们在数学中常用的笛卡尔坐标系略有不同(Y轴方向相反),其核心规则如下:
-
坐标原点 $(0, 0)$:永远位于当前窗口(或屏幕)的左上角。
-
X 轴:向右递增(向右为正方向)。
-
Y 轴:向下递增(向下为正方向)。
1.11.2 父子窗口与相对坐标
在一个复杂的 Qt 界面中,通常会有许多子窗口(或控件)嵌套在父窗口中。例如:A 窗口包含 B 窗口,B 窗口包含 C 窗口。
这里的核心原则是:每个窗口(或控件)都有自己独立的坐标系。
-
相对坐标:子窗口的位置是基于父窗口的坐标体系来确定的。也就是说,子窗口的代码中设置的坐标点,是相对于其直接父窗口左上角 $(0,0)$ 的偏移量。
-
顶级窗口:如果没有父窗口,那么它的坐标就是相对于电脑屏幕的左上角。
移动窗口的 API:
所有窗口和控件的基类都是 QWidget,我们可以通过它提供的 move() 函数来移动窗口:
// QWidget 中提供了移动窗口的 API 函数
// 参数 x, y 是目标坐标点,调用后,当前窗口的“左上角”将被移动到其父对象坐标系的 (x, y) 位置
void QWidget::move(int x, int y);
// 另一个重载版本,使用 QPoint 对象来传递坐标(学习 QPoint 后可深入了解)
void QWidget::move(const QPoint &);
1.11.3 示例代码及空间层级图
以下代码展示了一个包含三层嵌套按钮的窗口。请注意观察每个控件的 parent 参数以及它们调用的 move(100, 100) 产生的相对位移效果。
MainWindow.cpp 示例:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 1. 设置主窗口(顶级窗口)
// 它的父对象是 nullptr(或桌面),所以它相对于电脑屏幕的左上角移动 100x100
this->move(100, 100);
this->setFixedSize(1500, 1500);
// 2. 创建 btnA
// this (MainWindow) 是 btnA 的父窗口
QPushButton* btnA = new QPushButton(this);
// btnA 相对于 MainWindow 的左上角向右下移动 100x100
btnA->move(100, 100);
btnA->setFixedSize(1000, 1000);
// 3. 创建 btnB
// btnA 是 btnB 的父窗口
QPushButton* btnB = new QPushButton(btnA);
// btnB 相对于 btnA 的左上角向右下移动 100x100
btnB->move(100, 100);
btnB->setFixedSize(1000, 1000);
// 4. 创建 btnC
// btnB 是 btnC 的父窗口
QPushButton* btnC = new QPushButton(btnB);
// btnC 相对于 btnB 的左上角向右下移动 100x100
btnC->move(100, 100);
btnC->setFixedSize(500, 500);
}
MainWindow::~MainWindow()
{
delete ui;
}
1.12 Qt 中的内存回收机制(对象树)
在标准 C++ 中,new 出来的堆内存必须手动 delete,否则会造成内存泄漏。但在 Qt 中,引入了基于对象树(Object Tree)的半自动内存回收机制。
我们在查看 Qt 类的构造函数时,经常会看到一个 QObject *parent 参数,这个参数就是 Qt 内存管理的核心——父对象指针。
1.12.1 核心原理:什么是对象树?
- 自动挂载:当你创建一个
QObject对象并为其指定父对象(parent)时,这个新对象会自动添加到其父对象的children()列表中。 - 连锁析构:当父对象被销毁(析构)时,Qt 会自动遍历其
children()列表,并将列表中的所有子对象一并安全析构。 - 双向安全:任何对象树中的
QObject被手动delete时,如果它有父对象,它会自动将自己从父对象的children()列表中移除;如果它有子对象,则自动触发所有子对象的delete。Qt 的底层机制保证了没有任何对象会被delete两次。
⚠️ 核心易混淆点(面试常考):
这里的“父对象(Parent)”指的是对象实例之间的层级归属关系(例如:按钮属于窗口),而不是 C++ 继承意义上的“父类(Base Class/Super Class)”!
1.12.2 对象树在 UI 界面中的作用
QWidget 是所有可视化组件(窗口、按钮、标签等)的基类,它不仅继承了 QObject 的对象树机制,还将其扩展到了屏幕坐标系中:
- 当一个控件成为另一个父组件的子组件时,它会显示在父组件的坐标系统中,并被父组件的边界剪裁。
- 逻辑一致性:当用户关闭并销毁一个主对话框时,属于这个对话框的所有按钮、输入框、图标理应一起被销毁。对象树机制完美实现了这一逻辑。
1.12.3 触发Qt自动回收的“两大条件”
Qt 确实有内存回收机制,但并非所有被 new 出来的对象都会被自动回收。想要让 Qt 帮你自动管理内存,必须同时满足以下两个条件:
-
必须是
QObject的派生类- 只有继承自
QObject(直接或间接)的类,才具备对象树功能。 - Qt 中绝大多数的核心类(特别是所有窗口类和 UI 控件)都是
QObject的子类。
- 只有继承自
-
必须指定父对象(Parent)
-
创建出来的堆对象,必须指明它挂载在哪个父对象下。通常有两种指定方式:
-
在实例化时通过构造函数传参(最常用)
// parent: 当前控件的父对象,直接在 new 的时候传入 QWidget* myWidget = new QWidget(this); QTimer* myTimer = new QTimer(this); -
通过
setParent()方法后期绑定// 假设控件在构造时没有指定父对象 QPushButton* btn = new QPushButton; // 稍后调用 API 强制指定父对象 btn->setParent(this);
-
-
1.12.4 示例代码
-
自定义一个类
SubWindow(SubWindow.h+SubWindow.cpp)SubWindow.h:#ifndef SUBWINDOW_H #define SUBWINDOW_H #include <QDialog> namespace Ui { class SubWindow; } // 继承自QDialog(间接继承自QObject) class SubWindow : public QDialog { Q_OBJECT public: // 构造函数,透传 parent 指针 explicit SubWindow(QWidget *parent = nullptr); // 析构函数:如果自动回收生效,这里一定会执行 ~SubWindow(); private: Ui::SubWindow *ui; }; #endif // SUBWINDOW_HSubWindow.cpp:#include "subwindow.h" #include "ui_subwindow.h" #include <QDebug> SubWindow::SubWindow(QWidget *parent) : QDialog(parent), ui(new Ui::SubWindow) { qDebug() << "SubWindow构造函数被调用"; ui->setupUi(this); } SubWindow::~SubWindow() { qDebug() << "SubWindow析构函数被调用"; delete ui; } -
在主窗口中使用它 (mainwindow.cpp)
MainWindow.cpp:#include "mainwindow.h" #include "ui_mainwindow.h" #include "SubWindow.h" #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { qDebug() << "MainWindow构造函数被调用"; ui->setupUi(this); SubWindow* sub = new SubWindow(this); sub->show(); } MainWindow::~MainWindow() { qDebug() << "MainWindow析构函数被调用"; // 注意:这里没有delete sub delete ui; } -
运行并关闭窗口后的输出结果 (Application Output):
MainWindow构造函数被调用 SubWindow构造函数被调用 MainWindow析构函数被调用 SubWindow析构函数被调用
结论:即使我们没有手动 delete sub;,当 MainWindow(父对象)被系统销毁时,SubWindow (子对象)也会被自动且安全地析构。

浙公网安备 33010602011771号