一、Qt简介
1. Qt是什么?
Qt是一个基于C++语言的图形用户界面(GUI)开发框架,Qt不仅仅可以进行GUI开发,除此之外Qt也能进行很多其它功能开发,包括但不限于多线程、数据库、图像处理、音视频处理、网络通信与文件IO等。
Qt广泛地应用于嵌入式开发和传统软件开发中:
(1) 传统软件客户端
(2)上位机:远程控制嵌入式下位机
(3)嵌入式产品控制程序
2. Qt的优势
Qt与各种竞品相比,主要的优势是跨平台特性。
跨平台特性指的是:一次编程,到处编译。
除此之外,Qt也拥有一些其它竞品的共同优势特点:
面向对象开发
丰富的API,并配以大量的开发文档
易用且开源的开发环境
3. 开发环境
创建一个新的Qt项目的步骤如下:
1. 启动Qt Creator后,点击New Project按钮。

2. 在弹出窗口中,按照下图所示进行操作。

3. 在弹出的窗口中,编辑项目的名称和工作目录的位置,要求同之前C++,设定后点击“下一步”。

4. 在弹出的窗口中,直接点击“下一步”。

5. 在弹出的窗口中,按照下图所示进行操作,更改基类为QDialog,以便于日常学习方便。

6. 在项目管理界面直接点击“完成”,可以看到项目包含的各种文件,如下所示。

7. 点击运行按钮,观察程序执行效果。

4. 工作目录与构建目录
工作目录:即新建项目时配置的路径,存放源代码文件的目录。

构建目录:当程序构建时,会在此目录下生成编译的文件。

需要注意的是,默认情况下开启影子构建,此时工作目录与构建目录分离。如果取消此模式,构建目录会合并到工作目录,优势是可以提升编译的稳定性,劣势是文件分类不明确。

5. 查看帮助文档
Qt自带了大量的帮助文档,可以通过以下几种方式打开:
法一:直接打开Assistant程序,可以在一个独立窗口中打开帮助文档。

法二:在Qt Creator中点击左栏的“帮助”,可以打开一个内置的帮助文档。

法三:在Qt Creator中,光标定位到要查询的内容,双击键盘F1,可以直接通过内置的帮助文档查询到对应的内容。
每个类在文档中需要注意下面的部分。

6. 解析默认文件代码
6.1 项目配置文件
#-------------------------------------------------
#
# Project created by QtCreator 2023-01-30T21:19:35
#
#-------------------------------------------------
# 当前项目需要添加的模块
QT += core gui
# 当Qt主版本号大于4时,添加widgets模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = HelloQt # 可执行文件名称
TEMPLATE = app # 当前项目的构建模式
# 当前项目包含的源文件
SOURCES += main.cpp\
dialog.cpp
# 当前项目包含的头文件
HEADERS += dialog.h
# 当前项目包含的界面文件
FORMS += dialog.ui
此文件除了添加的模块外,通常不需要程序员手动维护。
6.2 Dialog类文件
默认的Qt项目会创建一个Dialog类,此类包含三个文件:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
namespace Ui {
class Dialog;
}
/**
* @brief The Dialog class
* 自定义类继承了源代码的QDialog类
*/
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0); // 构造函数
~Dialog(); // 析构函数
private:
Ui::Dialog *ui; // 成员变量
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
/**
* 构造函数
*/
Dialog::Dialog(QWidget *parent) :
QDialog(parent), // 透传构造
ui(new Ui::Dialog) // 构造初始化列表
{
ui->setupUi(this);
}
/**
* 析构函数
*/
Dialog::~Dialog()
{
delete ui;
}
dialog.ui
略,后续有单独章节讲解
6.3 主文件
main.cpp
#include "dialog.h"
#include <QApplication>
/**
* @brief main 程序的入口,主函数
* @return
*/
int main(int argc, char *argv[])
{
// 创建一个应用程序管理类对象
QApplication a(argc, argv);
// 创建了一个Dialog类对象w
Dialog w;
// 显示对象w在UI上
w.show();
return a.exec(); // 进入循环状态,程序持续运行
}
7. 调试函数
之前C++课程中更改了Qt Creator的文件编码,现在需要恢复之前的默认编码,操作如下:

前期学习为了代码更加精简,在创建项目时,更改第5步为:

其它步骤不变,这样的项目没有了界面文件,代码更加简洁。
Qt中调试信息是不能在界面上被用户看到的,因此需要使用QDebug类将调试信息在后台输出,这样的信息用户无法看到。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
// 引入头文件
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
// 注意Qt中命名规范与C++有不同
// 函数与变量使用 驼峰命名法(小驼峰命名法):
//第一个单词全小写,后面的单词首字母大写
qDebug() << "构造函数"; // 结束后会自动换行
}
Dialog::~Dialog()
{
qDebug() << "析构函数";
}
二、UI基础
1. QWidget类
QWidget类是所有可视化组件和窗口的基类,因此QWidget中成员可以继承给众多派生类使用。

QWidget最基础的属性:
width : const int
宽度,单位像素
可以通过int width() const获得数值,即getter
height : const int
高度,单位像素
getter:int height() const
x : const int
横坐标,单位像素,原点左上角,正方向右
getter:int x() const
y : const int
纵坐标,单位像素,原点左上角,正方向下
getter:int y() const
QWidget最基础的函数:
void resize(int w, int h)
重定义宽高
void move(int x, int y)
移动到设定的坐标处,所有的组件和窗口以左上角为定位点
void setGeometry(int x, int y, int w, int h)
同事设置坐标和宽高
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
// 头文件
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
// 重定义宽高
resize(1000,240);
// this指针指向主函数中的w对象
int w = this->width();
int h = height();
// 输出
qDebug() << w << h;
// 获得位置
int x = this->x();
int y = this->y();
qDebug() << x << y; // 0,0 不准,因为窗口对象还在创建中
// 移动
move(100,100);
// 同时设置坐标和宽高
setGeometry(400,400,200,200);
}
Dialog::~Dialog()
{
}
2. 添加子组件
以最常见按钮(QPushButton)为例,讲解在窗口中添加子组件的方法。
QPushButton的构造函数如下:
QPushButton::QPushButton(const QString & text, QWidget * parent = 0)
参数1:按钮显示的文字
参数2:按钮在哪个对象上
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
// 头文件
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
// 私有成员变量
QPushButton* btn;
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(400,400);
// 创建一个按钮对象
// 参数2是使用this指针结合多态传参
btn = new QPushButton("初十",this);
// 更改按钮大小
btn->resize(100,100);
// 移动按钮位置,注意坐标是窗口内部的相对坐标
btn->move(100,100);
}
Dialog::~Dialog()
{
// 释放按钮内存
delete btn;
}
3. 样式表
Qt可以使用QSS语法设置组件的样式效果。
颜色的配置可以通过以下几个方法:
QQ截图
在线色表+颜色进制转换
http://tools.jb51.net/static/colorpicker/
色彩搭配
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
// 头文件
#include <QPushButton>
#define QPushButton_STYTLE (QString("\
/*按钮普通态*/\
QPushButton\
{\
font-family:Microsoft Yahei;\
/*字体大小为20点*/\
font-size:20pt;\
/*字体颜色为粉色*/\
color:rgb(238, 210, 238);\
/*背景颜色*/\
background-color:rgb(197,146,163);\
/*边框圆角半径为8像素*/\
border-radius:8px;\
}\
/*按钮悬停态*/\
QPushButton:hover\
{\
/*背景颜色*/\
background-color:#9F79EE;\
}\
/*按钮按下态*/\
QPushButton:pressed\
{\
/*背景颜色*/\
background-color:rgb(14 , 135 , 10);\
/*左内边距为3像素,让按下时字向右移动3像素*/\
padding-left:3px;\
/*上内边距为3像素,让按下时字向下移动3像素*/\
padding-top:3px;\
}"))
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
// 私有成员变量
QPushButton* btn;
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(400,400);
btn = new QPushButton("初十",this);
// 设置样式表
btn->setStyleSheet(QPushButton_STYTLE);
btn->resize(100,100);
btn->move(100,100);
}
Dialog::~Dialog()
{
delete btn;
}
三、信号槽(重点)
1. 信号槽的概念
在之前的学习中,可以实现简单的UI效果,但是按钮不能点击。如果让按钮能在用户点击后执行某个代码,就需要用到Qt中的信号槽机制。
信号槽是Qt基于C++语法上新增的特性,可以实现对象之间的通信,形成一定因果关系。
使用信号槽的对象需要具备两个条件:
通信的对象必须继承自QObject
类中要有Q_OBJECT宏
2. 函数原型
QObject类是所有Qt对象的基类,此类中有一个静态成员函数connect,用于连接信号槽之间的因果关系,函数原型如下:

参数1:发射者,通信的对象,此对象是信号槽触发的来源,例如:按钮对象(n.)
参数2:信号函数,使用SIGNAL()包裹,表示发射者触发的效果,例如:点击(v.)
参数3:接收者,通信对象,此对象是执行结果代码的主体(n.)
参数4:槽函数,使用SLOT()包裹,表示接收者要执行的函数(v.)
为了方便讲解各种场景下使用信号槽的不同方式,分别使用三种类型进行讲解:
自带信号→ 自带槽
自带信号→ 自定义槽
自定义信号→ 槽函数
2.1 自带信号 → 自带槽
这是最简单的一种连接方式,因为信号函数和槽函数都在Qt中预设了,只需要通过connect函数“连线”即可。
【例子】点击按钮,关闭窗口。
分析:
参数1,按钮对象;
参数2,点击函数;
参数3,窗口对象;
参数4:关闭函数。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton* btn;
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(300,300);
btn = new QPushButton("关闭",this);
btn->move(100,100);
// 参数1,按钮对象 btn
// 参数2,点击函数 void clicked()
// 参数3,窗口对象 this
// 参数4:关闭函数 bool close()
connect(btn,SIGNAL(clicked()),this,SLOT(close()));
}
Dialog::~Dialog()
{
delete btn;
}
2.2 自带信号 → 自定义槽
这种方式是使用频率最高的一种连接方式,因为Qt源代码中不可能囊括所有要执行的代码。实际上槽函数是一种特殊的成员函数,编写方式基本等同成员函数。
【例子】点击按钮,左下角移动窗口并输出移动后的窗口坐标。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton* btn;
// 私有槽函数
private slots:
// 声明自定义槽函数
void mySlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(300,300);
btn = new QPushButton("移动并输出",this);
btn->move(100,100);
// 连接信号槽
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
}
void Dialog::mySlot()
{
// 获得当前坐标
int x = this->x();
int y = this->y();
// 移动窗口
move(x+10,y+10);
// 输出
qDebug() << x << y;
}
Dialog::~Dialog()
{
delete btn;
}
2.3 自定义信号
自定义信号主要用于后期一些相对复杂的通信场景,本次学习强行使用,并不是功能实现的最优解。
信号函数是非常特殊的一种函数,只有声明,没有定义,且不能在代码中直接调用,可以配合emit关键字进行发射。
【例子】点击按钮,关闭窗口。

dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn;
// 自定义槽函数
private slots:
void mySlot();
// 声明信号函数,只声明
signals:
void mySignal();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(500,500);
btn = new QPushButton("关闭",this);
btn->move(200,300);
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot()));
connect(this,SIGNAL(mySignal()),this,SLOT(close()));
}
// 自定义槽函数
void Dialog::mySlot()
{
// 发射自定义信号
emit mySignal();
}
Dialog::~Dialog()
{
delete btn;
}
3. 参数传递
【例子】点击按钮,按钮上显示点击的次数。
提示:
QPushButton显示文字的属性:
text : QString
getter:QString text() const
setter:void setText(const QString & text)
3.1 全局参数
本次使用成员变量作为一个对象内部的全局参数,根据实际情况也可以使用静态变量。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
int count; // 记录点击的次数
QPushButton* btn;
private slots:
void btnClickedSlot(); // 按钮点击的槽函数
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
count = 0; // 属性赋予初始值
resize(300,400);
btn = new QPushButton("0",this);
btn->move(100,250);
connect(btn,SIGNAL(clicked()),
this,SLOT(btnClickedSlot()));
}
void Dialog::btnClickedSlot()
{
// 计数+1
count++;
// int → QString
QString text = QString::number(count);
// 设置显示
btn->setText(text);
}
Dialog::~Dialog()
{
delete btn;
}
3.2 信号槽传参
使用信号槽也可以进行参数传递,但是这种方式通常用户后面较为复杂的情况,本次讲解的代码也不是最优解。

dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn;
private slots:
void mySlot1(); // 自定义槽函数1
void mySlot2(int); // 自定义槽函数2
signals:
// 带参数的自定义信号函数
void mySignal(int);
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(300,300);
btn = new QPushButton("0",this);
connect(btn,SIGNAL(clicked()),this,SLOT(mySlot1()));
connect(this,SIGNAL(mySignal(int)),this,SLOT(mySlot2(int)));
}
Dialog::~Dialog()
{
delete btn;
}
void Dialog::mySlot1()
{
// 静态局部变量
static int count = 0;
// 发射自定义信号
emit mySignal(++count);
}
void Dialog::mySlot2(int count)
{
// int → QString
QString text = QString::number(count);
// 设置显示
btn->setText(text);
}
需要注意的是:
1. 理论上可以通过信号槽发送任意多个参数
2. 信号函数的参数个数必须大于等于槽函数的参数个数
3. 参数类型必须一致
4. 对应关系
4.1 一对多
同一个信号可以同时连接多个槽函数。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton* btn1;
QPushButton* btn2;
private slots:
void mySlot1();
void mySlot2();
void mySlot3();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(300,600);
btn1 = new QPushButton("一对多",this);
btn1->move(100,200);
connect(btn1,SIGNAL(clicked()),this,SLOT(mySlot1()));
connect(btn1,SIGNAL(clicked()),this,SLOT(mySlot2()));
btn2 = new QPushButton("一对一",this);
btn2->move(100,400);
connect(btn2,SIGNAL(clicked()),this,SLOT(mySlot3()));
}
void Dialog::mySlot1()
{
qDebug() << "A";
}
void Dialog::mySlot2()
{
qDebug() << "B";
}
void Dialog::mySlot3()
{
// 槽函数也是成员函数,可以直接调用槽函数1和槽函数2
mySlot1();
mySlot2();
}
Dialog::~Dialog()
{
delete btn1;
delete btn2;
}
4.2 多对一
多个信号可以连接到同一个槽函数。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QDebug>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
private:
QPushButton *btn1;
QPushButton *btn2;
private slots:
void mySlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
resize(600,200);
btn1 = new QPushButton("1",this);
btn1->move(200,100);
btn2 = new QPushButton("2",this);
btn2->move(400,100);
connect(btn1,SIGNAL(clicked()),this,SLOT(mySlot()));
connect(btn2,SIGNAL(clicked()),this,SLOT(mySlot()));
}
void Dialog::mySlot()
{
qDebug() << "自定义槽函数";
}
Dialog::~Dialog()
{
delete btn1;
delete btn2;
}