参考文章:(部分内容转载自以下文章)
【Qt】边学边写之Qt教程(零基础)-CSDN博客
QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)-CSDN博客
1.创建Qt项目
1.1 使用向导创建
Qt5基本模块
向导会默认添加一个继承自QMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),QWidget 是所有能看到的窗口或者控件的父类,QMainWindow、QDialog 都继承自它
1.2一个简单的Qt应用程序
1.2.1 main函数中
#include "widget.h"
#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}
main函数
QApplication 就是Qt里边每个应用程序有且仅有一个的应用程序对象
QApplication::exec() 程序的生命循环、消息循环 ,当作以下形式
QApplication应用程序类
- 管理图形用户界面应用程序的控制流和主要设置。
- 是Qt生命,一个程序要确保一直运行,就肯定至少得有一个循环,这就是Qt主消息循环,在其中完成来自窗口系统和其它资源的所有事件消息处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
- 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,不论这个应用程序在同一时刻有多少个窗口。
a.exec()
- 程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
1.2.4 QtCreator常用快捷键
可以在工具>选项>环境>键盘中进行快捷键修改
运行 ctrl +R
编译 ctrl +B
帮助文档 F1 ,点击F1两次跳到帮助界面
跳到符号定义 F2 或者ctrl + 鼠标点击
注释 ctrl+/
字体缩放 ctrl + 鼠标滚轮
整行移动代码 ctrl + shift + ↑或↓
自动对齐 ctrl + i
同名之间的.h和.cpp文件跳转 F4
2、Qt 按键小程序
2.1 按钮的创建和父子关系
- 没有建立父子关系,显示的是顶层窗口
#include <QPushButton>//添加按钮QPushButton btu;btu.setText("按钮1");//将按钮显示出来btu.show();
- 建立父子关系,两种方式
- 利用指针的形式创建
//第二种创建QPushButton * btn2 = new QPushButton("按键1",this);//重新指定窗口大小this->resize(600,400);//设置窗口标题this->setWindowTitle("第一个项目");//限制窗口大小this->setFixedSize(600,400);
2.2 Qt窗口坐标体系
对于嵌套窗口,其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。
2.3 Qt常用API函数
move 移动窗口到父窗口某个坐标resize 重新设置窗口的大小setFixedSize 设置窗口的固定大小setWindowTitle 设置窗口标题setGeometry 同时设置窗口位置和大小,相当于move和resize的结合体
上面这几个函数都是QWidget类的成员函数,按钮其实是个窗口,而窗口类最终都是继承自QWidget类。
QWidget > QAbstractButton > QPushButton
2.4 对象树模型
QObject是Qt里边绝大部分类的根类
1.QObject对象之间是以对象树的形式组织起来的。
当两个QObject(或子类)的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children(孩子)的list(列表)中。
当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里是说父对象和子对象,不要理解成父类和子类)
2.QWidget是能够在屏幕上显示的一切组件的父类
QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。我们向某个窗口中添加了一个按钮或者其他控件(建立父子关系),当用户关闭这个窗口的时候,该窗口就会被析构,之前添加到他上边的按钮和其他控件也会被一同析构。这个结果也是我们开发人员所期望的。
当然,我们也可以手动删除子对象。当子对象析构的时候会发出一个信号destroyed,父对象收到这个信号之后就会从children列表中将它剔除。比如,当我们删除了一个按钮时,其所在的主窗口会自动将该按钮从其子对象列表(children)中删除,并且自动调整屏幕显示,按钮在屏幕上消失。当这个窗口析构的时候,children列表里边已经没有这个按钮子对象,所以我们手动删除也不会引起程序错误。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
3.对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
4.任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
Qwidget是能够再屏幕上显示的一切组件的父类。
- 程序崩溃代码示例:
{QPushButton quit("Quit");QWidget window;quit.setParent(&window);
}
在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
3、信号和槽机制
信号:各种事件 (signal)
槽: 响应信号的动作 (slot)
类似于单片机中的中断信号和中断函数
3.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了,我们看下面的代码
QPushButton * quitBtn = new QPushButton("关闭窗口",this);connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
# 第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:
connect(sender, signal, receiver, slot);
参数解释:
- sender:信号发送者
- signal:信号
- receiver:信号接收者
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
这里要注意的是connect的四个参数都是指针,信号和槽是函数指针,使用connect的时候保留&符号。
3.2 自定义信号和槽
Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。
3.2.1 自定义信号使用条件
函数声明在类头文件的signals域下
没有返回值,void类型的函数
只有函数声明,没有实现定义
可以有参数,可以重载
通过emit关键字来触发信号,形式:emit object->sig(参数);
3.2.2 自定义槽函数使用条件
qt4 必须声明在类头文件的 private/public/protected slots域下面,qt5之后可以声明再类的任何位置,同时还可以是静态的成员函数,全局函数,lambda表达式
没有返回值,void类型的函数
不仅有声明,还得要有实现
可以有参数,也可以重载
3.2.3 使用自定义信号和槽
【定义场景】:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
- 首先定义一个学生类Student和老师类Teacher:
-
- 老师类中声明信号 饿了 hungry
- 学生类中声明槽 请客treat
- 自定义槽函数 实现
#include<QDebug>
void Student::treat()
{qDebug() << "Student treat teacher";
}
- 在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
- 在窗口中连接信号槽
3.2.4 解决使用自定义信号和槽的重载问题
- 问题:如果有两个重名的自定义信号或者槽,在直接使用connect连接时就会报错,所以需要解决重载问题。
- 解决方法:
//因为函数发生了重载,所以解决
* 1 使用函数指针赋值,让编译器挑选符合类型的函数
* 2 使用static_cast 强制转换,也是让编译器自动挑选符合类型的函数
1.重载有参函数,使用函数指针赋值:
void(Teacher::*teacher_qstring)(QString) = &Teacher::hungry;
void(Student::*student_qstring)(QString) = &Student::treat;
connect(pTeacher,teacher_qstring,pStudent,student_qstring);
2.使用强制类型转换解决重载问题:
//也可以使用static_cast静态转换挑选我们要的函数
connect(pTeacher,static_cast<void(Teacher:: *)(QString)>(&Teacher:: hungry),pStudent,static_cast<void(Student:: *)(QString)>(& Student::treat)
);
3.3 信号和槽的拓展
- 一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。
- 多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。
- 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。
- 信号和槽可以断开连接
可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
connect(pTeacher,teacher_qstring,pStudent,student_qstring);//建立连接disconnect(pTeacher,teacher_qstring,pStudent,student_qstring);//断开连接
- 信号和槽的关系信号和槽函数参数类型和个数必须同时满足两个条件
1) 信号函数的参数个数必须大于等于槽函数的参数个数
2) 信号函数的参数类型和槽函数的参数类型必须一一对应
hungry(QString) -> treat() ok
hungry(QString) -> treat(int) 编译出错
hungry(QString,int) -> treat(int) 编译出错
hungry(int,String) -> treat(int) ok