自学内容网 自学内容网

Qt --- 系统相关---事件、文件操作、多线程编程、网络编程、多媒体

虽然Qt是跨平台的C++开发框架,Qt的很多能力其实是操作系统提供的。只不过Qt封装了系统API。程序是运行在操作系统上的,需要系统给我们提供支撑。

事件、文件操作、多线程编程、网络编程、多媒体(音频、视频)。

一、事件

信号槽,用户进行的各种操作,就可能会产生信号,可以给某个信号指定槽函数,当信号触发时,就能够自动的执行到对应的槽函数。

事件也是非常类似的,用户进行的各种操作,也会产生事件,程序员同样可以给事件关联上处理函数(处理的逻辑)当事件触发的时候,就能够执行到对应的代码。

事件本身是操作系统提供的机制,Qt也同样把操作系统事件机制进行了封装,拿到了Qt中。但是由于事件对应的代码编写起来不是很方便,Qt对于事件机制又进行了进一步的封装,就得到了信号槽。信号槽就是对于事件的进一步封装。事件是信号槽的底层机制。实际Qt开发程序过程中,绝大部分和用户之间进行的交互都是通过“信号槽”来完成的,有些特殊情况下,信号槽不一定能搞定(某个用户的动作行为,Qt没有提供对应的信号……)此时就需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑。开发事件机制给咱们程序员,咱们就可以根据实际的需要进行更深度的定制化diy操作了。

用户进行了很多操作,就会产生很多的事件(当然也会产生很多的信号)

子类代表各种具体的事件。不同场景下,要关注的点是不一样,这些事件的子类中就会包含一些对应的不同属性。

 事件的处理

 让一段代码和某个事件关联起来,当事件触发的时候,就能指定到这段代码,之前信号槽这里通过connect来完成上述关联的。对于事件来说,还不太一样。

让当前的类,重写某个事件处理函数。这里用到的是多态机制。创建子类继承自Qt已有的类。在子类中重写父类的事件处理函数。后续事件触发过程中,就会通过多态这样的机制。执行到咱们自己写的子类的函数中。

处理一下鼠标进入和鼠标离开的事件。enterEvent和leaveEvent。

代码:这里需要创建QLabel的子类重写enterEvent和leaveEvent

上述代码虽然重写了这两函数,但是还有点问题。当前在界面上创建的这个lable是一个咱们自己定义的Label类的实例。才会执行到。我们可以提升类。

一定要确保你的类名以及头文件的名字和上述自定义的类名头文件都匹配。通过提升为这样的方式,就可以把Qt Designer中拖上去的控件的类型,转换成自定义的控件类型。

给女神表白的程序,鼠标一进去按钮就跑了。可以自己试着实现一下。

代码:

处理鼠标事件

通过事件获取到鼠标点击的位置

mousePressEvent这个函数,按下左键,右键,滚轮都能触发。有的鼠标还带有前进后退侧键。也是可以触发。但是还有的鼠标,有更多的按键,更多的按键就不一定了。

mouseReleaseEvent释放鼠标事件

像clicked这样的信号,就相当于一次按下和释放事件。

mouseDoubleClickEvent双击鼠标事件

比如有的程序,可能是单击有一些逻辑,双击有另一些逻辑。如果我们没注意,可能双击操作就能触发单机的逻辑,可能就有bug

mouseMoveEvent移动鼠标事件

刚才重写鼠标事件的操作,都是在自定义的Label中完成的,此时鼠标只有在Label范围内进行动作的时候,才能捕获到。

也可以把这些操作直接放到Widget(QWidget子类)来完成,这样的化,鼠标在整个窗口中进行各种动作都能获取到了。

鼠标移动不同于鼠标按下,随便移动一下鼠标,就会产生出大量的鼠标移动事件,当你进行捕获事件的时候,尤其是在这里再进行一些复杂逻辑的时候,程序负担就很重,很容易产生卡顿之类的情况。Qt为了保证程序的流畅性。默认情况下不会对鼠标移动进行追踪,鼠标移动的时候不会调用mouseMoveEvent,除非显示告诉Qt就要追踪鼠标的位置。可以使用setMouseTracing函数设为true。

WheelEvent鼠标滚轮的滚轮动作

代码:

处理键盘事件

QShortCut 这是信号槽机制封装过,获取键盘按键的方式。站在更底层的角度,也可以通过事件获取当前用户键盘按下的情况 

keyPressEvent(QKeyEvent*); 看你捕获键盘按键是在哪个控件的范围内。

如果组合键呢,把这些用来搭配组合键的功能键,单独拎出来了,modifiers(修饰符)。

定时器事件

QTimer实现了定时器功能。再QTimer背后是QTimerEvent定时器事件进行支撑的。QObject提供了一个timerEvent这个函数。搭配startTimer启动定时器和killTimer关闭定时器。

代码:

使用timeEvent比QTimer还是要更复杂一些。需要手动管理timerId,还需要区分这次函数调用是哪个timer引起的。后续实际开发中,还是使用QTimer即可。

窗口移动和大小改变事件

moveEvent窗口移动时触发的事件

resizeEvent窗口大小改变时触发的事件

事件分发/事件过滤属于Qt事件机制背后的一些逻辑,Qt也把这部分内容提供了一些API让程序员有更多的可操作空间。重写event函数,直接获取到所有的事件。杀伤力比较广,不当使用可能对现有的逻辑(现有的事件体系造成一些负面影响)。

二、文件

C语言中,fopen打开文件,fread fwrite读写文件,fclose关闭文件。C++中,fstream打开文件,<< >> 读写文件。close关闭文件。Linux中,open打开文件,read,write读写文件。close关闭文件。系统调用一般开发的时候很少会直接使用,主要时理解文件操作背后的原理。

Qt也提供了一套文件操作。Qt中使用上述的集中方案来读写文件,也是完全可以的(Linux这一套,局限于Linux系统,Windows上的Qt就需要使用Windows API)。但Qt还是又封装了一套。Qt诞生的太早了,C++还没有标准化的概念。咱们在编写Qt程序的时候,更推荐使用Qt自己提供的这一套文件操作,可以和QString和Qt内置的类可以很好的配合。

Qt中的文件操作,核心操作也是这几个部分,打开,读,写,关闭。

QFile类,完成上述文件操作。

QTemporaryFile这个对象销毁,对应的文件就自动删除了。要写入大量的数据,写文件的时候,往往会把旧的文件先清空,再写。万一新的数据写一半,结果出错了,新的数据用不了,旧的数据也无了。如果你要想写一个文件,会自动的把内容先写到临时文件里(不会破坏原有的文件),等到所有内容写完了之后,再把旧文件自动删掉。并且用新的文件替换旧的文件

QFile类的使用

1、打开 open

2、读 read/readline/readall

QByteArray很方便的转成QString

3、写 write

4、关闭 close

关闭本质上是要释放文件描述符表中的表项。文件描述符表存在上限,如果一直打开,不关闭的话就可能会使文件描述符表被占满,后续再尝试打开,就打开不了了。

代码:

QString text = file.readAll()。

需要确保打开的文件是一个文本文件才可以,如果是二进制文件,交给QString就不合适了。

QFileInfo的使用

它可以获取到Qt的文件的相关属性,类似于这样的功能,在C/C++标准库本来是没有的所以要想使用类似的功能往往要使用系统api。C++17引入了模块,filesystem。

代码:

三、Qt多线程

Qt多线程和Linux中的线程本质是一个东西。

Qt中的多线程的API,Linux中学过的多线程API,Linux系统提供的pthread库。Qt中针对系统提供的线程API重新封装了。C++11中,也引入了线程std::thread。Qt中的多线程API,还要更好一点,其实参考了Java中的线程库API 的设计方式。

QThread要想创建线程,就需要创建这样类的实列。创建线程的时候,需要重点指定线程的入口函数,创建一个QThread的子类,重写其中的run函数,起到指定入口函数的方式(多态)。

start这个操作就是真正调用系统API创建线程。新的线程创建出来之后自然就会自动的执行run函数。

代码:之前基于定时器,写过倒计时这样的程序。也可以通过线程,来完成类似的功能。定时器内部本质上也是可以基于多线程来实现的。

由于存在线程安全问题。多个线程同时对于界面的状态进行修改,此时就会导致界面就出错了。Qt选择了一刀切,针对界面的空间状态进行任何修改,务必在主线程中执行。 

QThread应用场景

之前学习多线程,主要还是站在服务器开发的角度来看待的。当时谈到的多线程,最主要的目的,是为了充分利用多核CPU的计算资源。双路CPU(一个主板上面有两个CPU)在客服端多线程仍然有意义,侧重点不同了。相比之下,客户端中的多线程主要是用于通过多线程的方式,执行一些耗时的等待IO的操作,避免主线程被卡死,避免对用户造成一些不好的体验。客户端经常会和服务器进行网络通信,比如客户端要上传/下载一个很大的文件,传输需要消耗好久。密集的IO操作。这种密集IO就会使程序被系统阻塞,挂起。一旦进程被挂起了,此时意味着用户进行的各种操作,程序就无法响应。因此相比之下更好的做法,使用单独的线程,来处理这种密集的IO操作,要挂起也是挂起这个新的线程,主线程要负责事件循环,负责处理用户的各种操作。此时主线程仍然可以继续工作,继续响应用户的各种操作。

Qt中的锁

线程安全问题,多线程程序太复杂了。加锁 把多个线程要访问的公共资源,通过锁保护起来 =》把并发执行编程串行执行。Linux中mutex为互斥量。C++11引入了std::mutex。Qt同样也提供了对应的锁,来针对系统提供的锁进行封装。QMutex lock方法加锁,unlock解锁。

代码:

注意这里的锁需要使用static进行修饰,否则两个线程访问到的就不是同一个锁的资源。就无法起到互斥的作用。

这里的锁很容易忘记释放,忘记unlock,实际开发中,加锁之后,设计到的逻辑可能很复杂。下面很容易就忘记释放锁。C++中引入智能指针来解决上述问题的。C++11引入了std::lock_guard相当于是std::mutex的智能指针,借助RAII机制。

上述机制Qt也继承过来了。QMutexLocker

Qt的锁和C++标准库中的锁,本质上都是封装的系统提供的锁。编写多线程程序的时候,可以使用Qt的锁,也可以使用Qt的锁,也可以使用C++的锁。C++的锁能不能锁Qt的线程,也是可以的。虽然混着用也行,一般不建议。还提供了读写锁的问题。

条件变量和信号量

条件变量和信号量,和Linux中谈到的条件变量和信号量完全一致。多个线程之间的调度是无序的,为了能够一定程度的干预线程之间的执行顺序,引入条件变量,QWaitConditon,wait,wake,wakeAll,mutex.lock();wait中就会先释放锁 + 等待。要想释放锁,前提就是先获取到锁。

这里要使用while判定而不是if,唤醒之后需要再确认一下,当前条件是否真的成立了,wait可能被提前唤醒(可能被信号打断了)

信号量,其实还可以进行进程之间的控制,当然也同样可以作为同一个进程内部的线程之间通信方式。计数器,描述了可用资源的个数。QSemaphore semaphore(2),

分别代表PV操作。

四、网络编程

Qt网络编程,网络编程操作系统提供的一组提供API(Socket API)C++标准库中,并没有提供网络编程的api的封装。进行网络编程的时候,本质上是在编写应用层代码,需要传输层提供支持。Qt提供了两套API,UDP和TCP。使用Qt网络编程的API,需要现在.pro文件中添加network模块。之前学过的Qt的各种控件,各种内容,都是包含在QtCore模块中(已经添加到.pro文件中Q)。

为什么Qt要划分出这些模块呢?

Qt本身是一个非常庞大,包罗万象的框架。如果把所有的Qt的功能放到一起,即使咱们就只是写一个简单的hello world,此时生成的可执行程序也会非常庞大。所以我们进行了模块化处理,其他的功能分别封装成不同的模块,默认情况下这些额外的模块不会参与编译。需要在.pro文件中,引入对应的模块,才能把对应功能给编译加载进来。

Qt其实提供了静态库的版本和动态库的版本。

UDPSocket

当socket收到请求的时候,QUdpSocket就会触发这个信号,此时可以在槽函数里完成读取请求的操作了。基于信号槽,就天然达成了事件驱动这样的一种网络编程方式。

代码:写一个带有界面的Udp回显服务器,Qt也是完全可以编写控制台程序的。

server

client

quint16 端口号本质上是一个2字节的无符号整数。本质上就是一个unsigned short。虽然short通常都是2个字节,但是C++标准中没有明确规定这一点,只是说short不应该少于2个字节。所以为了规避这个问题,Qt中引入了quint16这样的类型。

 

TCPSocket

TCP有连接,可靠传输,面向字节流,全双工。因此TCP的代码,要比UDP的代码要复杂。

事件循环: 简单理解,可以认为是Qt程序内部,带有一个生物钟这样的东西,周期性的执行一些逻辑。

代码:TCP回显服务器

server

 上述代码其实不够严谨,作为回显服务器是已经够了的。实际使用TCP的过程中,TCP是面向字节流的。一个完整的请求,可能会分成多段字节数组进行传输,这就会导致上层读取不完整的报文,从而导致了粘包问题。

释放文件描述符

client

之前在学习Linux时写的TCP的回显服务器的时候,遇到了问题,多个客户端同时访问的时候就只有一个生效,后来引入了多线程,每个客户端安排一个单独的线程,问题才得到改善。之前写的那个程序,之所以出现上述问题,和TCP,多线程都没啥关系,从来没有说TCP服务器必须使用多线程编写,之前存在那个问题的本质原因是写双重循环,里层循环没有即使结束,导致外层循环不能快速的第二次调用到accept,导致第二个客户端无法进行处理了。引入多线程,本质上就是把双重循环,化简成两个独立的循环。咱们Qt的服务器程序中,其实一个循环也没有写,是通过Qt内置的信号槽机制来驱动的。一个正经的TCP服务器,不太可能会用Qt来写,服务器一般都是不需要图形界面的。

HTTP客户端

HTTP使用比TCP/UDP更多一些。Qt中也提供了HTTP的客户端。HTTP协议本质上也就是基于TCP协议实现的。实现一个HTTP客户端/服务器,本质上也就是基于TCP Socket进行封装的。

Qt只是提供了HTTP客户端,而没有提供HTTP服务器的库。

 代码:

 实际开发中,HTTP Client获取到的数据,也不一定是HTML,更大的可能性是客户端开发和服务器开发约定好交互的数据格式,按照约定的格式,客户端拿到之后,进行解析,并显示到界面上。

五、多媒体操作

播放声音

需要先引入multimedia模块。需要使用到QSound类,play()来进行播放。只能播放.wav格式的音频文件。

代码:

小结:

1、Qt事件机制

2、文件操作

3、多线程

4、网络编程

5、多媒体


原文地址:https://blog.csdn.net/m0_67635008/article/details/142613167

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!