Qt初学笔记-1

Qt介绍

1、Qt介绍

Qt5采用了新的模块化库,使得平台移植更简单,Qt5将所有功能模块分成了3个部分:基本模块(Qt Essentials)、扩展模块(Qt Add-Ons)和开发工具(Qt Tools)。

Qt基本模块定义了适应于所有平台的基础功能,所有Qt应用程序都需要使用该模块中提供的功能,基本模块的基础是Qt Core模块,其它所有模块都依赖于该模块。Qt保证,在Qt的整个声明周期内,这部分模块均会保持源代码甚至二进制级别的兼容性。另外,如果某个功能即便是所有平台都能够支持,但是仅仅为了满足某个特定的功能或目的,那么这个功能也不会进入基本模块。

1、1Qt基本模块

Qt的基本模块简介:

  • Qt Core:Qt 5的核心内库,其它各个模块都建立在Core模块上,相对于Qt4,Qt5增加了JSON支持,并且将对XML的支持移入到Core模块,不再是独立的XML功能。
  • Qt GUI:图形用户界面开发的最基础的类库,包括各种交互事件等,同时这个模块还包括了OpenGL的内容。
  • Qt Multimedia:提供对视频、音频、无线电以及摄像头的支持。
  • Qt Mutimedia Widgets:提供基于部件的多媒体功能的支持。
  • Qt Network:提供网络编程库。
  • Qt QML:提供对QML和JavaScript语言的支持。
  • Qt Quik:提供一个用于创建高度动画效果的应用程序的声明式框架,该框架建立在QML和JavaScript的基础之上。
  • Qt Quick Controls:提供一组基于Qt Quick的UI控件,用于创建经典的桌面风格的用户界面。
  • Qt Quik Dialogs:提供一组适用于Qt Quick应用程序的系统对话框。
  • Qt Quick Layouts:提供一组适用于Qt Quick的项目布局 。
  • Qt SQL:提供对关系数据库SQL的支持。
  • Qt Test:提供Qt 应用程序和类库使用的单元测试工具。
  • Qt Widgets:提供C++用户界面部件,是对Qt GUI的扩展。
1、2Qt扩展模块

Qt扩展模块是针对某种特殊目的的额外模块,这些模块在某个或某些特定平台可用,扩展模块不一定在所有平台的发布版本中都有提供。与基本模块不同,扩展模块的版本兼容性是模块自己指定的。

2、图形界面库的架构

Qt 5重构了Qt GUI模块,它不再是一个大而全的图形界面类库,而是为各种图形用户界面组件提供一般的处理,包括窗口系统集成、事件处理、OpenGL和OpenGl ES的集成、2D绘图、-基本图像、字体和文本等。
Qt初学笔记-1_第1张图片
在Qt 5支持的平台之上是平台抽象层(它曾经是Qt4的一个开源项目),QPA层上所有深色背景组块都是Qt GUI模块的内容。它们被分为两类,一类是以OpenGL为核心,一类是以辅助访问和输入方式为基础的一般图形显示类。

3、Qt 5架构主要特点

主要特点有:

  • 把全部的Qt接口迁移到Qt平台抽象层上,使Qt能更容易地移植到另外的系统和设备,上。QPA的出现从根本上改变了Qt移植到其它环境的复杂度,由此创造一个更简洁的架构。
  • 重新设计了图形堆栈,与Qt4相比提高了性能。Qt5为Qt Quick引入了全新的图形架构,使用了基于OpenGL的场景图,其最低需求是Open GL(ES)2.0。新版本的Qt GUI包含了一组QOpenGL类,用来替代旧版本的QGL类,还引入了一个比QApplication更轻量级的新类QGuiApplication和一个处理屏幕上顶层窗口的类QWindow,QPainter比以前支持更少的后端,限制使用一个光栅后端(Raster Backend)来绘制屏幕、像素和图像,一个OpenGL后端提供GL接口以及一个提供PDF生成打印的后端。
  • 更加灵活的模块结构,满足桌面和移动的融合,按需添加或删除特定的模块。模块化使Qt开发更容易、更独立地推进不同的部分,这对于Qt 5的稳定以及保持二进制兼容性具有很重要的意义。模块化也简化了第三方模块到Q t集成。

在没有开发工具帮助的情况下,设计一个良好的UI是非常耗时的,Qt Quick相对于原生的C/C++方式来说,已经大大降低了工作复杂度,而Qt 5中全新的Qt Quick Controls和Qt Quick Layouts使得设计用户界面变得更加简单。

3、1Qt5部分新增功能

Qt5中可以使用常用的Sensors和Qt Positioning模块在Qt应用中实现对传感器和定位的支持。

QWebEngine是一个Web内容渲染引擎,它基于Chromium,对标准的Web技术具有广泛的支持。在Qt Quick和传统的Widget编程中都可以使用Qt WebEngine模块,该模块为Qt 5带来最新的HTML5的支持,包含CSS的滤镜、动画、视频和Canvas等。

Qt5将库划分为特定领域的库和模块,这意味着应用程序可以选择自己需要的库进行编译、使用和部署。

Qt可以通过第三方供应商提供的地图数据就可以实现位置和地图信息,Qt Location模块提供了接口和必要的端口,可以从流行的第三方地图解决方案中获取地图数据。

Qt平台抽象层是一个插件架构,它允许Qt动态加载一个窗口系统,并基于现在运行的系统进行集成,这样做有如下优点:集中窗口系统集成代码库到一些类中,这些类多平台共享;简化移植Qt到新平台所需要的工作;在Qt中删除窗口系统依赖,使得多个窗口系统中可以运行同一个Qt二进制文件成为可能。
Qt4已经在C++程序中支持处理多点触控输入,而Qt5将这种支持扩展到了Qt Quick,并且涵盖了触控点的所有信息,包括触控位置、压力和速度等。

Qt5对鼠标事件处理进行了加强,在Qt Quick程序中可以对鼠标事件进行更多的控制。QML文档中的鼠标区域可以传递鼠标滚轮和单击事件,也可以忽略传递的事件。

3、2Qt core的主要更新

主要更新:

  • 作为Qt最基本的信号槽机制,Qt5有了自诞生依赖最大的改变。在Qt5之前的版本中,信号槽利用宏实现,虽然语法简单,但是这种实现的缺点是没有编译期类型检查,只能在运行时发现问题。Qt5巧妙地利用C++的函数指针,为信号槽连接提供了编译期类型检查。不仅如此,Qt5还减少了信号槽的限制,在Qt4中,只有类的非静态成员函数才允许作为槽函数,但在Qt5中,所有函数,包括全局函数、静态函数甚至匿名函数都可以作为槽函数。
  • 包含了一个JSON解析器,随着Web2.0的兴起,JSON正在取代XML成为新一代数据交换格式。Qt5直接内置对JSON的支持。
  • 引入了插件形式和文件内容的Mime类型识别。
  • 引入了一个完整的兼容Perl的正则表达式引擎。
  • 增加了对C++11的支持,同时保证Qt也能在较旧的编译器上运行。在Qt5中,可以直接将信号与一个普通的Lambda表达式连接起来。
3、3网络和联通性

Qt5对IPv6和双模式网络提供了更好的支持。大多数使用主机名和网络层协议的应用现在都可以添加IPv6的支持,这些应用可以选择同时接收IPv4和IPv6连接或者只绑定其中的一个连接类型。

Qt5提供了更好的方式来处理基于TCP套接字的连接和SSL证书:在连接之前绑定一个TCP套接字到一个IP地址上;验证SSL证书链;访问原有系统。
许多涉及处理机密或者重要数据的应用程序往往要考虑到使用严格的客户端身份验证。Qt5支持不透明私钥,允许应用程序从设备(如PKCS#11加密狗)中读取私钥,从而实现严格的客户端身份验证。

Qt5新添了Qt Bluetooth和Qt NFC两个模块,使开发者可以在应用程序中利用这些机制关联和共享信息。

4、Qt5与Qt4的兼容性

Qt 5的发布就是为了让这个C++框架更加高效和易于使用,所有Qt5中使用了很多新的API取代了旧有的。很多内容已在前面介绍过了,这里再次强调以下:

  • 模块化的代码库,只需要关心自己程序中使用的基本模块和扩展模块。
  • 特定平台编码:Symbian和Meego特定的代码已经从代码库移除了。
  • 平台定义:平台特定的代码需要使用预处理宏,Qt5使用Q_OS_*宏代替了以前版本中的Q_WS_*宏。
  • Widgets:现在所有的部件都在独立的Qt Widget模块中,以前它们是Qt Gui模块的一部分。
  • Qt Quick:旧的Qt Quick版本(1.0)现在成了独立的QtDeclarative扩展模块的一部分,它的存在只是为了兼容。建议使用新的Qt Quick(2.0)来进行开发。
  • Qt3Support:这个模块从Qt 5中移除了。
  • Qt WebKit:这个模块被分成了Qt WebKit和Qt WebKit Widgets两个模块,Qt 5.6中该模块已被移除,需要使用Qt WebEngine来代替。
  • 多媒体:在Qt 5中多媒体通过Qt Multimedia模块来提供。Phone框架不再是Qt的一部分,不过它会继续由Phone开发者进行维护并且支持Qt5.0。
    在Qt5,QML和Qt Quick成为了Qt的核心之一,但Qt的本质还是C++。
    Qt5增加了QML这种应用程序开发方式,由于QML的简洁性,QML和Qt Quick2更适合快速开发动态界面程序,解决了QWidgets这种为大量以静态图形为主要显示单元的用户界面设计的部件所不擅长的。
    Qt5的源代码文件必须是UTF-8编码。

5、信号和槽

5、1旧的信号和槽语法

connect(sender,SIGNAL(valueChanged(QString,QString)),receiver,SLOT(showValue(QString)):这是Qt4关联信号和槽的方法,SIGNAL和SLOT两个宏实际是将其参数转换成相应的字符串(必须写明函数参数类型),在编译之前,Qt的moc工具从源代码中提取所需要的元数据,形成了一张由使用了signals和slots修饰的所有函数组成的字符串表。connect函数将与信号关联起来的槽的字符串同这张字符串表中信息进行比较匹配,从而在发出信号时知道需要调用哪个槽函数。这种实现有下面两个问题:

  • 没有编译期检查。由于信号和槽都会被SIGNAL和SLOT宏处理成字符串,字符串的对比是在运行时完成的,并且失去了类型信息。所以,编写Qt4程序时,可能会出现编译通过但是运行时原本应该调用的槽函数却没有执行的情况。所以,编译器不能给出任何错误信息(函数名字写错也不会报错),只能看在运行时有没有警告。
  • 无法使用相容类型的参数。由于connect函数使用的是字符串比对,所以槽函数的参数类型的名字必须和信号完全一致。也必须与头文件中的类型一致。这里的“一致”是严格的字符串意义上的相同,因此,那些使用了typedef或者namespace的类型,即便实际类型相同,依然可能由于字符串名字不一样而不能正常工作。
5、2新的信号和槽语法

connect(sender,&Sender::valueChanged,receiver,&Receiver::showValue);其中,Sender是发出信号的sender对象的类型,Receiver是接收信号的receiver对象的类型(如果使用的信号函数和槽函数是继承父类没有重写的,可使用父类类型,因为引用的是同一个函数)。Qt4的关联方式在Qt5程序依然可用,不过新的语法有以下几个优点:

  • 支持编译期检查,Qt5新的关联语法可以在编译时进行检查,信号或槽的拼写错误、槽函数参数数目多于信号的参数数目等错误在编译时就能够被发现。
  • 支持相容参数类型的自动转换。使用新语法不仅支持使用typedef或者命名空间对应的参数类型,还支持隐式类型转换。例如,当信号参数类型是QString,而槽函数对应参数类型是QVariant时,那么在进行信号槽的连接时,QString将被自动挡转换成QVariant,这是因为QVariant有一个可以使用QString的隐式构造函数。
  • 允许连接任意函数。在Qt4中,槽函数只能使用slots关键字修饰的成员函数,而新的语法则通过函数指针直接调用函数,任意成员函数,静态函数(可以是任意类的静态函数)或者Lambda表达式都可以作为槽进行关联(以前的信号槽语法是不受private限制的,作为槽连接时,SLOT无视private修饰(因为仅作为字符串连接),而新语法无法获取私有函数指针,编译时就会有警报,因而更安全)。
    除了可以将普通成员函数作为槽函数,Qt5新语法还允许使用任意类的静态函数,例如 : connect(dlg,&StringDialog::stringChanged,&QApplication::quit);这是希望在对话框的字符发生改变时,调用QApplication的静态函数quit实现程序的退出。因为Qt4的信号槽机制只能处理成员函数,所以如果要将QApplication::quit函数作为槽函数,则必须使用一个成员函数封装这个静态函数,而在Qt5中,使用新的语法可以将信号直接关联到静态函数(这里接收信号的receiver对象是this,允许进行省略)。

当信号有重载的情况时,使用Qt5的新语法可能有一些不方便(重载函数会有歧义),例如,QspinBox有两个重载信号:void valueChanged(int i); void valueChanged(const QString & text); 可以使用Qt4的语法,connect(spinBox,SIGNAL(valueChanged(int),this,SLOT(onSpinBoxValueChanged(int)));由于SIGNAL和SLOT两个宏都要求指明参数类型,所以不会出现歧义,但是这样做失去了编译期检查的优点,为了使用Qt5的新语法,需要增加一个显式类型转换:connect(spinBox,
static_cast( QSpinBox ::valueChanged),
this,
&MainWindow::onSpinBoxValueChanged);
另外,有些槽函数具有默认值,比如,QPushButton的一个槽函数:void animateClick(int msec=100);如果想要连接这个槽函数(不管是Qt4还是Qt5都不能直接连接,编译器会要求槽函数的参数个数比信号少)。这个函数的参数具有默认值,但是函数参数的默认值只在直接调用函数时才有效,在取函数地址的时候,编译器是看不到参数默认值,所以这个槽函数并不真正需要显示地提供一个参数,这种情况在Qt4中就需要使用合适的槽函数进行封装,在Qt5中也可以这么做,但可以使用Qt5的lambda表达式更简洁地达到这一目的。

Lambda表达式是匿名函数,可以捕捉到函数体意外的变量(这些变量在引入符可见的作用域有定义,外部全局变量虽然可见但没有定义,可以通过声明在此函数体内从而获得“定义”,从而在lambda内部使用)。

5、3信号和槽机制应用

Qt是一个跨平台的C++GUI应用程序,它提供了丰富的窗口部件集,具有面向对象、易于扩展、真正的组件编程等特点,更为引人注目的是目前Linux上最为流行的KDE桌面环境就是建立在Qt库的基础上。

信号和槽机制是Qt的核心机制,要精通Qt编程就必须对信号和槽要有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是Qt的核心特性,也是Qt区别于其它工具包的重要地方,信号和槽是Qt自定义的一种通信机制,它独立于C/C++语言,因此要正确处理信号和槽必须借助一个称为moc的Qt工具,该工具是一个C++预处理器,它为高层次的事件处理自动生成所需要的代码。

在我们所熟知的很多GUI工具包中,窗口小部件(widget)都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针,但是,在Qt中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了,信号和槽能携带任意数量和任意类型的参数,它们的类型完全安全的,不会像回调函数那样产生core dumps。
所有从QObject或其子类(如Qwidget)派生的类都能包含信号和操,但对象改变其状态时,信号就由该对象发射出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用,槽用于接收信号,但它们是普通的成员函数,一个槽并不知道是否有任何信号与自己相连,而且,对象并不了解具体的通信机制。

你可以将很多信号与单个的槽进行连接,也可以将单个的信号与很多槽进行连接,甚至将一个信号与另外一个信号相连接也是可能的,这时无论第一个信号什么时候发射系统都将立刻发射第二个信号。总之,信号和槽构造了一个强大的部件编程机制。
当某个信号对其客户或所有者发生的内部状态发生改变,信号被一个对象发射,只有定义过这个信号的类及派生类能够发射这个信号,当一个信号被发射时,与其相关联的槽将被立刻执行,就像一个正常的函数调用一个样,信号和槽机制完全独立于任何GUI事件循环,只有当所有槽返回以后发射函(emit)才返回。如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地执行,但是它们执行的顺序是随机的,我们不能人为地指定哪个先执行,哪个后执行。

信号的声明是在头文件中进行的,Qt的signals关键字指出进入了信号声明区,随后可声明自己的信号。signals是Qt的关键字,而非C/C++的,从形式上讲信号的声明与普通的C++函数是一样的,但信号却没有函数体定义,另外信号的返回类型都是void,不要指望能从信号返回什么有用信息。
信号由moc自动产生,它们不应该在.cpp文件中实现。
槽是普通的C++成员函数,可以被正常的调用,它们唯一的特殊性是很多信号可以与其相关联。当与其相关联的信号被发射时,这个槽就会被调用,槽可以有参数,但槽的参数不能有缺省值。既然操是普通的成员函数,因此与其它 的函数一样,它们也有存取权限,槽的存取权限决定了谁能够与其关联。
槽函数分为三种类型:public slots、private slots和protected slots。public slots,在这个区间内声明的槽意味着任何对象都可将信号与之连接,这对于组件编程非常有用,你可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息能够正确的传递;protected slots,在这个区内声明的槽意味着当前类及其子类可以将信号与之连接,这适用于那些槽(它们是类实现的一部分,但是其界面接口却面向外部);private slots,在这个区内声明的槽意味着只有类自己可以将信号与之连接,这适用于联系非常紧密的类。

槽也能够被声明为虚函数,这是非常有用的。
槽的声明是在头文件中进行的。
通过调用QObject对象的connect函数来将某个对象的信号与另外一个对象的槽函数相关联,这样当发射者发射信号时,接收者的槽函数将被调用,connect函数的作用就是将发射着sender对象中的信号signal与接收者reciver中的member槽函数联系起来,当指定信号signal时可以使用Qt的宏SIGNAL(),当指定槽函数可以使用宏SLOT(),如果发射者与接收者属于同一个对象的话,那么在connect调用中接收者参数可以省略(值得商榷)。

有三种情况必须使用disconnect()函数,断开与某个对象相关联的任何对象,这似乎有点不可理解,事实上,当我们在某个对象中定义了一个或者多个信号,这些信号与另外若干个对象中的槽相关联,disconnect(myObject,0,0,0)或者myObject->disconnect();断开与某个特定信号的连接,disconnect(myObject,SIGNAL(mySignal),0,0)或者myObject->disconnect(SIGNAL(mySignal());断开两个对象之间的关联,disconnect(myObject,0,myReceiver,0)或者myObject->disconnect(myReceiver)。还可以断开某个特定信号与某个特定槽的连接。
在disconnect函数中0可以用作一个通配符,分别表示任何信号、任何接收对象、接收对象中的任何槽函数,但是发射者sender不能为0,其它三个参数的值可以等于0.。

元对象编译器moc对C++文件中的类声明进行分析并产生用于初始化元对象的C++代码,元对象包含全部信号和槽的名字以及指向这些函数的指针,moc读C++源文件,如果发现有Q_OBJECT宏声明的类,它就会生成另外一个C++源文件,这个新生成的文件包含有该类的元对象代码,例如,假设我们有一个头文件mysignal.h在这个文件中包含有信号和槽的声明,那么在编译之前moc工具就会根据该文件自动生成一个mysignal.moc.h的C++源文件并将其提交给编译器,类似地,对应于mysignal.cpp文件moc工具将自动生成一个名为mysignal.moc.cpp文件提交给编译器。元对象代码是signal/slot机制所必须的,用moc产生的C++源文件必须与类实现一起进行编译和连接,或者用#include语句将其包含到类的源文件中,moc并不扩展#define宏定义,它只是简单的跳过所遇到的任何预处理指令。
在类声明的开始位置必须加上Q_OBJECT宏,它将告诉编译器之前必须用moc工具进行扩展,signals没有public、private、protected等属性。

信号和槽的效率是非常高的,但同真正的回调函数比较起来,由于增加了灵活性,因此速度上有所损失,在实时系统上尽可能少用这种机制,信号和槽机制使用不当可能会产生死循环,宏定义不能用在signal和slot参数中,既然moc工具不扩展#define,因此在signals和slots中携带参数的宏就不能正确的工作,如果不带参数是可以的。构造函数不能用在signals和slots声明区域内,函数指针不能作为信号或槽的参数(但可以通过typedef包装以下函数指针就能绕过这个限制),信号和槽不能有缺省参数,信号和槽也不能携带模板类参数(如果将信号、槽声明为模板类参数(模板参数已指明类型)的话,即使moc工具不报告错误,也不可能得到与其的结果(但可以使用typedef来绕过这个限制)typedef pair InPair;。嵌套的类不能位于信号和槽区域内,也不能有信号和槽(在嵌套类中声明槽是不合法的,在信号或槽区域声明嵌套类是不合法的)。友元声明不能位于信号和槽声明区内。

软件自身信号和槽(自关联信号和槽(UI设计转到槽,类的静态函数被触发)),自定义信号和槽。

槽函数为lambda表达式时,可以不写接收者。

槽函数的响应按照信号和槽连接的顺序依次响应。

6、事件机制

6、1Qt事件简介

Qt程序是事件驱动的,程序的每个动作都是由内部某个事件所触发的Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期。

常见的Qt事件类型如下:
1> 键盘事件,按键按下、松开。
2> 鼠标事件,鼠标移动、按下、松开,按下又分为左键、右键、中键、双击。
3> 拖放事件,用鼠标进行拖放。
4> 滚轮事件,鼠标滚轮时间。
5> 绘屏事件,重新绘制屏幕某些部分。
6> 定时事件,定时器到时。
7> 焦点事件,键盘焦点移动。
8> 进入和离开事件:鼠标移入某区域或离开某区域。
9> 移动事件,窗口位置改变(以下窗口是指widget,也就是说子部件也可)。
10> 大小改变事件,窗口大小改变。
11> 显示和隐藏事件,widget显示和隐藏。
12> 窗口事件,窗口是否为当前窗口。

Qt将系统产生的消息转化为Qt事件,Qt事件被封装为对象,所有的Qt事件均继承抽象类QEvent,用于描述程序内部或外部发生的动作,任意的QObject对象都具备处理Qt事件的能力。

6、2事件的产生

操作系统(电脑所用的操作系统)产生消息,Qt将消息封装为事件放入到系统的消息队列中,Qt事件循环的时候读取消息队列中的事件,转换为QEvent,再依次处理。

Qt应用程序自己也可产生事件,一种是调用QApplication::postEvent(),例如QWidget::update函数(当需要重新绘制屏幕时,程序调用update()函数)会产生一个paintEvent事件,被放置到消息队列中等待被处理;一种是调用sendEvent()函数,事件不会被放入队列,而是直接被派发和处理,QWidget::repaint()函数用的就是这种方式。

6、3事件的处理

事件有两种调度方式,同步和异步。

Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环,先处理Qt事件队列中的事件,直至为空,在处理系统事件消息队列中的消息,直至为空。处理系统消息时会产生新的Qt事件,需要对其再次进行处理。

调用QApplication::sendEvent()的时候,消息会被立即处理,是同步的,实际上,QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节。

6、4事件的派发和过滤

事件过滤器是Qt中一个独特的事件处理机制,功能强大而且使用起来灵活方便。通过事件过滤器,可以让一个对象侦听拦截另外一个对象的事件。

事件过滤器实现如下:在所有Qt对象的基类QObject中有一个类型为QObjectList的成员变量,名字为eventFilter,当某个QObject(A)给QObject(B)安装了事件过滤器后,B会把A的指针保存在eventFilter中,在B处理事件前,会先去检查eventFilter列表,如果非空就先调用列表中对象的eventFilter()函数。一个对象可以给多个对象安装过滤器(也可给自己安装事件过滤器),一个对象能同时被安装多个过滤器,在事件到达后,事件过滤器以安装次序的反序被调用。

事件过滤器函数eventFilter()返回值是bool型,如果返回true则表示事件已经被处理完毕,Qt将直接返回,进行下一事件的处理,如果返回true将接着被送往剩下的事件过滤器或是目标对象进行处理(目标对象含有很多事件处理函数)。

事件的派发:
1> Qt中,事件的派发是从QApplication::notify()函数开始的,因为QApplication也是继承自QObject,所以先检查QApplication对象,如果有事件过滤器接下来notify()会过滤一些事件或合并一些事件(如失效widget的鼠标事件会被过滤掉,而同一区域重复的绘图事件会被吞并),事件被送到receiver::event()处理。
2> 在receiver::event()中,先检查有无事件过滤器安装在receiver上,若有则调用之,然后根据QEvent的类型,调用相应的特定事件处理函数(在实际中,经常需要重载特定事件处理函数处理事件,即重写),对于不常见的事件没有相对应的特定事件处理函数,如果要处理这些事件,就需要使用别的方法(比如重载event()函数,或者安装事件过滤器)。

事件的转发:对于某些类别的事件,如果在整个事件的派发过程结束后,还没有被处理,那么这个事件将会向上转发给它的父widget,直到最顶层窗口。Qt中和事件相关的函数通过两种方式通信,一种是QApplication::notify(),QObject::eventFilter()(分发和过滤),另一种是QEvent::ignore()或QEvent::accept()对事件进行标识,只用于event()函数和特定事件处理函数之间的沟通,而且只有用在某些类别事件上是有意义的。

在事件分发函数中,必须将事件返回出去,QLabel::event(ev);继承谁将返回给谁;在事件过滤器中,不能将事件返回(这么做会将事件传递终止);在特定事件处理时,此时已是事件的处理函数,不需要在将事件传递,可以不返回事件(从void参数就可以看出不需要返回出事件,因此此时是事件处理的终端了),但是在绘屏事件中,每一次鼠标的移动都会触发绘屏事件,但是如果不将绘屏事件返回将不会出现影像,即图像已被绘制但不显现,所以要将绘屏事件返回给父类QPushbutton::painter(event);,具体参考下文,这里没说明白)。

事件的处理5种:重写特定事件处理函数;重写event()函数(需要调用父类的event()函数来处理不需要处理或者是不清楚如何处理的事件);在Qt对象上安装事件过滤器;给QApplication对象安装过滤器;继承QApplication::notify()函数来分发事件。

事件处理函数中发送Qt预定义的信号。

6、5 创建用户事件

下述文字仅有参考意义。

  • 创建一个自定义类型的事件,你需要定义一个事件号,其值必须大于QEvent::User,为了传递有关你的自定义事件,可能自定义的事件要从QCustonEvent类继承。

    编写用户事件类的方法是先定义一个事件号,再实现事件类,应用程序把用户事件类和Qt事件类一样处理。

6、6特殊事件的处理

Qt中,在双击事件mouseDoubleClickEvent中会触发单击事件,原因是:鼠标按下-弹起,一个单击信号就发射了,在单击后的一段时间(很短)内,鼠标按下-弹起,一个双击信号发送。

鼠标按下。双击时产生的信号可以触发单击事件,且在单击事件内事件类型为双击。

鼠标事件的处理:在鼠标事件中,不论是在父类还是在子类重写鼠标事件函数,都会触发子类区域鼠标事件,得到想要的结果。

void CButton::mousePressEvent(QMouseEvent *e)
{
    int x=e->x();
    int y=e->y();
    int button=e->button();

    QString btnstr;
    if(button==Qt::LeftButton)
    {
        btnstr="LeftButton";
    }

    if(button==Qt::RightButton)
    {
        btnstr="RightButton";
    }

    if(button==Qt::MidButton)
    {
        btnstr="MidButton";
    }
    this->setText(QString("[%1,%2],%3").arg(x).arg(y).arg(btnstr));
}

void CButton::mouseMoveEvent(QMouseEvent *e)
{
    int x=e->x();
    int y=e->y();
    int button=e->button();//返回的是空

    QString btnstr;
    if(button==Qt::LeftButton)
    {
        btnstr="LeftButton";
    }

    if(button==Qt::RightButton)
    {
        btnstr="RightButton";
    }

    if(button==Qt::MidButton)
    {
        btnstr="MidButton";
}
this->setText(QString("[%1,%2],%3").arg(x).arg(y).arg(btnstr));//此时需要按住某个按键才能触发移动事件(原先移动的坐标仍然会打印在屏幕上),如果打印格式改为h5格式,会像开启鼠标光标追踪一样(不需要按住某个按键,只需要移动鼠标就能触发鼠标移动事件)。

绘屏事件:
绘屏事件需要调用绘屏事件,重写类的绘屏事件后,需要将绘屏事件中的画家设置父类this,否则屏幕上不会显现没有父类的画家所画出来的画。

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawLine(0,0,34,100);
    QPainter painter2;
    painter2.drawText(200,100,"singal");
    QWidget::paintEvent(event);//这一行的添加,可能是为了继承此类的子类,防止事件不能到达子类,鼠标事件中QPushButton::mousePressEvent(e);与之类似
}

事件分发:

bool CButton::event(QEvent *e)
{
    if(e->type()==QEvent::MouseButtonPress)
    {
        QPushButton::event(e);// 1 删除此条语句,将屏蔽鼠标按下事件,鼠标按下事件不会被分发,鼠标移动事件仍被触发
        return true;
    }
    QPushButton::event(e);
    return false;
} 
这个函数将继续调用鼠标按下事件显现坐标,对鼠标事件没有影响。

这个是Qt内部event函数格式:

class MyClass : public QWidget
  {
      Q_OBJECT

  public:
      MyClass(QWidget *parent = 0);
      ~MyClass();

      bool event(QEvent* ev)
      {
          if (ev->type() == QEvent::PolishRequest) {
              // overwrite handling of PolishRequest if any
              doThings();
              return true;
          } else  if (ev->type() == QEvent::Show) {
              // complement handling of Show if any
              doThings2();
              QWidget::event(ev);
              return true;
          }
          // Make sure the rest of events are handled
          return QWidget::event(ev);
      }
  };

事件过滤器:

bool CButton::eventFilter(QObject *watched, QEvent *event)
{
    if(event->type()==QEvent::MouseButtonPress)
    {

        return true;
    }
//    if(event->type()==QEvent::MouseMove)
//    {

//        return true;
//    }
    return false;
}
重写类的事件过滤器,并不会过滤事件,需要将事件过滤器安装到类身上,才能起到事件过滤的作用。

定时事件:
startTimer(int x)函数会返回一个时间id,起始为1,参数是x毫秒,开启这个函数后,每隔x 毫秒就会使timerEvent事件参数带上这个信号,通过killTimer( int timid),参数是时间id,来终止这个进程。

void CButton::timerEvent(QTimerEvent *e)
{
    if(e->timerId()==timId)
    {
       qDebug()<

定时器,QTimer tim; 定义了一个定时器,tim.start(x);开启一个定时器,tim.stop();停止计时器。

connect(&a1,&QTimer::timeout,[=](){
          qDebug()<

7、元对象系统

Qt提供了强大的基于元对象系统的属性系统,可以在运行Qt的平台上支持标准C++编译器。要在一个类中声明属性,该类必须继承自QObject类,而且还要在声明前使用Q_PROPERTY()宏,这个宏里面有许多函数。

Qt中使用对象树来组织和管理所有的QObject类及其子类的对象。

元对象系统:Qt中的元对象系统(Meta-Object System)提供了对象间通信的信号和槽机制、运行时类型信息和动态属性系统。元对象系统是基于以下3个条件的:该类必须继承自QObject类;必须在类的私有声明区Q_OBJECT宏;元对象编译器Meta-Object Compiler为QObject的子类实现元对象特性提供必要的代码。

moc工具读取一个C++源文件,如果它发现一个或者多个类的声明中包含Q_OBJECT宏,则会另外创建一个C++源文件,其中包含了为每一个类生成的元对象代码,这些产生的源文件或者被包含进类的源文件中,或者和类的实现同时进行编译和链接。

元对象系统主要是为了实现信号和槽机制才被引入的,不过除了信号和槽以外,元对象系统还提供了其它特性:
QObject::metaObject()函数可以返回一个类的元对象;
QMetaObject::className()可以在运行时以字符串形式返回类名,而不需要C++编辑器原生的运行时类型信息RTTI的支持;
QObject::inherits()函数返回一个对象是否是QObject继承树上一个类的实例的信息;
QObject::tr()和QObject::trUtf8()进行字符串翻译来实现国际化;
QObject::setProperty()和QObject::property()通过名字来动态设置或者获取对象属性;
QMetaObject::newInstance()构造类的一个新实例。
使用qobject_cast()函数对QObject类进行动态类型转换,这个功能类似于C++动态转换,但不再需要RTTI的支持,如果不兼容,返回0。

8、QApplication简介

QApplication类管理GUI程序的控制流和主要设置,是基于QWidget的,为此特化了QGuiApplication的一些功能,处理QWidget特有的初始化和结尾工作。
QApplication的主要职责是使用用户的桌面设置进行初始化、然后跟踪这些属性的变化;处理事件,从窗口系统接收事件并派发到相应的Widget,使用sendEvent()和postEvent函数可以派发时间;处理命令行参数,设置内部状态;定义GUI外观,外观由QStyle对象包装,运行时通过setStyle()函数进行设置;设置颜色分配规则,对应的函数为setColorSpec();本地化字符串,函数为translate();提供了一些有用的对象,如desktop()、clipboard()函数;知道Widget以及Window相应的函数widgetAt()、topLevelWidget()、closeAlWindows();管理鼠标光标,函数setOverrideCursor()。
QApplication继承自QGuiApplication,后者是基于非QWidget的,提供了会话管理,用户退出时可以友好地终止程序,如果终止不了还可以取消对应的进程,甚至于保存程序的所有相关状态用于将来的会话,相关函数isSessionRestored()、sessionId()、conmitDataRequest()、saveDataRequest()。
QGuiApplication继承于QCoreAppliction,后者不包括UI,一大核心功能是提供了event loop,这些event可以来自操作系统,如timer、网络时间以及其他来源的event都可以被收发,调用exec()函数进入event loop,知道quit()函数调用时才退出,退出时发送aboutToQuit()信号,等同于exit(0),sendEvent()函数立即处理事件,posEvent()函数把时间放入消息队列等待后续处理,处于消息队列的event还可以通过removeEvent()和sendPosedEvent()进行删除和立即处理。
与程序路径相关的有两个函数applicationDirPath()和applicationFilePath(),另外一个是库相关的函数libraryPaths()、setLibraryPaths()、addLibraryPath()、removeLibraryPath(),以及QLibrary类。
获取命令行参数使用函数argument(),专门处理命令行参数的类为QCommandLineParser,为保证兼容性,还要适当地设置语系setLocals()。

你可能感兴趣的:(Qt学习,Qt概述)