新建一个项目,进入mian函数:
#include "mainwindow.h"
#include //包含一个应用程序类的头文件
//argc代表命令行变量的数量
//argv[]代表命令行数组
int main(int argc, char *argv[])
{
//a是应用程序对象,在QT中,应用程序对象有且只有一个(记住)
QApplication a(argc, argv);
//窗口的对象(记住)
MainWindow w;
//窗口对象默认不会显示,必须要调用show方法
w.show();
//让应用程序对象a进入消息循环机制(可以简单认为是一个死循环),一直等待用户发送消息
//当代码阻塞到这一行,后面的代码是无法执行的
return a.exec();
}
``argc和 argv[]作用:这两个数据用以接收各种外部的指令,比如鼠标指令、键盘指令。捕获到的数据再传送给
QApplication对象a,然后应用程序对象a可以做出响应。
QT += core gui //程序所使用到的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets//大于4版本以上,需要在包含widgets模块
TARGET = Hello_QT //目标,生成.exe文件的名称(可以自定义修改)
TEMPLATE = app //模板:app,也即是应用程序的模板
SOURCES += \ //源文件
main.cpp \ //项目的源代码文件名汇总
mainwindow.cpp
HEADERS += \ //头文件
mainwindow.h
FORMS += \ //ui文件
mainwindow.ui
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include //包含头文件,QMainWindow接口类
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT//Q_OBJECT宏,允许类中使用信号和槽机制
public:
explicit MainWindow(QWidget *parent = 0);//构造函数
~MainWindow();//析构函数
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),//初始化列表,将构造函数中的值传给父类
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
类名,首字母大写,单词和单词之间首字母大写
函数名 变量名称首字母小写,单词和单词之间首字母大写
注释:ctrl + /
整行的移动:ctrl + shift + ↑或↓
帮助文档:f1
自动对齐:ctrl + i //规范代码缩进
同名.h和.cpp的快速切换:f4
以图片为例:
一个类的帮助文档一定要学会查看。
金科玉律:一般,我们给一个窗口设置组件的时候,直接在该窗口的构造函数中编写。
下面,我们以显示一个按钮为例(来到对应的主界面class MainWindow的构造函数中):
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
/**************begin********************/
//根据帮助文档,我们建立一个按钮:QPushButton类型的对象,首先引入头文件:
//创建一个按钮
QPushButton* btu = new QPushButton;
//显示该按钮:
//btu->show();//由于QPushButton继承自QWeight运行结果会是显示两个界面,而并非按钮显示在该类的界面中
//解决方法:由于QPushButton是QWeight的一个子类,又因为我们这个class本身又是QWeight的一个子类,因此我们将QPushButton类对象设置依赖MainWindow,然后当现实主界面MainWindow的时候i,会将子类按钮组件视为该界面的一部分进行显示。
btu->setParent(this);
//给按钮的内容(文字)进行填充:
btu->setText(tr("Yes"));
/***************************************/
}
MainWindow::~MainWindow()
{
delete ui;
}
至此,我们建立了第一个按钮。
下面,我们建立一个同样的按钮,只不过我们使用不同的方式:
仍然来到主界面类的构造函数中:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//建立按钮新方法:
//直接指定按钮文本内容,并关联当前界面为父类(特点:窗口创建的时候会按照控件的大小创建窗口)
QPushButton* pbt = new QPushButton("确定",this);
//下面,我们给出重置窗口大小的函数:
this->resize(800,500);
//显然我们用resize也可以重置控件的大小:
pbt->resize(200,200);
//我们显示了按钮以后,如何给按钮定位呢?这里就要用到move函数
pbt->move(100,100);//直接指定按钮的左上角起始位置
//设置窗口标题:
this->setWindowTitle(tr("第一个窗口"));
//如果我们不想改变窗口的大小,我们可以设置固定大小的窗口:
this->setFixedSize(400,400);
}
重置窗口大小:
重置控件大小:
在QT中,最大的基类是Object类,在Object下面可以建立很多窗口类,每一个窗口类又可以建立很多元件类,然后可以实现一系列的功能。
每一个类(除了object类)都需要关联自己的父类,即将一个指向自己的指针赋予自己的父类。然后才可以实现一些列层次关系以及析构关系。(设计模式)
例如:在主界面的构造函数中设置一个按钮元件的时候,必须有:
//创建一个按钮
QPushButton* btu = new QPushButton;
//指定其父类(由于这一段代码是在父类的构造函数中编写的,因此父类指针可以用this代替)
btu->setParent(this);指定其父类
//或直接由其构造函数参数形式指定其父类
QPushButton* pbt = new QPushButton("确定",this);
对象树的析构顺序也是自底向上
当一个组件被建立,无论是窗口还是其他组件,都必须在对象生成后直接指定其父类,父类的指定直接决定的该组件的层级。
比如我们的上面的按钮组件,我们在编写的时候指定其父类为当前界面,也即是该按钮从属于当前界面类。而我们的当前界面类又从继承自QMainWindow。如果我们设置按钮的父类不是当前界面,而是当前界面的父类,那么这个按钮组件是不是就相当于跟当前界面属于一个层级了?那我们再输出(调用show函数)组件是不是也会产生两个窗口?
QT精髓,信号与槽
基本框架:
实例:
基本函数:connect(信号发送者,发送的具体的信号(信号函数),信号的接收者,信号的处理(槽函数))
专业语言描述:
信号槽的优点:松散耦合,即信号的发送端和信号的接收端本身是没有关联的,只是通过connect连接,将两端耦合起来。
问题是,我们怎么知道信号发起者有哪些触发信号?这时候我们可以到帮助文档中寻找(关键字signals)
我们仍以QAbstractButton为例,来到QAbstractButton Class的文档中寻找signals:
注意!!你查找的类的文档中若没有对应的signals函数,可以到父类中寻找。
代码示例:
//参数1信号的发送者;参数2发送的信号(注意,这里要的是函数的地址)
//参数3信号的接收者;
connect(pbt,&QPushButton::clicked,this,&QMainWindow::close);
signals函数的问题解决了,但是编写代码的时候又出现了一个问题:信号接收者捕获到信号后,要发起动作的槽函数在哪?这时候我们需要看另一个关键字:Slots(槽)
我们上面代码的信号接收者是this,也即是我们自己建立的一个窗口,Class名为MainWindow,但是问题是,这个Class是我们自己编写的,哪来的槽函数?这时候就可以想到我们上面的一句话:注意!!你查找的类的文档中若没有对应的signals函数,可以到父类中寻找。这句话在寻找Slots的时候也是一样,因此我们去帮助文档中寻找我们this类的父类QWidget的文档内容,然后寻找Slot关键字
MainWindow直接继承的实际上是QMainWindow,但是
然后,我们选择close槽函数:
//参数1信号的发送者;参数2发送的信号(注意,这里要的是函数的地址)
//参数3信号的接收者;
connect(pbt,&QPushButton::clicked,this,&QMainWindow::close);
信号与槽实际上指的的是类中的Signal信号函数和Slots槽函数。信号函数由发起者发起(比如点击信号),然后connect产生作用,通知接收者,然后接收者调用在connect中指定的Slots槽函数,进而做出相应的响应。另外,在Connect中指定发起者的信号函数的以及接收者的槽函数的时候,使用的是函数指针的形式,并且要用拥有这些函数的类或父类去对其进行域解析。具体参见上方代码。
我们在学习前面的信号与槽的时候,connect中signal函数以及slots函数使用的都是类中自带的。那么我们能不能自己去定义这些东西呢?答案当然是可以的。
实际上,信号与槽机制我们还可以进一步简化思想:
发起者调用信号函数触发connect,然后connect通知信号接收者,信号接收者再去调用slots函槽数做出响应。
我们前面定义信号与槽以及connect都是针对QT中的一些组件对象去建立的,并且这些组件对象中都封装了一系列的成熟的函数,并搭配一个成熟的框架(设计模式)
那么,我们如果让两个非组件class也能实现信号与槽的相应功能,我们能不能借助connect这个东西呢?实际上,这是可以的。我们分析一下可以想到,实际上所有的组件class都是Object class的子类(对象树机制),因此,当我们想要让非组件类也实现connect的功能的话,直接让其继承object class即可。
代码示例:
根据上面的思想,我们建立两个class :Teacher and Student,并继承Object
自定义Teacher class和Student class:
然后我们以Teacher为信号发送者,Student 为信号接收者。因此,Teacher需要编写Signal信号函数,Student需要编写Slots槽函数。
//Teacher class
#ifndef TEACHER_H
#define TEACHER_H
#include
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);
signals:
//自定义信号,写到signals下
//信号函数返回值是void只需要声明,不需要实现(也不用在CPP文件中实现)
//可以有参数,可以重载
void hungry();
public slots:
};
#endif // TEACHER_H
//Student class
#ifndef STUDENT_H
#define STUDENT_H
#include
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);
signals:
public slots:
//早期QT版本槽函数必须要写到Public Slots中,高级版本可以直接写到Public下
//函数类型Void,需要声明,也需要实现(为了方便读者查看,我们直接在头文件中实现)
//可以有参数,可以重载
void treat(){
cout<<"老师饿了,学生请客吃饭"<<endl;
}
};
#endif // STUDENT_H
ok,目前为止,我们已经给Teacher和Student编写的对应的Signals函数和Slots函数,也就是说二者具备的通信的基础,下面我们开始让二者进行connect:
我们来到窗口界面Class的构造函数中:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建一个老师对象:
Teacher* t = new Teacher(this);
//创建一个学生对象:
Student* s = new Student(this);
//建立连接:
connect(t,&Teacher::hungry,s,&Student::treat);
std::cout<<"\nfunction is run this"<<std::endl;
}
建立对象后并建立连接,after run the function:
通过运行结果我们发现,连接后,Teacher 对象和Student对象之间并没有产生通信。这是因为,我们虽然在两个对象间成功建立的连接渠道(link channel),但是,我们并没有触发老师饥饿这一个函数,也即是Teacher对象并没有发送Signals信号,问题是,我们如何触发Signal呢?
实际上,触发Signal很简单,只需要发送者调用Signal函数,并且在其调用语句前添加关键字emit即可:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建一个老师对象:
Teacher* t = new Teacher(this);
//创建一个学生对象:
Student* s = new Student(this);
std::cout<<"\nfunction is run this"<<std::endl;
//建立连接:
connect(t,&Teacher::hungry,s,&Student::treat);
//发送者触发signal
emit t->hungry();
}
运行结果:
自定义的信号与槽发生重载的解决:
//建立连接:
connect(t,&Teacher::hungry,s,&Student::treat);
我们去上面的一段代码作为例子。我们可以看得到,在connect中我们的Signals函数和Slots函数存放的是函数的地址,如果我们 的函数存在重载,显然只通过一个地址,由于无法捕获具体参数的个数,根本无法区分具体重载的是哪个函数,那么这个问题我们该如何解决?
仍以自定义connect为例:
直接运行:
显然会报错,那么,我们该如何解决这个问题呢?
下面就要用到函数指针。我们来到主界面class中:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建一个老师对象:
Teacher* t = new Teacher(this);
//创建一个学生对象:
Student* s = new Student(this);
std::cout<<"\nfunction is run this"<<std::endl;
//重点:先将每一个重载类型均用不同的函数指针代替
void(Teacher::* funcpoint00)(QString) = &Teacher::hungry;
void(Student::* funcpoint01)(QString) = &Student::treat;
void(Teacher::* funcpoint02)() = &Teacher::hungry;
void(Student::* funcpoint03)() = &Student::treat;
//建立连接
connect(t,funcpoint02,s,funcpoint03);
connect(t,funcpoint00,s,funcpoint01);
//调用信号函数
emit t->hungry();
emit t->hungry("鸡蛋");
}
1、用函数指针代替函数重载必须在connect建立之前
2、只要存在重载,就必须将每种重载类型均转化为函数指针代替
3、建立连接的时候只能使用函数指针,不允许再使用类中的成员函数名
输出结果:
我们平时使用C++进行文字打印的时候,都是使用标准IO流,在QT中,我们更支持使用QDebug头文件
#include
int main(){
qDebug()<<"Hell0 World";
qDebug()<<"Hello World";
return 0;
}
结果:
Hell0 World
Hello World
显然,这是自带换行的
为什么我们要使用QDebug而不直接使用iostream进行输出呢?因为在QT中,我们的字符串更多使用QString类型,但是普通io流的<<运算符并没有重载这种类型,而QDebug的<<实现了这种重载。因此我们考虑使用相对更为便捷的QDebug进行文字在控制台的打印。
#include
#include
int main(){
QString s = "Hello World";
qDebug()<<s;
return 0;
}
输出结果:
"Hello World"
我们发现,输出的结果自带双引号,那么我们该如何去除双引号呢?
下面直接给出解决方案:
#include
#include
int main(){
QString s = "Hello World";
qDebug()<<s.toUtf8().data();//此处修改
return 0;
}
输出结果:
Hello World
在前面的学习中,我们学习了基本的对象树概念。在QT中,所有的组件实际上都是基于一个object类生成的一个对象树。比如:一个窗口组件继承QWidget class,而一个QWidget class又继承自QObject class。并且我们还可以在一个组件class下生成许多其他的子类,并且一定要用setParent指定其父类,否则无法加入对象树。并且加入对象树以后,当让一个界面进行显示的时候,其子类组件class对象也会在该界面内显示。并且在界面内显示的按钮组件也可以指定其具体的显示位置。当然内容还有很多,都需要你不断编写代码去体会这些东西。
接着我们又学习了QT的精髓:信号与槽机制
容易忽略的点:我们在界面类的构造函数中编写按钮之间的动作的时候,若想在该类中可以使用connect,则必须加入指令:“Q_OBJECT”。否则你是无法使用connect的信号与槽机制的。
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT//Q_OBJECT宏,允许类中使用信号和槽机制------------------看这里----------------------
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
connect是两个Object子类通信的桥梁。两个子类分别为信号发送者和信号接收者。信号发送者的class成员函数中必须存在对应的signals(信号)类型的成员函数声明。而信号接收者class成员函数中必须有对应的public Slots(槽)类型的成员函数。
connect在已经引入:Q_OBJECT的类中让上述两个对象进行通信的基本格式:
在QT组件class中存在很多已经编写好的信号函数和槽函数供我们使用,但是有时候这些官方编写好的信号函数和槽函数并不能满足我们的需求,这时候我们就可以自定义这些Signals函数和Slots函数。并且Signals函数和Slots函数存在一些编写规则:
signal函数:
1、只需要在头文件中给出函数声明,不需给出具体的定义。
2、可以有参数,可以重载。
slot函数:
1、不仅要在头文件中给出声明,还需要给出具体的实现。(转到槽实际上就是自己编写槽函数)
2、可以有参数,可以重载。
目前为止,connect的信号与槽机制的描述看起来优点松散,我们来具体规范一下描述:
1、若在一个class中想让另外两个class进行信号与槽通信,则在该class中必须引入Q_OBJECT宏指令
2、两个进行通信的class必须是Object类的子类
3、信号发送方必须有signal函数声明且无需实现以供调用;信号接收方必须有slots函数声明及定义以供调用。
4、当以上三个条件均具备,可以让通信双方建立连接:
注意:这里的信号函数与槽函数必须指定的是函数地址而非带参的函数调用
//示例:在主界面类让Teacher和Student对象进行通信
connect(t,&Teacher::hungry,s,&Student::treat);
5、连接建立后,并不是说就会直接通信,二者产生通信的必要条件是信号发送者触发了connect中指定的Signals函数:信号发送者调用signal函数并在前有关键字emit。
emit 发送者对象名.signals成员函数
注意:必须先有连接,才能有触发信号发起通信
6、关于信号函数与槽函数的重载问题:
由于我们的connect中指定的信号与槽函数均为函数地址而非函数调用,因此无法使用参数进行重载区分,解决该问题的办法就是使用函数指针:
//必须为每一个重载配备一个函数指针
void(Teacher::* funcpoint00)(QString) = &Teacher::hungry;
void(Student::* funcpoint01)(QString) = &Student::treat;
void(Teacher::* funcpoint02)() = &Teacher::hungry;
void(Student::* funcpoint03)() = &Student::treat;
//建立连接(参数为函数指针)
connect(t,funcpoint02,s,funcpoint03);
connect(t,funcpoint00,s,funcpoint01);
//发起信号
emit t->hungry();
emit t->hungry("鸡蛋");
注意:每一个函数重载都必须配备唯一的函数指针,并且不可缺漏。还有一个重要的点,函数指针的定义必须在connect连接建立之前,并且在connect中信号与槽函数地址处转为填装每个重载函数的函数指针。
前面我们学习了一个class和另一个class通过connect进行信号与槽的连接。即发起者的信号函数每被调用,通过connect就会导致接收者调用槽函数。
实际上,信号与信号之间也可以这样:
connect(对象B,B信号函数地址,对象C,C槽函数地址)
connect(对象A,A信号函数地址,对象B,B信号函数地址)
由于B和C之间已经建立了连接,当B触发信号,C就会调用槽函数响应。而若将A的信号函数与B的信号函数也通过connect进行连接的话,当A触发信号,就会引起B触发信号,B触发信号,就会引起C调用槽函数。
断开信号顾名思义,并且使用很简单,直接调用:
1、信号是可以连接信号的
2、一个信号 可以 连接多个槽函数
3、多个信号 可以 连接同一个槽函数
4、信号和槽函数的参数类型,必须一致
5、信号函数的参数个数是可以多与槽函数的参数个数,但是不能违反规则4