这里写目录标题
- [Qt 事件](#Qt 事件)
- [Qt ⽂件](#Qt ⽂件)
- [Qt 多线程](#Qt 多线程)
- [Qt ⽹络](#Qt ⽹络)
- [Qt ⾳视频](#Qt ⾳视频)
Qt 事件
事件介绍
事件是应⽤程序内部或者外部产⽣的事情或者动作的统称 。在 Qt 中使⽤⼀个对象来表⽰⼀个事件。所有的 Qt 事件均继承于抽象类 QEvent。事件是由系统或者 Qt 平台本⾝在不同的时刻发出的。当⽤⼾按下⿏标、敲下键盘,或者是窗⼝需要重新绘制的时候,都会发出⼀个相应的事件。⼀些事件是在⽤⼾操作时发出,如键盘事件、⿏标事件等,另⼀些事件则是由系统本⾝⾃动发出,如定时器事件。常⻅的 Qt 事件如下:
常⻅事件描述:
事件名称 | 描述 |
---|---|
⿏标事件 | ⿏标左键、⿏标右键、⿏标滚轮,⿏标的移动,⿏标按键的按下和松开 |
键盘事件 | 按键类型、按键按下、按键松开 |
定时器事件 | 定时时间到达 |
进⼊离开事件 | ⿏标的进⼊和离开 |
滚轮事件 | ⿏标滚轮滚动 |
绘屏事件 | 重绘屏幕的某些部分 |
显⽰隐藏事件 | 窗⼝的显⽰和隐藏 |
移动事件 | 窗⼝位置的变化 |
窗⼝事件 | 是否为当前窗⼝ |
⼤⼩改变事件 | 窗⼝⼤⼩改变 |
焦点事件 | 键盘焦点移动 |
拖拽事件 | ⽤⿏标进⾏拖拽 |
事件的处理
事件处理⼀般常⽤的⽅法为:重写相关的 Event 函数 。
在 Qt 中,⼏乎所有的 Event 函数都是虚函数,所以可以重新实现。
⽰例1:
-
新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰
-
设计 UI ⽂件,如下图⽰;
-
在项⽬中新添加⼀个类:MyLabel;
-
选择:Choose ... ,弹出如下界⾯:
-
此时项⽬中会新添加以下两个⽂件:
-
在帮助⽂档中查找对应的内容;
-
点击 "显⽰" 之后,出现如下内容:
-
复制 enterEvent() ,粘贴在项⽬⽂件 "mylabel.h" 中;
-
重写 enterEvent() ⽅法;
-
在 UI ⽂件中选中 Label,右键 ------> 提升为...
-
当点击 "提升为... " 之后,弹出如下对话框:
-
修改基类:
-
执⾏效果如下:当⿏标进⼊设计好的标签之后,就会在应⽤程序输出栏中打印:⿏标进⼊
⽰例2:当⿏标点击时,获取对应的坐标值;
-
在上述⽰例的基础上,在 mylabel.h 中声明 mousePressEvent() ⽅法
-
在 mylabel.cpp 中重写 mousePressEvent() ⽅法;
-
实现效果如下:
⽰例:⿏标左键点击时,打印对应的坐标值,⿏标右键点击时,打印基于屏幕的坐标
运行效果:
按键事件
Qt 中的按键事件是通过QKeyEvent 类来实现的。当键盘上的按键被按下或者被释放时,键盘事件便会触发。在帮助⽂档中查找 QKeyEvent 类如下:
查找按键事件中所有的按键类型:在帮助⽂档中输⼊:Qt::Key,如下图:
单个按键
⽰例:当某个按键被按下时,输出:某个按键被按下了;
-
新建项⽬,在头⽂件 "widget.h" 中声明虚函数 keyPressEvent();如下图:
-
在 "widget.cpp" ⽂件中重写 keyPressEvent() 虚函数;
-
运行效果
组合按键
在 Qt 助⼿中搜索:Qt::KeyboardModifier,如下图⽰:
Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改键。在 Qt 中,键盘事件可以与修改键⼀起使⽤,以实现⼀些复杂的交互操作。KeyboardModifier 中修改键的具体描述如下:
Qt::NoModifier | ⽆修改键 |
---|---|
Qt::ShiftModifier | Shift 键 |
Qt::ControlModifier | Ctrl 键 |
Qt::AltModifier | Alt 键 |
Qt::MetaModifier | Meta键(在Windows上指Windows键,在macOS上指Command键) |
Qt::KeypadModifier | 使⽤键盘上的数字键盘进⾏输⼊时,Num Lock键处于打开状态 |
Qt::GroupSwitchModifier | ⽤于在输⼊法 组之间 切换 |
⽰例:
运行效果:
⿏标事件
在 Qt 中,⿏标事件是⽤ QMouseEvent 类来实现的。当在窗⼝中按下⿏标或者移动⿏标时,都会产⽣⿏标事件。
利⽤ QMouseEvent 类可以获取⿏标的哪个键被按下了以及⿏标的当前位置等信息。在 Qt 帮助⽂档中查找QMouseEvent类 如下图⽰:
⿏标单击事件
在 Qt 中,⿏标按下是通过虚函数 mousePressEvent()来捕获的。
⿏标左右键及滚的表⽰如下:
- Qt::LeftButton ⿏标左键
- Qt::RightButton ⿏标右键
- Qt::MidButton ⿏标滚轮
⽰例1:⿏标左键
-
在 "widget.h" 头⽂件中声明⿏标按下事件;
-
在 "widget.cpp" ⽂件中重新实现 mousePressEvent() 函数
实现效果如下:
⽰例2:⿏标右键:
⽰例3:⿏标滚轮
⿏标释放事件
⿏标释放事件是通过虚函数 mouseReleaseEvent() 来捕获的。
⽰例:
-
在 "widget.h" 头⽂件中声明⿏标释放事件;
-
在 "widget.cpp" ⽂件中重新实现 mouseReleaseEvent() 函数
运行效果:
⿏标双击事件
⿏标双击事件是通过虚函数:mouseDoubleClickEvent() 来实现的。
⽰例:⿏标左键双击
-
在 "widget.h" 头⽂件中声明⿏标双击事件;
-
在 "widget.cpp" ⽂件中重新实现 mouseDoubleClickEvent() 函数
运行效果:
⿏标移动事件
⿏标移动事件是通过虚函数:mouseMoveEvent() 来实现的。同时为了实时捕获⿏标位置信息,需要通过函数 setMouseTracking() 来追踪⿏标的位置。
setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获⿏标位置信息。否则只有当⿏标按下时才能捕获其位置信息。
⽰例:
运行效果:
滚轮事件
在 Qt 中,⿏标滚轮事件是通过QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获取。
其中返回值代表滚轮滑动的距离。正数表⽰滚轮相对于⽤⼾向前滑动,负数表⽰滚轮相对于⽤⼾向后滑动。
⽰例:
运行效果:
定时器
Qt 中在进⾏窗⼝程序的处理过程中,经常要周期性的执⾏某些操作,或者制作⼀些动画效果,使⽤定时器就可以实现。所谓定时器就是在间隔⼀定时间后,去执⾏某⼀个任务。定时器在很多场景下都会使⽤到,如弹窗⾃动关闭之类的功能等。
Qt中的定时器分为 QTimerEvent 和 QTimer 这2个类。
- QTimerEvent类 ⽤来描述⼀个定时器事件。在使⽤时需要通过 startTimer() 函数来开启⼀个定时器,这个函数需要输⼊⼀个以毫秒为单位的整数作为参数来表明设定的时间,它返回的整型值代表这个定时器。当定时器溢出时(即定时时间到达)就可以在 timerEvent() 函数中获取该定时器的编号来进⾏相关操作。
- QTimer类 来实现⼀个定时器,它提供了更⾼层次的编程接⼝,如:可以使⽤信号和槽,还可以设置只运⾏⼀次的定时器。
QTimerEvent 类
⽰例1:在UI界⾯上放置两个 Label 控件,⼀个让其1秒数字累加⼀次,⼀个让其2秒数字累加⼀次。
-
新建项⽬,在UI界⾯⽂件放置两个 Label 控件;
-
在 "widget.h" 头⽂件中声明timerEvent() 函数,并定义两个整型变量;
-
在 "widget.cpp" ⽂件中重写timerEvent() 函数;
实现效果如下:
QTimer 类
⽰例:在UI界⾯放置⼀个 Label 标签,两个按钮,分别是 "开始" 和 "停⽌" ,当点击 "开始" 按钮时,开始每隔1秒计数⼀次,点击 "停⽌" 按钮时,暂停计数。
-
设计 UI 界⾯如下:
-
在 "widget.cpp" ⽂件中实现对应功能;
实现效果如下:
获取系统⽇期及时间
在 Qt 中,获取系统的⽇期及实时时间可以通过 QTimer 类 和 QDateTime类。
QDateTime类 提供了字符串格式的时间。字符串形式的时间输出格式由 toString() ⽅法中的 format
参数列表决定,可⽤的参数列表如下:
⽰例:获取系统⽇期及实时时间;
- 设计UI界⾯⽂件;放置⼀个 Label控件,⽤来显⽰⽇期及时间,放置两个按钮:"开始" 和 "停⽌" ;
- 在 "widget.h" 头⽂件中声明更新时间的槽函数;
- 在 "widget.cpp" ⽂件中实现对应功能;
实现效果如下:
事件分发器
概述
在 Qt 中,事件分发器(Event Dispatcher) 是⼀个核⼼概念,⽤于处理 GUI应⽤程序中的事件。事件分发器负责将事件从⼀个对象传递到另⼀个对象,直到事件被处理或被取消。
每个继承⾃ QObject类或QObject类本⾝都可以在本类中重写 bool event(QEvent*e) 函数,来实现相关事件的捕获和拦截.
事件分发器⼯作原理
在 Qt 中,我们发送的事件都是传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函数 。所有的事件都会进⼊到这个函数⾥⾯,那么我们处理事件就要重写这个 event() 函数。event() 函数本⾝不会去处理事件,⽽是根据 事件类型(type值)调⽤不同的事件处理函数 。事件分发器就是⼯作在应⽤程序向下分发事件的过程中,如下图:
如上图,事件分发器⽤于分发事件 。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是
通过 bool event(QEvent*e) 函数来实现。其返回值为布尔类型,若为 ture,代表拦截,不向下分发。
Qt 中的事件是封装在 QEvent类 中,在 Qt 助⼿中输⼊ QEvent 可以查看其所包括的事件类型,如下图⽰:
⽰例:
-
在 "widget.h" 头⽂件中声明⿏标点击事件 和 事件分发器;如下图⽰:
-
在 "widget.cpp" ⽂件中实现 ⿏标点击事件 和 拦截事件;
执⾏结果如下:
事件过滤器
在 Qt 中,⼀个对象可能经常要查看或拦截另外⼀个对象的事件,如对话框想要拦截按键事件,不让别的组件接收到,或者修改按键的默认值等。通过上⾯的学习,我们已经知道,Qt 创建了 QEvent事件对象之后,会调⽤QObject 的 event()函数 处理事件的分发。显然,我们可以在 event()函数 中实现拦截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个event()函数。这当然相当⿇烦,更不⽤说重写 event()函数还得⼩⼼⼀堆问题。好在 Qt 提供了另外⼀种机制来达到这⼀⽬的:事件过滤器。
事件过滤器是在应⽤程序分发到 event事件分发器之前,再做⼀次更高级的拦截。如下图示 :
事件过滤器的⼀般使⽤步骤:
1、安装事件过滤器;
2、重写事件过滤器函数:eventfilter() 。
⽰例:
-
新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件
-
设计 UI ⽂件,如下图⽰;
-
在项⽬新添加⼀个类:MyLabel;
-
在 UI ⽂件中选中 Label,右键 ------> 提升为...
-
当点击 "提升为... " 之后,弹出如下对话框:
-
在 "mylabel.h" 中声明**⿏标点击事件** 和 事件分发器
-
在 "mylabel.cpp" ⽂件中实现⿏标点击事件和事件分发器;
-
在 "widget.h" 头⽂件中声明事件过滤器函数;
-
在 "widget.cpp" ⽂件中实现事件过滤器的两个步骤;
执⾏结果如下所⽰:
Qt ⽂件
Qt ⽂件概述
⽂件操作是应⽤程序必不可少的部分。Qt 作为⼀个通⽤开发库,提供了跨平台的⽂件操作能⼒。 Qt
提供了很多关于⽂件的类,通过这些类能够对⽂件系统进⾏操作,如⽂件读写、⽂件信息获取、⽂件
复制或重命名等。
输⼊输出设备类
在 Qt 中,⽂件读写的类为 QFile。QFile 的⽗类为 QFileDevice ,QFileDevice 提供了⽂件交互操作的
底层功能。 QFileDevice 的⽗类是 QIODevice,QIODevice 的⽗类为 QObject 。
QIODevice 是 Qt 中所有输⼊输出设备(input/output device,简称 I/O 设备)的基础类,I/O 设备就
是能进⾏数据输⼊和输出的设备,例如⽂件是⼀种 I/O 设备,⽹络通信中的 socket 是I/O 设备, 串
⼝、蓝⽛等通信接⼝也是 I/O 设备,所以它们也是从 QIODevice 继承来的。Qt 中主要的⼀些 I/O 设备
类的继承关系如下图所⽰:
上图中各类的说明如下:
- QFile 是⽤于⽂件操作和⽂件数据读写的类,使⽤ QFile 可以读写任意格式的⽂件。
- QSaveFile 是⽤于安全保存⽂件的类。使⽤ QSaveFile 保存⽂件时,它会先把数据写⼊⼀个临时⽂件,成功提交后才将数据写⼊最终的⽂件。如果保存过程中出现错误,临时⽂件⾥的数据不会被写⼊最终⽂件,这样就能确保最终⽂件中不会丢失数据或被写⼊部分数据。 在保存⽐较⼤的⽂件或复杂格式的⽂件时可以使⽤这个类,例如从⽹络上下载⽂件等。
- QTemporaryFile 是⽤于创建临时⽂件的类。使⽤函数 QTemporaryFile::open() 就能创建⼀个⽂件名唯⼀的临时⽂件,在 QTemporaryFile 对象被删除时,临时⽂件被⾃动删除。
- QTcpSocket 和 QUdpSocket 是分别实现了 TCP 和 UDP的类。
- QSerialPort 是实现了串⼝通信的类,通过这个类可以实现计算机与串⼝设备的通信。
- QBluetoothSocket 是⽤于蓝⽛通信的类。⼿机和平板计算机等移动设备有蓝⽛通信模块,笔记本电脑⼀般也有蓝⽛通信模块。通过QBluetoothSocket类,就可以编写蓝⽛通信程。如编程实现笔记本电脑与⼿机的蓝⽛通信。
- QProcess 类⽤于启动外部程序,并且可以给程序传递参数。
- QBuffer 以⼀个 QByteArray 对象作为数据缓冲区,将 QByteArray 对象当作⼀个 I/O 设备来读写。
⽂件读写类
在 Qt 中,⽂件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些⽤来读写⽂件的⽅法。对
于⽂件的操作主要有:
- 读数据:QFile 类中提供了多个⽅法⽤于读取⽂件内容;如 read()、readAll()、readLine()等。
- 写数据:QFile 类中提供了多个⽅法⽤于往⽂件中写内容;如 write()、writeData()等。
- 关闭⽂件:⽂件使⽤结束后必须⽤函数 close() 关闭⽂件。
访问⼀个设备之前,需要使⽤ open()函数 打开该设备,⽽且必须指定正确的打开模式,QIODevice 中
所有的打开模式由 QIODevice::OpenMode 枚举变量定义,其取值如下:
QIODevice::NotOpen | 没有打开设备 |
---|---|
QIODevice::ReadOnly | 以只读⽅式打开设备 |
QIODevice::WriteOnly | 以只写⽅式打开设备 |
QIODevice::ReadWrite | 以读写⽅式打开设备 |
QIODevice::Append | 以追加⽅式打开设备,数据将写到⽂件末尾 |
QIODevice::Truncate | 每次打开⽂件后重写⽂件内容,原内容将被删除 |
QIODevice::Text | 在读⽂件时,⾏尾终⽌符会被转换为 '\n';当写⼊⽂件时,⾏尾终⽌符会被转换为本地编码 |
QIODevice::Unbuffered | ⽆缓冲形式打开⽂件,绕过设备中的任何缓冲区 |
QIODevice::NewOnly | ⽂件存在则打开失败,不存在则创建⽂件 |
⽰例1:读取⽂件内容
-
新建 Qt 项⽬,在 UI ⽂件中拖⼊⼀个 LineEdit,⼀个pushButton,⼀个 TextEdit。当点击按钮时,弹出窗⼝选择要读取的⽂件,并将读取到的内容在 TextEdit 中显⽰;
-
在 "widget.cpp" ⽂件中实现对应功能;
实现效果如下:
⽰例2:写⽂件
在上述⽰例的基础上修改 "widget.cpp" ⽂件;
实现效果如下:
⽂件和⽬录信息类
QFileInfo 是 Qt 提供的⼀个⽤于获取⽂件和⽬录信息的类,如获取⽂件名、⽂件⼤⼩、⽂件修改⽇期
等。QFileInfo类中提供了很多的⽅法,常⽤的有:
- isDir() 检查该⽂件是否是⽬录;
- isExecutable() 检查该⽂件是否是可执⾏⽂件;
- fileName() 获得⽂件名;
- completeBaseName() 获取完整的⽂件名;
- suffix() 获取⽂件后缀名;
- completeSuffix() 获取完整的⽂件后缀;
- size() 获取⽂件⼤⼩;
- isFile() 判断是否为⽂件;
- fileTime() 获取⽂件创建时间、修改时间、最近访问时间等;
⽰例:
在 "widget.cpp" ⽂件中添加如下代码:
实现效果如下:
Qt 多线程
Qt 多线程概述
在 Qt 中,多线程的处理⼀般是通过QThread类 来实现。
QThread 代表⼀个在应⽤程序中可以独⽴控制的线程,也可以和进程中的其他线程共享数据。
QThread 对象管理程序中的⼀个控制线程。
QThread 常⽤ API
run() | 线程的⼊⼝函数 |
---|---|
start() | 通过调⽤ run() 开始执⾏线程。操作系统将根据优先级参数调度线程。如果线程已经在运⾏,这个函数什么也不做 |
currentThread() | 返回⼀个指向管理当前执⾏线程的 QThread的指针 |
isRunning() | 如果线程正在运⾏则返回true;否则返回false |
sleep() | 使线程休眠,单位为秒 |
wait() | 阻塞线程,直到满⾜以下任何⼀个条件:与此 QThread 对象关联的线程已经完成执⾏(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。已经过了⼏毫秒。如果时间是ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回 false。 |
terminate() | 终⽌线程的执⾏。线程可以⽴即终⽌,也可以不⽴即终⽌,这取决于操作系统的调度策略。在terminate() 之后使⽤ QThread::wait() 来确保 |
finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理⼯作 |
使⽤线程
创建线程的步骤:
- ⾃定义⼀个类,继承于 QThread,并且只有⼀个线程处理函数(和主线程不是同⼀个线程),这个线程处理函数主要就是重写⽗类中的 run() 函数。
- 线程处理函数⾥⾯写⼊需要执⾏的复杂数据处理;
- 启动线程不能直接调⽤ run() 函数,需要使⽤对象来调⽤ start() 函数实现线程启动;
- 线程处理函数执⾏结束后可以定义⼀个信号来告诉主线程;
- 最后关闭线程。
⽰例:
-
⾸先新建 Qt 项⽬,设计 UI界⾯如下:
-
新建⼀个类,继承于 QThread类;
执⾏效果:
connect() 函数第五个参数为 Qt::ConnectionType,⽤于指定信号和槽的连接类型。同时影响信号的
传递⽅式和槽函数的执⾏顺序。Qt::ConnectionType 提供了以下五种⽅式:
Qt::AutoConnection | 在 Qt 中,会根据信号和槽函数所在的线程⾃动选择连接类型。如果信号和槽函数在同⼀线程中,那么使⽤ Qt:DirectConnection 类型;如果它们位于不同的线程中,那么使⽤Qt::QueuedConnection 类型。 |
---|---|
Qt::DirectConnection | 当信号发出时,槽函数会⽴即在同⼀线程中执⾏。这种连接类型适⽤于信号和槽函数在同⼀线程中的情况,可以实现直接的函数调⽤,但需要注意线程安全性 |
Qt::QueuedConnection | 当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下⼀次事件循环时执⾏。这种连接类型适⽤于信号和槽函数在不同线程中的情况,可以确保线程安全。 |
Qt::BlockingQueuedConnection | 与 Qt:QueuedConnection 类似,但是发送信号的线程会被阻塞,直到槽函数执⾏完毕,这种连接类型适⽤于需要等待槽函数执⾏完毕再继续的场景,但需要注意可能引起线程死锁的⻛险。 |
Qt::UniqueConnection | 这是⼀个标志,可以使⽤位或与上述任何⼀种连接类型组合使⽤。 |
线程安全
实现线程互斥和同步常⽤的类有:
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
互斥锁
互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法,在 Qt 中,互斥锁主要是通过
QMutex类来处理。
QMutex
特点:QMutex 是 Qt 框架提供的互斥锁类,⽤于保护共享资源的访问,实现线程间的互斥操作。
⽤途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
QMutexLocker
特点:QMutexLocker 是 QMutex 的辅助类,使⽤ RAII(Resource Acquisition Is Initialization)⽅式
对互斥锁进⾏上锁和解锁操作。
⽤途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
QReadWriteLocker、QReadLocker、QWriteLocker
特点:
QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。
QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。
QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。
⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提
供了更⾼效的并发访问⽅式。
条件变量
在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才
能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只
是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁
或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被
另⼀个线程唤醒。
在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题。
特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调。
信号量
有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序
的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可
⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥
锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。
特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。
Qt ⽹络
和多线程类似, Qt 为了⽀持跨平台, 对⽹络编程的API 也进⾏了重新封装.
UDP Socket
核⼼ API 概览
主要的类有两个. QUdpSocket 和 QNetworkDatagram
QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件.
名称 | 类型 | 说明 | 对标原⽣ API |
---|---|---|---|
bind(const QHostAddress&,quint16) | ⽅法 | 绑定指定的端⼝号. | bind |
receiveDatagram() | ⽅法 | 返回 QNetworkDatagram . 读取⼀个 UDP 数据报. | recvfrom |
writeDatagram(constQNetworkDatagram&) | ⽅法 | 发送⼀个 UDP 数据报. | sendto |
readyRead | 信号 | 在收到数据并准备就绪后触发. | ⽆ (类似于 IO 多路复⽤的通知机制) |
QNetworkDatagram 表⽰⼀个 UDP 数据报.
名称 | 类型 | 说明 | 对标原⽣ API |
---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 ) | 构造函数 | 通过 QByteArray , ⽬标 IP 地址,⽬标端⼝号 构造⼀个 UDP数据报.通常⽤于发送数据时. | ⽆ |
data() | ⽅法 | 获取数据报内部持有的数据. 返回QByteArray | ⽆ |
senderAddress() | ⽅法 | 获取数据报中包含的对端的 IP 地址. | ⽆, recvfrom 包含了该功能. |
senderPort() | ⽅法 | 获取数据报中包含的对端的端⼝号. | ⽆, recvfrom 包含了该功能 |
回显服务器
-
创建界⾯, 包含⼀个 QListWidget ⽤来显⽰消息.
-
创建 QUdpSocket 成员
修改 widget.h
修改 widget.cpp, 完成 socket 后续的初始化
⼀般来说, 要先连接信号槽, 再绑定端⼝.
如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了.
-
实现 processRequest , 完成处理请求的过程
读取请求并解析
根据请求计算响应
把响应写回到客⼾端
-
实现 process 函数
由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容.
此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.
回显客⼾端
-
创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的sizePolicy 为 Expanding
再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好
设置垂直布局的 layoutStretch 为 5, 1
-
在 widget.cpp 中, 先创建两个全局常量, 表⽰服务器的 IP 和 端⼝
-
创建 QUdpSocket 成员
修改 widget.h, 定义成员
修改 widget.cpp, 初始化 socket
-
给发送按钮 slot 函数, 实现发送请求.
-
再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应.
最终执⾏效果:
TCP Socket
核⼼ API 概览
核⼼类是两个: QTcpServer 和 QTcpSocket
QTcpServer ⽤于监听端⼝, 和获取客⼾端连接.
名称 | 类型 | 说明 | 对标原⽣ API |
---|---|---|---|
listen(const QHostAddress&,quint16 port) | ⽅法 | 绑定指定的地址和端⼝号, 并开始监听 | bind 和 listen |
nextPendingConnection() | ⽅法 | 从系统中获取到⼀个已经建⽴好的tcp 连接.返回⼀个 QTcpSocket , 表⽰这个客⼾端的连接.通过这个 socket对象完成和客⼾端之间的通信. | accept |
newConnection | 信号 | 有新的客⼾端建⽴连接好之后触发 | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
QTcpSocket ⽤⼾客⼾端和服务器之间的数据交互.
名称 | 类型 | 说明 | 对标原⽣ API |
---|---|---|---|
readAll() | ⽅法 | 读取当前接收缓冲区中的所有数据.返回 QByteArray 对象. | read |
write(const QByteArray& ) | ⽅法 | 把数据写⼊ socket 中. | write |
deleteLater | ⽅法 | 暂时把 socket 对象标记为⽆效. Qt会在下个事件循环中析构释放该对象. | ⽆ (但是类似于 "半⾃动化的垃圾回收") |
readyRead | 信号 | 有数据到达并准备就绪时触发. | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
disconnected | 信号 | 连接断开时触发. | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
QByteArray ⽤于表⽰⼀个字节数组. 可以很⽅便的和 QString 进⾏相互转换.
- 使⽤ QString 的构造函数即可把 QByteArray 转成 QString.
- 使⽤ QString 的 toUtf8 函数即可把 QString 转成 QByteArray.
回显服务器
-
创建界⾯. 包含⼀个 QListWidget , ⽤于显⽰收到的数据
-
创建 QTcpServer 并初始化
修改 widget.h, 添加 QTcpServer 指针成员.
修改 widget.cpp, 实例化 QTcpServer 并进⾏后续初始化操作
设置窗⼝标题
实例化 TCP server. (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
通过信号槽, 处理客⼾端建⽴的新连接.
监听端⼝
-
继续修改 widget.cpp, 实现处理连接的具体⽅法 processConnection
获取到新的连接对应的 socket.
通过信号槽, 处理收到请求的情况
通过信号槽, 处理断开连接的情况
-
实现 process ⽅法, 实现根据请求处理响应.
由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容.
此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.
回显客⼾端
-
创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的sizePolicy 为 Expanding
再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好
设置垂直布局的 layoutStretch 为 5, 1
-
创建 QTcpSocket 并实例化
修改 widget.h, 创建成员.
修改 widget.cpp, 对 QTcpSocket 进⾏实例化.
设置窗⼝标题
实例化 socket 对象 (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁).
和服务器建⽴连接
等待并确认连接是否出错
-
修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器
-
修改 widget.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应.
先启动服务器, 再启动客⼾端(可以启动多个), 最终执⾏效果:
HTTP Client
进⾏ Qt 开发时, 和服务器之间的通信很多时候也会⽤到 HTTP 协议.
- 通过 HTTP 从服务器获取数据.
- 通过 HTTP 向服务器提交数据.
核⼼ API
关键类主要是三个. QNetworkAccessManager , QNetworkRequest , QNetworkReply .
QNetworkAccessManager 提供了 HTTP 的核⼼操作.
⽅法 | 说明 |
---|---|
get(const QNetworkRequest& ) | 发起⼀个 HTTP GET 请求. 返回 QNetworkReply 对象 |
post(const QNetworkRequest& , constQByteArray& ) | 发起⼀个 HTTP POST 请求. 返回 QNetworkReply 对象 |
QNetworkRequest 表⽰⼀个 HTTP 请求(不含 body).
如果需要发送⼀个带有 body 的请求(⽐如 post), 会在QNetworkAccessManager 的 post ⽅法中通过单独的参数来传⼊ body
⽅法 | 说明 |
---|---|
QNetworkRequest(const QUrl& ) | 通过 URL 构造⼀个 HTTP 请求. |
setHeader(QNetworkRequest::KnownHeaders header,const QVariant &value) | 设置请求头. |
其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型, 常⽤取值:
取值 | 说明 |
---|---|
ContentTypeHeader | 描述 body 的类型. |
ContentLengthHeader | 描述 body 的⻓度. |
LocationHeader | ⽤于重定向报⽂中指定重定向地址. (响应中使⽤, 请求⽤不到) |
CookieHeader | 设置 cookie |
UserAgentHeader | 设置 User-Agent |
QNetworkReply 表⽰⼀个 HTTP 响应. 这个类同时也是 QIODevice 的⼦类.
⽅法 | 说明 |
---|---|
error() | 获取出错状态. |
errorString() | 获取出错原因的⽂本. |
readAll() | 读取响应 body. |
header(QNetworkRequest::KnownHeaders header) | 读取响应指定 header 的值 |
此外, QNetworkReply 还有⼀个重要的信号 finished 会在客⼾端收到完整的响应数据之后触发.
代码⽰例
给服务器发送⼀个 GET 请求.
-
创建界⾯. 包含⼀个 QLineEdit , QPushButton
先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的sizePolicy 为 Expanding
再使⽤垂直布局把 QPlainTextEdit 和上⾯的⽔平布局放好. ( QPlainTextEdit 的readOnly 设为 true )
设置垂直布局的 layoutStretch 为 5, 1
-
修改 widget.h, 创建 QNetworkAccessManager 属性
-
修改 widget.cpp, 创建实例
-
编写按钮的 slot 函数, 实现发送 HTTP 请求功能.
执⾏程序, 观察效果
发送 POST 请求代码也是类似. 使⽤ manager->post() 即可. 此处不再演⽰
Qt ⾳视频
Qt ⾳频
在 Qt 中,⾳频主要是通过 QSound 类来实现。但是需要注意的是QSound 类只⽀持播放 wav 格式的⾳频⽂件。也就是说如果想要添加⾳频效果,那么⾸先需要将 ⾮wav格式 的⾳频⽂件转换为 wav 格式。
通过帮助⼿册查看 QSound 类如下:
核⼼API概览
⽰例
Qt 视频
在 Qt 中,视频播放的功能主要是通过 QMediaPlayer类 和 QVideoWidget类来实现。在使⽤这两个类时要添加对应的模块 multimedia 和 multimediawidgets 。
核⼼API概览
setMedia() | 设置当前媒体源 |
---|---|
setVideoOutput() | 将QVideoWidget视频输出附加到媒体播放器。如果媒体播放器已经附加了视频输出,将更换⼀个新的 |
⽰例
⾸先在 .pro ⽂件中添加 multimedia 和 multimediawidgets 两个模块;如下图⽰:
修改widget.h
修改widget.cpp
执行效果