Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。
Qt的发展史
1991年 Qt最早由奇趣科技开发
1996年 进入商业领域,它也是目前流行的Linux桌面环境KDE的基础
2008年 奇趣科技被诺基亚公司收购,Qt称为诺基亚旗下的编程语言
2012年 Qt又被Digia公司收购
2014年4月 跨平台的集成开发环境Qt Creator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt实现了对iOS、Android、WP等各平台的全面支持。
支持的平台
Windows – XP、Vista、Win7、Win8、Win2008、Win10
Uinux/X11 – Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/OS、和其他很多X11平台
Macintosh – Mac OS X
Embedded – 有帧缓冲支持的嵌入式Linux平台,Windows CE
Qt的下载与安装
下载地址:
http://www.qt.io/download-open-source/
Linux Host
•
Qt 5.5.0 for Linux 32-bit (535 MB) (info)
•
Qt 5.5.0 for Linux 64-bit (532 MB) (info)
•
Qt 5.5.0 for Android (Linux 64-bit, 605 MB) (info)
•
Qt 5.5.0 for Android (Linux 32-bit, 608 MB) (info)
OS X Host
•
Qt 5.5.0 for Mac (588 MB) (info)
•
Qt 5.5.0 for Android (Mac, 652 MB) (info)
•
Qt 5.5.0 for Android and iOS (Mac, 1.7 GB) (info)
Windows Host
•
Qt 5.5.0 for Windows 64-bit (VS 2013, 650 MB) (info)
•
Qt 5.5.0 for Windows 32-bit (VS 2013, 633 MB) (info)
•
Qt 5.5.0 for Windows 32-bit (VS 2012, 587 MB) (info)
•
Qt 5.5.0 for Windows 32-bit (VS 2010, 585 MB) (info)
•
Qt 5.5.0 for Windows 32-bit (MinGW 4.9.2, 959 MB) (info)
•
Qt 5.5.0 for Android (Windows 32-bit, 1.0 GB) (info)
•
Qt 5.5.0 for Windows RT 32-bit (621 MB) (info)
安装
默认安装(建议组件全部选中)
•
Qt对不同的平台提供了不同版本的安装包,可根据实际情况自行下载安装,本文档使用qt-opensource-windows-x86-mingw482_opengl-5.3.1 版本进行讲解
Qt的优点
跨平台,几乎支持所有的平台
接口简单,容易上手,学习QT框架对学习其他框架有参考意义。
一定程度上简化了内存回收机制
开发效率高,能够快速的构建应用程序。
有很好的社区氛围,市场份额在缓慢上升。
可以进行嵌入式开发。
成功案例
Linux桌面环境KDE
VPS Office 办公软件
Skype 网络电话
Google Earth 谷歌地图
VLC多媒体播放器
VirtualBox虚拟机软件
.pro文件
在使用Qt向导生成的应用程序.pro文件格式如下:
QT += core gui
//模块的名字
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets//大于qt4版本才加入 widgets模块
TARGET = test
//应用程序名\生成的可执行文件的名称
TEMPLATE = app
//生成的makefile的模板类型
//源文件
SOURCES += main.cpp\
mainwindow.cpp
//头文件
HEADERS += mainwindow.h
//窗口设计文件
FORMS += mainwindow.ui
.pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。.pro文件的写法如下:
注释
从“#”开始,到这一行结束。
模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:TEMPLATE = app
app -建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用。
lib - 建立一个库的makefile。
vcapp - 建立一个应用程序的VisualStudio项目文件。
vclib - 建立一个库的VisualStudio项目文件。
subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。
#指定生成的应用程序名:
TARGET = QtDemo
#工程中包含的头文件
HEADERS += include/painter.h
#工程中包含的.ui设计文件
FORMS += forms/painter.ui
#工程中包含的源文件
SOURCES += sources/main.cpp sources/painter.cpp
#工程中包含的资源文件
RESOURCES += qrc/painter.qrc
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这条语句的含义是,如果QT_MAJOR_VERSION大于4(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写。
#配置信息
CONFIG用来告诉qmake关于应用程序的配置信息。
CONFIG += c++11
//使用c++11的特性
在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。
一个最简单的Qt应用程序
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.show();
return a.exec();
}
解释:
Qt头文件没有.h后缀
Qt一个类对应一个头文件,类名就是头文件名
QApplication应用程序类
管理图形用户界面应用程序的控制流和主要设置。
是Qt的整个后台管理的命脉它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口。
a.exec()
程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
信号和槽机制
信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现,并不是 GoF(四人组《设计模式》) 经典的观察者模式的实现方式。)
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
参数:
sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收到信号之后所需要调用的函数
信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
如果信号槽不符合,或者根本找不到这个信号或者槽函数,比如我们改成:
connect(&button, &QPushButton::clicked, &QApplication::quit2);
由于 QApplication 没有 quit2 这样的函数,因此在编译时会有编译错误:
'quit2' is not a member of QApplication
这样,使用成员函数指针我们就不会担心在编写信号槽的时候出现函数错误。
Qt4 的书写方式:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *button = new QPushButton("Quit");
connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
button->show();
return a.exec();
}
这里使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
自定义信号槽
使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。
首先定义一个学生类和老师类:
老师类中声明信号 饿了 hungry
signals:
void hungury();
学生类中声明槽 请客 treat
public slots:
void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void MyWidget::ClassIsOver()
{
//发送信号
emit teacher->hungury();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
void Student::eat()
{
qDebug() << "该吃饭了!";
}
在窗口中连接信号槽
teacher = new Teacher(this);
student = new Student(this);
connect(teacher,&Teacher::hungury,student,&Student::treat);
并且调用下课函数,测试打印出 “该吃饭了”
自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽
void hungury(QString name); 自定义信号
void treat(QString name ); 自定义槽
但是由于有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来指向函数地址, 然后在做连接
void (Teacher:: * teacherSingal)(QString) = &Teacher::hungury;
void (Student:: * studentSlot)(QString) = &Student::treat;
connect(teacher,teacherSingal,student,studentSlot);
自定义信号槽需要注意的事项
发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
使用 emit 在恰当的位置发送信号;
使用QObject::connect()函数连接信号和槽。
任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
信号槽的更多用法
一个信号可以和多个槽相连
如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
多个信号可以连接到一个槽
只要任意一个信号发出,这个槽就会被调用。
一个信号可以连接到另外的一个信号
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
槽可以被取消链接
这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。
使用Lambda 表达式
在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理。
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
[函数对象参数](操作符重载函数参数)mutable或exception ->返回值{函数体}
① 函数对象参数;
[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
空。没有使用任何函数对象参数。
=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
this。函数体内可以使用Lambda所在类中的成员变量。
a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
&a。将a按引用进行传递。
a, &b。将a按值进行传递,b按引用进行传递。
=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
② 操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
③ 可修改标示符;
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
④ 错误抛出标示符;
exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)
⑤ 函数返回值;
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
⑥ 是函数体;
{},标识函数的实现,这部分不能省略,但函数体可以为空。