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

  • 下拉菜单中有三个选项, 分别为: QMainWindowQDialogQWidget

补充 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 (图形用户界面) 交互设计的概念。它描述的是窗口之间的交互逻辑

  1. 非模态显示 (show())
    • 特点:窗口弹出后,程序不会阻塞。焦点可以在主窗口和对话框之间自由切换。
    • 适用场景:悬浮工具箱、查找替换窗口等。
  2. 模态显示 (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() 列表中移除;如果它有子对象,则自动触发所有子对象的 deleteQt 的底层机制保证了没有任何对象会被 delete 两次。

⚠️ 核心易混淆点(面试常考):

这里的“父对象(Parent)”指的是对象实例之间的层级归属关系(例如:按钮属于窗口),而不是 C++ 继承意义上的“父类(Base Class/Super Class)”!

1.12.2 对象树在 UI 界面中的作用

QWidget 是所有可视化组件(窗口、按钮、标签等)的基类,它不仅继承了 QObject 的对象树机制,还将其扩展到了屏幕坐标系中:

  • 当一个控件成为另一个父组件的子组件时,它会显示在父组件的坐标系统中,并被父组件的边界剪裁。
  • 逻辑一致性:当用户关闭并销毁一个主对话框时,属于这个对话框的所有按钮、输入框、图标理应一起被销毁。对象树机制完美实现了这一逻辑。

1.12.3 触发Qt自动回收的“两大条件”

Qt 确实有内存回收机制,但并非所有被 new 出来的对象都会被自动回收。想要让 Qt 帮你自动管理内存,必须同时满足以下两个条件

  1. 必须是 QObject 的派生类

    • 只有继承自 QObject(直接或间接)的类,才具备对象树功能。
    • Qt 中绝大多数的核心类(特别是所有窗口类和 UI 控件)都是 QObject 的子类。
  2. 必须指定父对象(Parent)

    • 创建出来的堆对象,必须指明它挂载在哪个父对象下。通常有两种指定方式:

      1. 在实例化时通过构造函数传参(最常用)

        // parent: 当前控件的父对象,直接在 new 的时候传入
        QWidget* myWidget = new QWidget(this); 
        QTimer* myTimer = new QTimer(this);
        
      2. 通过 setParent() 方法后期绑定

        // 假设控件在构造时没有指定父对象
        QPushButton* btn = new QPushButton; 
        // 稍后调用 API 强制指定父对象
        btn->setParent(this);
        

1.12.4 示例代码

  1. 自定义一个类SubWindowSubWindow.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_H
    

    SubWindow.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;
    }
    
  2. 在主窗口中使用它 (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;
    }
    
  3. 运行并关闭窗口后的输出结果 (Application Output):

    MainWindow构造函数被调用
    SubWindow构造函数被调用
    MainWindow析构函数被调用
    SubWindow析构函数被调用
    

结论:即使我们没有手动 delete sub;,当 MainWindow(父对象)被系统销毁时,SubWindow (子对象)也会被自动且安全地析构。

posted @ 2026-05-16 22:06  学习笔记草稿存放账号  阅读(21)  评论(0)    收藏  举报