《QT学习第一篇:QT的概述与安装、信号与槽》

**前引:**本文介绍了QT框架的基本概念、开发环境搭建及核心功能实现。首先阐述了QT作为跨平台GUI应用程序框架的特点,包括其面向对象的架构和高效的开发流程。然后详细说明了开发环境配置步骤,包括编译器安装、QTSDK部署和IDE选择。重点解析了QT项目文件结构、对象树机制和信号槽系统,通过HelloWorld示例展示了控件创建和事件处理的方法。文章还介绍了坐标体系、编码问题解决、快捷键使用等实用技巧,最后讲解了如何查阅官方文档。全文系统性地呈现了QT开发的核心知识体系,为初学者提供了清晰的入门路径。

目录

一、QT介绍

(1)定义

(2)框架理解

(3)优点

二、QT开发环境

[(1)QT SDK下载](#(1)QT SDK下载)

[(2)QT 环境变量配置](#(2)QT 环境变量配置)

(3)第一个项目

(4)注意点

三、配置文件讲解

(1)main.cpp

(2)new_project.cpp

(3)new_project.h

(4)new_project.ui

(5)project_test.pro

(6)项目整体逻辑

[四、第一个项目:Hello World](#四、第一个项目:Hello World)

(1)图形化

(2)代码化

五、对象树

(1)针对内存泄漏

(2)在栈上创建

(3)如何使用自定义类

(4)几大问题

[六、QT Creater中的快捷键](#六、QT Creater中的快捷键)

七、QT文档查询

[八、QT 坐标体系](#八、QT 坐标体系)

九、信号与槽

(1)理解

(2)三要素

(3)connect函数

(1)自定义槽函数(图形)

(2)自定义槽函数(代码)

(3)自定义信号

(4)槽函数带参

(5)解除绑定

(6)多关联性


一、QT介绍

(1)定义

简单直接,从大的范围来说:QT属于桌面客户端开发

客户端:直接和用户打交道的界面,背后可能存在服务端,也可能属于单客户端

而桌面客户端又可以分为:指令式客户端(TUI)图形化客户端(GUI)

而QT就属于:桌面客户端的图形化界面方向,更严格来说属于图形化 用户界⾯ 应⽤程序框架

(2)框架理解

在了解框架之前,我们先看一下库:

库提供了具体的方法实现接口 ,这个工具流程如何设计,完全交给你,你调用库的代码

那么框架恰恰相反:

框架规定了大致的主导流程 ,你需要实现细节,那么需要按照框架的结构,框架调用你的细节代码

(3)优点

至于解释为什么,不重要,它有什么优点,我们拿到知道怎么用就行:

(1)跨平台支持,几所所有平台

(2)接口简单,上手快

(3)开发效率高,快速的构建程序,因为它已经给你固定了大致的主导流程

(4)可以进行嵌入式开发

二、QT开发环境

简单点,它的开发环境需要完成三步:

(1)C++(为例)的编译器,比如gcc,而VS Code这种属于IDE,这需要做一个区分!

(而IDE内部可以自动调用 编译器)

(2)QT SDK,封装好的软件开发工具包

(3)QT的集成开发环境(IDE)

{

(1)官方提供的QT creator------>上手快,适合新手

(2)IDE插件 Visual Studio------>功能强、但配置多

(3)Eclipse

}

那么综上,我们搞一个 IDE 就行了,以QT creator 为例!

(1)QT SDK下载

http://download.qt.io/archive/qt/

选择:5.14.2版本

再选择Windows 桌面应用环境:

下载完成之后双击安装,在SDK组件中选择下面三个:

完成安装:

(2)QT 环境变量配置

为什么设置环境变量:

在Windows上设置Qt的环境变量是为了能够在命令⾏或其他应⽤程序中

直接访问Qt相关的命令和⼯具

找到QT的安装路径,复制,打开"系统环境变量"

在"系统环境变量中"添加(用户或者系统都可以)

(3)第一个项目

(1)文件------>新建项目------>选择"Application",

(2)选择QT构建工具------>老牌稳!(qmake最终生成makefile文件)

QT构建工具选择解释:

(3)选择父类(QWidget)

前面我们提到了框架------>那不就是父类和基类的关系吗?我们是新手,就选择 Qwidget

(4)勾选 generate from

原因:可以以图形化的方式生成界面

(5)选择翻译语言(默认下一步即可)

(6)选择编译器(默认下一步即可)

(7)版本控制(git 或者 none)

(8)完成创建

(4)注意点

在项目的目录中不能出现中文

三、配置文件讲解

(1)main.cpp
cpp 复制代码
#include "new_project.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    new_project w;
    w.show();
    return a.exec();
}

main函数的命令行参数就不多说了:参数个数、对应参数内容

QApplication a(argc, argv):创建QT框架对象,就是实例化一份QT资源

new_project w:要实现的具体功能对象

w.show():触发绘制逻辑指令

return a.exec():一直循环显示窗口

作用:项目的整个调用逻辑顺序

(2)new_project.cpp
cpp 复制代码
#include "new_project.h"
#include "ui_new_project.h"

//第一部分
new_project::new_project(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::new_project)
{
    ui->setupUi(this);
}

//第二部分
new_project::~new_project()
{
    delete ui;
}

第一部分:完成自定义功能窗口的初始化工作

第二部分:对你的自定义窗口资源进行释放

那么为什么不直接利用 main.cpp 实例化 new_project 对象自动调用构造和析构呢?

作用:通过自定义构造和析构提高工程扩展性和可维护性,将类的声明和定义分离

(3)new_project.h
cpp 复制代码
#ifndef NEW_PROJECT_H
#define NEW_PROJECT_H

#include <QWidget>

//第一部分
QT_BEGIN_NAMESPACE
namespace Ui { class new_project; }
QT_END_NAMESPACE

//第二部分
class new_project : public QWidget
{
    Q_OBJECT

public:
    new_project(QWidget *parent = nullptr);
    ~new_project();

private:
    Ui::new_project *ui;
};
#endif // NEW_PROJECT_H

首先这是一个头文件,开头的#ifndef....就是保护相同的头文件只出现一次

第一部分:定义了一个命令空间 Ui,里面包含了 new_project 类

第二部分:new_project 的具体实现

那么为什么要这么做呢?需要额外定义一个命令空间?

前置声明:只告诉编译器 "存在一个Ui::new_project类",提高编译效率

作用:

它像项目整个蓝图描述,只标注 "项目继承了整个父类、有哪些函数、有哪些成员变量",

不包含具体的实现细节(那是.cpp的事)

(4)new_project.ui

作用:

  • 提效:可视化设计界面,省去所有手写布局 / 控件属性的繁琐代码;
  • 解耦:界面布局和功能逻辑分离,改界面不影响功能,改功能不影响界面;

(因为.cpp是管具体的功能实现,那么 .ui 就是管这个图形化界面排版)

(5)project_test.pro

核心配置文件,由qmake工具解析,其核心作用是定义项目的构建规则

告诉 qmake 如何编译代码、链接库、处理资源,最终生成可执行文件

(6)项目整体逻辑

编译阶段:

qmake 把.pro的配置、.ui的设计(图形排版)、.h/.cpp的代码(功能实现),转为可执行文件

运行阶段:

main函数启动,先初始化 QT 框架,再加载自定义界面,最后启动事件循环响应操作,执行析构

四、第一个项目:Hello World

(1)图形化

直接在 .ui 文件中,选择 Label 控件(标签意思),调整大小后,输入要显示的内容

(2)直接运行即可

(2)代码化

在构造函数中创建一个 Qlabel 对象(栈、堆都可以),然后使用里面的接口即可

为什么要不在 new_project.h 中实现?

new_project.h 中是声明,可能需要向外暴露,但在实际中,具体的实现和声明应该是分离的

为什么 Qlabel 的参数是 this ?

很好理解,谁调用的这个类那么this就指向的谁,在外面是 w(自定义功能具体实现) 调用的,所以它的具体含义是:将 Qlabel 这个控件交给 this 也就是 w 进行管理

五、对象树

例如:

(1)针对内存泄漏

那么这样 new 出来的对象需要担心内存泄漏吗?

首先明白:堆上内存什么时候结束?程序结束自动释放或者手动,那么这里将QLabel挂在了 w 上面,形成一棵N叉树,可以按照(派生类->基类)的内存释放顺序理解!树状结构的对象!

cpp 复制代码
QApplication a(顶层根节点)
    ↳ new_project w(窗口,父对象)
        ↳ QLabel label(子对象,父是w)

同时也表明了:QApplication 是框架,其余只是添加"框架"的枝干!

(2)在栈上创建

完全不用担心,我们先看另外一种形式的创建:在栈上创建,可以看见没有输出

具体原因:我们是在 new_project 类对象 w 构造中创建的,所以 w 在当前栈一销毁,就没了

(3)如何使用自定义类

(1)首先在文件里面创建一个C++的类,自定义类名称

(2)将这个类加入到对象树

(3)实现具体的方法

(4)几大问题

为什么 new_project 类实例的 w 对象,它的构造类型是 Qwidget 类型?

首先原本 this 指针的类型是 new_project * 类型,而子类指针可以安全的转化为父类指针也就是 Qwidget * 类型,从而 this 指针类型可以进行一个转化!

一个类(自定义或内置生类)是如何加入到对象树的?

**原则:**只有继承了QObject的类,才能被加入 QT 对象树(QWidget继承⾃QObject)

内置类:内置类天上继承了 QObject,可以直接用,不用管

自定义类:这个自定义类现在是派生类,只有让派生类继承某个内置类才能进入对象树

字符串编码显示问题?

首先一个汉字占多少字节取决于编码方式:

(1)GBK------>2个字节一个汉字

(2)UTF-8------>3个字节一个汉字

在QT中,可能因为编码格式不统一(编码和显示器)会形成乱码,所以将 cout 切换为 qDebug

例如:

同时:

qDebug 在调试阶段可看见打印内容,但是在发布版本中是不显示的,很方便用来打印日志

六、QT Creater中的快捷键

注释:Ctrl + /

运行:Ctrl + R

编译:Ctrl + B

字体缩放:Ctrl + 鼠标滑轮

查找:Ctrl + F

整行移动:Ctrl + shift + 向上箭头/向下箭头

自动对齐:Ctrl + i

同类的源文件和头文件切换:F4

立刻给函数声明实现定义:选中这个函数 + Alt + Enter

七、QT文档查询

(1)直接打开assistant.exe

(2)帮助

八、QT 坐标体系

对于嵌套窗⼝,其坐标是相对于⽗窗⼝来说的

通过 move(x,y)内置接口来完成相较于父窗口的移动

例如:

九、信号与槽

(1)理解

触发了什么信号------>执行什么样的方法

(2)三要素

信号源:由哪个控件触发

信号的类型:比如点击信号、输入信号........本质也是一个类

槽:信号处理的方法------>函数

(3)connect函数

那么如何把信号与槽关联起来?

用 **connect()**接口将二者进行关联:它属于QObject的接口,由于具有继承关系,可以直接用!

接口原型:

cpp 复制代码
connect(
    发送者对象指针,    // 参数1:谁发信号
    &发送者类::信号函数名, // 参数2:发什么信号
    接收者对象指针,    // 参数3:谁接收信号
    &接收者类::槽函数名  // 参数4:接收后执行什么(或Lambda表达式)
);

简单易上手:

第一个参数:如果是内置信号,就是控件;自定义信号就是 this(因为是当前 Widget 对象成员)

第三个参数:槽函数属于哪个对象,就写哪个对象的指针

参数说明:

(1)**参数:**发送对象的指针

举例:如点击按钮,指针指向的是QPushbutton按钮(堆上存储),含义就是由QPushbutton发出

注意:必须是QObject*类型(或其子类指针,如QPushButton*QWidget*

(2)**参数:**要发出的信号

举例:信号本质也是一个类函数,那么比如点击,不就是点击里面的一个信号函数吗:

控件 信号函数名 含义
QPushButton clicked() 按钮被点击
QLineEdit textChanged(QString) 输入框文本改变
QCheckBox toggled(bool) 复选框选中状态改变

connect ( btn , &QPushButton::clicked , ...); // 参数2是按钮的"点击信号"

注意:类型要求: 必须是「发送者类的成员函数指针」,格式是&类名::信号函数名

(3)**参数 :**信号接收者

举例:接收到了某个信号,谁来处理?那不当前窗口类型的指针吗?比如 this

注意:和参数 1 一样,必须是QObject*类型(或其子类指针)。

(4)**参数:**槽函数或者Lambda表达式

例如:(1)直接使用接受者里面的成员函数方法

cpp 复制代码
// new_project.h里声明了void onBtnClicked();
connect(btn, &QPushButton::clicked, this, &new_project::onBtnClicked);

(2)Lambda表达式

cpp 复制代码
connect(btn, &QPushButton::clicked, this, [=]() {
    // 这里直接写槽逻辑,比如修改标签文本
    label->setText("按钮被点击了!");
});

注意:类型要求 :「接收者类的成员函数指针」,格式是&接收者类::槽函数名

(1)自定义槽函数(图形)

我们可以直接在 .ui 文件中选择操作,比如点击操作,然后右键"槽函数"

然后使用 ui 来操作这个点击按钮,就比如 " ui -> pushbutton"就拿到了这个点击按钮控件,而通过图形化自定义槽函数我们只需要实现 槽函数即可,QT自动完成了connect连接!

为什么是通过 ui 操作,而不是 this?

可以看到我们自定义的细节功能 Widget 只是 ui 里面的一个类,而我们的图形化操作是直接属于 ui

ui管 "可视化拖的控件",this管 "自己写的 Widget 类本身"

(2)自定义槽函数(代码)

通过代码实现的话,就是通过 connect 接口进行关联信号和槽:

第一个参数:哪里来的信号,这个我们已经具备,就是 ui 里面的(控件名称)

第二个参数:触发的是什么信号,很明显,是这个控件的点击信号(为例)

第三个参数:信号的接受者,就是当前窗口 this

第四个参数:任务执行函数,所以我们需要自定义一个任务函数

效果例如:

(3)自定义信号

自定义信号只需要在类里面增加一个:signals修饰,信号的本质就是一个成员函数

这里的 emit 不用加域的原因是,它是在Widget函数里面使用的,自带域

(4)槽函数带参

为什么槽函数支持带参?让信号在触发时,将参数传给槽函数------>信号给槽函数传参

**规则1:**信号的参数个数 >= 槽函数的参数个数

**规则2:**槽函数会从左到右与信号进行参数匹配,即槽函数可以不全部获取完信号的参数

例如:

那么在connect后,槽函数会从左到右自动匹配参数类型:

效果:

(5)解除绑定

绑定信号和槽函数是:connect

解除信号和槽保证参数相同,将 connect 改成 disconnect

(6)多关联性

即一个信号可以对应多个槽函数,那么一个槽函数也可以被多个信号同时触发

相关推荐
Quz5 天前
QML Hello World 入门示例
qt
xcyxiner8 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner8 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner9 天前
DicomViewer (添加模型类)3
qt
xcyxiner9 天前
DicomViewer (目录调整) 2
qt
xcyxiner9 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00611 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术11 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
通信小呆呆11 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
码云数智-园园11 天前
C++20 Modules 模块详解
java·开发语言·spring