目录
1.Qt概述
1.1 什么是Qt
2.2 手动创建
2.3 pro文件
2.4 一个最简单的Qt应用程序
3 第一个Qt小程序
3.1 按钮的创建
3.2 对象模型(对象树)
3.3 Qt窗口坐标体系
4 信号和槽机制
4.1 系统自带的信号和槽
4.2 自定义信号和槽
4.3信号槽的拓展
4.4 Qt4版本的信号槽写法
4.5 Lambda表达式
1.Qt概述
1.1 什么是Qt
Qt是一个Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程
系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。
2.2 手动创建
添加一个空项目
选择【choose】进行下一步。设置项目名称和路径 —> 选择编译套件 --> 修改类信息 --> 完成(步骤同上),生成一个空项目。在空项目中添加文件:在项目名称上单击鼠标右键弹出右键菜单,选择【添加新文件】
弹出新建文件对话框
在此对话框中选择要添加的类或者文件,根据向导完成文件的添加。
2.3 pro文件
在使用Qt向导生成的应用程序.pro文件格式如下:
QT += core gui //包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于Qt4版本 才包含widget模块
TARGET = QtFirst //应用程序名 生成的.exe程序名称
TEMPLATE = app //模板类型 应用程序模板
SOURCES += main.cpp\ //源文件
mywidget.cpp
HEADERS += mywidget.h //头文件
.pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。.pro文件的写法如下:
- 注释从“#”开始,到这一行结束。
模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:TEMPLATE = app。
2.app-建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用
3. lib - 建立一个库的makefile
4. vcapp - 建立一个应用程序的VisualStudio项目文件。
5.vclib - 建立一个库的Visual Studio项目文件
subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。
#指定生成的应用程序名:
TARGET = QtDemo
#工程中包含的头文件
HEADERS += include/painter.h
#工程中包含的.ui设计文件
FORMS += forms/painter.ui
#工程中包含的源文件
SOURCES += sources/main.cpp sources
#工程中包含的资源文件
RESOURCES += qrc/painter.qrc
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这条语句的含义是,如果QT_MAJOR_VERSION大于4(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写
4#配置信息
CONFIG用来告诉qmake关于应用程序的配置信息。CONFIG += c++11 //使用c++11的特性。在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。
2.4 一个最简单的Qt应用程序
main入口函数中
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
解释:
- Qt系统提供的标准类名声明头文件没有.h后缀
- Qt一个类对应一个头文件,类名就是头文件名
- QApplication应用程序类
3.1 管理图形用户界面应用程序的控制流和主要设置
3.2是Qt的整个后台管理的命脉它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理.
3.3 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口。
- a.exec()
程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
3 第一个Qt小程序
3.1 按钮的创建
在Qt程序中,最常用的控件之一就是按钮了,首先我们来看下如何创建一个按钮
mywidget.cpp
#include "mywidget.h"
#include<QPushButton>
#include"mypushbutton.h"
#include<QtDebug>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
{
QPushButton *btn=new QPushButton();
// btn->show(); //show 使用顶层的方式弹出。
//如果想显示到当前的窗口中,需要做依赖
btn->setParent(this);
btn->setText("德玛西亚");
//按钮2
QPushButton *btn2=new QPushButton("第二个",this);
//移动btn2
btn2->move(100,100);
//如何重置窗口的大小。按钮同样可以重置大小。
// resize(1600,1400);
// btn->resize(40,80);
//指定窗口的标题
setWindowTitle("第一个窗口");
//设置窗口固定的大小。
setFixedSize(1000,800);
MyPushButton *mybtn=new MyPushButton();
mybtn->setParent(this);
mybtn->setText("this is my button");
mybtn ->move(200,200);
//connect(信号发送者,发送信号,信号的接受这,处理槽函数)有点松散耦合的作用
connect(mybtn,&QPushButton::clicked,this,&QWidget::close);
}
MyWidget::~MyWidget()
{
qDebug()<<"Mywidget()'s destrction";
}
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(QWidget *parent = 0);
~MyWidget();
};
#endif // MYWIDGET_H
main.cpp
#include "mywidget.h"
#include <QApplication> //Qapplication 应用程序类的头文件。
//程序的入口, int argc 命令行数量,命令行变量的数组。
int main(int argc, char *argv[])
{
//应用程序对象有且仅有一个。
QApplication a(argc, argv);
//通过--窗口类,实例化对象。
MyWidget w;
//窗口是不会默认弹出的,需要使用show进行显示。
w.show();
//进入一个消息循环的机制。 阻塞的功能。
return a.exec();
}
上面代码中,一个按钮其实就是一个QPushButton类下的对象,如果只是创建出对象,是无法显示到窗口中的,所以我们需要依赖一个父窗口,也就是指定一个父亲利用setParent函数即可,如果想设置按钮上显示的文字利用setText,移动按钮位置用move对于窗口而言,我们可以修改左上角窗口的标题setWindowTitle,重新指定窗口大小:resize,或者设置固定的窗口大小setFixedSize。
3.2 对象模型(对象树)
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的
QObject是以对象树的形式组织起来的。
1.当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
2.当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
3.QWidget是能够在屏幕上显示的一切组件的父类
QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
4.当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示
Qt 引入对象树的概念,在一定程度上解决了内存问题。
当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下下面的代码片段。
{
QWidget window;
QPushButton quit("Quit", &window);
}
作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
但是,如果我们使用下面的代码:
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
3.3 Qt窗口坐标体系
坐标体系:以左上角为原点(0,0),X向右增加,Y向下增加。
对于嵌套窗口,其坐标是相对于父窗口来说的。
4 信号和槽机制
信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
4.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?其实无法两行代码就可以搞定了,我们看下面的代码。
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式。
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
参数解释:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
那么系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个。
这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。
4.2 自定义信号和槽
使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。 下面我们看看使用 Qt 的信号槽:
首先定义一个学生类和老师类:老师类中声明信号 饿了 hungry,signals:
teacher.h
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = 0);
//自定义的信号需要写到sinal 中,返回值是void,只需要声明,不需要实现,可以有参数,可以发生重载的。
signals:
void hungry();
void hungry(QString foodName);
public slots:
};
#endif // TEACHER_H
teacher.cpp
#include "teacher.h"
Teacher::Teacher(QObject *parent) : QObject(parent)
{
}
student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
public:
//禁止隐式类型转换的功能,explicit
explicit Student(QObject *parent = 0);
signals:
public slots:
//自定义槽函数的地方 Q.0 5 可以写成全局,可以写在public 作用或者lambda 表达式
void treat();
void treat(QString foodName);
};
#endif // STUDENT_H
student.cpp
#include "student.h"
#include<QDebug>
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student:: treat(){
qDebug()<<"请老师吃饭";
}
void Student:: treat(QString foodName){
//将QString 转成char* footName.toUft8().data();
qDebug()<<"请老师吃饭,老师要吃: "<<foodName.toUtf8().data();
}
自定义信号槽需要注意的事项:
- 发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
- 信号和槽函数返回值是 void
- 信号只需要声明,不需要实现
- 槽函数需要声明也需要实现
- 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
- 使用 emit 在恰当的位置发送信号;
- 使用connect()函数连接信号和槽。
- 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。
4.3信号槽的拓展
1.一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的
2.多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用
3. 一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别
4.槽可以被取消链接。
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
5.信号槽可以断开
利用disconnect关键字是可以断开信号槽的
- 使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。后面我们会详细介绍什么是Lambda表达式。
4.4 Qt4版本的信号槽写法
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));
这里使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。Qt5在语法上完全兼容Qt4,而反之是不可以的。
4.5 Lambda表达式
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
[capture](parameters) mutable ->return-type
{
statement
}
[函数对象参数](操作符重载函数参数)mutable ->返回值{函数体}
① 函数对象参数;
[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
1.空。没有使用任何函数对象参数。
2.=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3.&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)
4.this。函数体内可以使用Lambda所在类中的成员变量
5.a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6.&a。将a按引用进行传递、
7.a, &b。将a按值进行传递,b按引用进行传递
8.=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递
9.&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递
② 操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递.
可修改标示符;
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身。
QPushButton * myBtn = new QPushButton (this);
QPushButton * myBtn2 = new QPushButton (this);
myBtn2->move(100,100);
int m = 10;
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 20; qDebug() << m; });
connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
qDebug() << m;
④ 函数返回值;
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
⑤ 是函数体;
{},标识函数的实现,这部分不能省略,但函数体可以为空。
总体介绍
- Qt简介
- 发展史
- 1991 奇趣科技
- 版本
- 商业版
- 开源版
- 优点
- 跨平台
- 接口简单 容易上手
- 一定程度上简化了内存回收机制
- 。。。
- 成功案例
- Linux 桌面环境 KDE
- WPS
- 谷歌地图
- VLC
- 虚拟机软件
- …
- Qt项目创建
- 项目名称 不能有空格和中文
- 项目路径不能有中文路径
- 创建窗口三大基类
- QWidget
- QMainWindow
- QDialog
- Main函数中
- QApplication a 应用程序对象 在Qt中 有且仅有一个
- MyWidget w 窗口对象
- w.show() 函数显示窗口
- return a.exec()进入消息循环机制,阻塞功能
- .pro文件
- QT += core gui //Qt包含的模块
- greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于4版本 加入 widgets 模块
- TARGET = 01_QtFirst //生成.exe程序的名称
- TEMPLATE = app //模板 应用程序模板
- SOURCES += main.cpp\ //源文件
- mywidget.cpp
- HEADERS += mywidget.h //头文件
- QPushButton按钮
- QPushButton * btn = new QPushButton; 创建按钮对象
- btn - >setParent(this) 设置父亲
- 设置显示文本 setText
- 移动 move
- 重置窗口大小 resize
- 设置固定窗口大小 setFixedSize
- 设置窗口标题 setWindowTitle
- Qt中的对象树
- 一定程度上简化了内存回收机制
- 当创建的对象 指定的父亲是由QObject或者Object派生的类时候,这个对象被加载到对象树上,当窗口关闭掉时候,树上的对象也都会被释放掉
- Qt中的坐标系
- x以右侧为正
- y以下侧为正
- 左上角是 0,0点
- Qt中信号和槽基本使用
- 需求:点击按钮关闭窗口
- 连接 connect ( 信号的发送者,发送的信号,信号的接受者,处理的槽函数)
connect( myBtn , &MyPushButton::clicked ,this, &MyWidget::close);
- 测试
- 自定义信号和槽
- 自定义信号 写在 signals下
- 返回值是void
- 只需要声明 不需要实现
- 可以有参数 可以发生重载
- 自定义槽函数 写在 public 或者全局函数 或者 public slot 或者lambda
- 返回值是void
- 需要声明 也需要有实现
- 可以有参数 可以发生重载
- 当信号和槽发生重载时候,需要利用函数指针明确指出函数地址
- void(Teacher:: *teacherSignal)(QString) = &Teacher::hungry;
- 将QString 转为 char *
- .toUtf8()转为 QByteArray数据类型
- .data() 转为 char * 类型
- 信号和槽拓展
- 信号是可以连接信号
- 可以断开信号和槽 disconnect
- 一个信号可以响应多个槽函数
- 多个信号可以连接同一个槽函数
- 信号和槽函数的参数类型 必须一一对应,信号的参数个数 可以多余槽函数的参数个数,反之不可以 , 参数类型要一一对应
- Qt4版本信号和槽写法
- 优势 :参数直观
- 劣势 :参数类型不做匹配检测
- Qt4本质 SIGNAL("hungry(int)")SLOT("treat(QString)")
- Lambda表达式
- []()mutable -> type {}组成
- []中可以加 = & a &a … 推荐 使用 =
- () 形参列表
- {} 实现体
- mutable可以修改按值传递进来的拷贝
- -> type 代表lambda表达式返回值类型