5、qt系统相关

文章目录

    • [1. Qt 事件](#1. Qt 事件)
    • [1.1 事件介绍](#1.1 事件介绍)
    • [1.2 事件的处理](#1.2 事件的处理)
    • [1.3 按键事件](#1.3 按键事件)
      • [1.3.1 单个按键](#1.3.1 单个按键)
    • [1.4 ⿏标事件](#1.4 ⿏标事件)
      • [1.4.1 ⿏标单击事件](#1.4.1 ⿏标单击事件)
      • [1.4.2 ⿏标释放事件](#1.4.2 ⿏标释放事件)
      • [1.4.3 ⿏标双击事件](#1.4.3 ⿏标双击事件)
      • [.4.4 ⿏标移动事件](#.4.4 ⿏标移动事件)
      • [1.4.5 滚轮事件](#1.4.5 滚轮事件)
    • [1.5 定时器](#1.5 定时器)
      • [1.5.1 QTimerEvent 类](#1.5.1 QTimerEvent 类)
      • [1.5.2 QTimer 类](#1.5.2 QTimer 类)
      • [1.5.3 获取系统⽇期及时间](#1.5.3 获取系统⽇期及时间)
    • [1.6 事件分发器](#1.6 事件分发器)
      • [1.6.1 概述](#1.6.1 概述)
      • [1.6.2 事件分发器⼯作原理](#1.6.2 事件分发器⼯作原理)
    • [1.7 事件过滤器](#1.7 事件过滤器)
  • 扩展
  • [2. Qt ⽂件](#2. Qt ⽂件)
    • [2.1 Qt ⽂件概述](#2.1 Qt ⽂件概述)
    • [2.2 输⼊输出设备类](#2.2 输⼊输出设备类)
    • [2.3 ⽂件读写类](#2.3 ⽂件读写类)
    • [2.4 ⽂件和⽬录信息类](#2.4 ⽂件和⽬录信息类)
  • [3. Qt 多线程](#3. Qt 多线程)
    • [3.1 Qt 多线程概述](#3.1 Qt 多线程概述)
    • [3.2 QThread 常⽤ API](#3.2 QThread 常⽤ API)
    • [3.3 使⽤线程](#3.3 使⽤线程)
    • [3.4 线程安全](#3.4 线程安全)
      • [3.4.1 互斥锁](#3.4.1 互斥锁)
      • [3.4.2 条件变量](#3.4.2 条件变量)
      • [3.4.3 信号量](#3.4.3 信号量)
    • [4. Qt ⽹络](#4. Qt ⽹络)
    • [4.1 UDP Socket](#4.1 UDP Socket)
      • [4.1.2 回显服务器](#4.1.2 回显服务器)
      • [4.1.3 回显客⼾端](#4.1.3 回显客⼾端)
    • [4.2 TCP Socket](#4.2 TCP Socket)
      • [4.2.1 核⼼ API 概览](#4.2.1 核⼼ API 概览)
      • [4.2.3 回显客⼾端](#4.2.3 回显客⼾端)
    • [4.3 HTTP Client](#4.3 HTTP Client)
      • [4.3.2 代码⽰例](#4.3.2 代码⽰例)
    • [4.4 其他模块](#4.4 其他模块)
  • [5. Qt ⾳视频](#5. Qt ⾳视频)
    • [5.1 Qt ⾳频](#5.1 Qt ⾳频)
      • [5.1.1 核⼼API概览](#5.1.1 核⼼API概览)
      • [5.1.2 ⽰例](#5.1.2 ⽰例)
    • [5.2 Qt 视频](#5.2 Qt 视频)
      • [5.2.1 核⼼API概览](#5.2.1 核⼼API概览)
      • [5.2.2 ⽰例](#5.2.2 ⽰例)
  • 总结

1. Qt 事件

类比

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

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

信号槽就是对于事件的进一步封装。

事件是信号槽的底层机制。
实际开发程序过程中,绝大部分和用户进行的交互都是通过"信号槽"来完成的,有些特殊情况下,信号槽不一定能搞定,(某个用户的动作行为,Qt没有提供对应的信号...)此时需要通过重写事件处理函数的形式,来手动处理事件的响应逻辑。

开发事件机制给咱们程序员,咱们程序员就可以根据实际的需要进行更深度的定制化DIY操作。

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

1.1 事件介绍

**事件是应⽤程序内部或者外部产⽣的事情或者动作的统称。**在 Qt 中使⽤⼀个对象来表⽰⼀个事件。所有的 Qt 事件均继承于抽象类 QEvent。事件是由系统或者 Qt 平台本⾝在不同的时刻发出的。当⽤⼾按下⿏标、敲下键盘,或者是窗⼝需要重新绘制的时候,都会发出⼀个相应的事件。⼀些事件是在⽤⼾操作时发出,如键盘事件、⿏标事件等,另⼀些事件则是由系统本⾝⾃动发出,如定时器事件。常⻅的 Qt 事件如下:

由上图可知,不同场景下,需要关注的点是不一样的。这些事件的子类中就会包含一些对应的不同属性。

常⻅事件描述:

事件名称 描述

⿏标事件 ⿏标左键、⿏标右键、⿏标滚轮,⿏标的移动,⿏标按键的按下和松开

键盘事件 按键类型、按键按下、按键松开

定时器事件 定时时间到达

进⼊离开事件 ⿏标的进⼊和离开

滚轮事件 ⿏标滚轮滚动

绘屏事件 重绘屏幕的某些部分

显⽰隐藏事件 窗⼝的显⽰和隐藏

移动事件 窗⼝位置的变化

窗⼝事件 是否为当前窗⼝

⼤⼩改变事件 窗⼝⼤⼩改变

焦点事件 键盘焦点移动

拽事件 ⽤⿏标进⾏拖拽

1.2 事件的处理

事件处理⼀般常⽤的⽅法为:重写相关的 Event 函数。

让一段代码和某个事件关联起来。当事件触发的时候,就能指定到这段代码。
事件的处理 :让当前的类,重写某个事件处理函数。
某个事件处理函数:这里运用的是"多态"机制创建子类,继承自Qt已有的类。在子类中重写父类的事件处理函数;后续事件触发过程中,就会通过多态这样的机制,执行到咱们自己写的子类的函数中。

在 Qt 中,⼏乎所有的 Event 函数都是虚函数,所以可以重新实现。如:在实现⿏标的进⼊和离开事件时,直接重新实现 enterEvent() 和 leaveEvent() 即可。enterEvent() 和 leaveEvent() 函数原型如下

⽰例1:

1、新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰;

2、设计 UI ⽂件,如下图⽰;

3、在项⽬中新添加⼀个类:MyLabel;

先选中项⽬名称 QEvent,点击⿏标右键,选择 add new ... ,弹出如下对话框:

4、选择:Choose ... ,弹出如下界⾯:

5、此时项⽬中会新添加以下两个⽂件:

6、在帮助⽂档中查找对应的内容;

7、点击 "显⽰" 之后,出现如下内容:

8、复制 enterEvent() ,粘贴在项⽬⽂件 "mylabel.h" 中;

9、重写 enterEvent() ⽅法;

10、在 UI ⽂件中选中 Label,右键 ------> 提升为...

11、当点击 "提升为... " 之后,弹出如下对话框:

12、修改基类:

13、执⾏效果如下:当⿏标进⼊设计好的标签之后,就会在应⽤程序输出栏中打印:⿏标进⼊

实践(重写enterevent和leaveevent):

cpp 复制代码
#include "label.h"
#include<QDebug>

Label::Label(QWidget*parent):QLabel(parent)//继承关系
{

}

void Label::enterEvent(QEvent *event)
{
    (void) event;
    qDebug()<<"enterEvent";
}

void Label::leaveEvent(QEvent *event)
{
    (void) event;
    qDebug()<<"leaveEvent";
}

⽰例2:当⿏标点击时,获取对应的坐标值;

1、在上述⽰例的基础上,在 mylabel.h 中声明 mousePressEvent() ⽅法;

2、在 mylabel.cpp 中重写 mousePressEvent() ⽅法;

实现效果如下:

⽰例:⿏标左键点击时,打印对应的坐标值,⿏标右键点击时,打印基于屏幕的坐标

1.3 按键事件

Qt 中的按键事件是通过 QKeyEvent 类来实现的。当键盘上的按键被按下或者被释放时,键盘事件便

会触发。在帮助⽂档中查找 QKeyEvent 类如下:

查找按键事件中所有的按键类型:在帮助⽂档中输⼊:Qt::Key,如下图:

1.3.1 单个按键

⽰例:当某个按键被按下时,输出:某个按键被按下了;

1、新建项⽬,在头⽂件 "widget.h" 中声明虚函数 keyPressEvent();如下图:

2、在 "widget.cpp" ⽂件中重写 keyPressEvent() 虚函数;

1.3.2 组合按键

在 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 ⽤于在输⼊法 组之间 切换

⽰例:

键盘实践:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QKeyEvent>
#include<QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::keyPressEvent(QKeyEvent *event)
{
    //qDebug()<<event->key();
    if(event->key()==Qt::Key_A&&event->modifiers()==Qt::ControlModifier)//按下a和ctrl
    {
        qDebug()<<"按下ctrl+A键";
    }
}

1.4 ⿏标事件

在 Qt 中,⿏标事件是⽤ QMouseEvent 类来实现的。当在窗⼝中按下⿏标或者移动⿏标时,都会产⽣

⿏标事件。

利⽤ QMouseEvent 类可以获取⿏标的哪个键被按下了以及⿏标的当前位置等信息。在 Qt 帮助⽂档中

查找QMouseEvent类 如下图⽰:

1.4.1 ⿏标单击事件

在 Qt 中,⿏标按下是通过虚函数 mousePressEvent() 来捕获的。mousePressEvent() 函数原型如

下:

virtual protected\] void QWidget::mousePressEvent(QMouseEvent \*event) ⿏标左右键及滚的表⽰如下: Qt::LeftButton ⿏标左键 Qt::RightButton ⿏标右键 Qt::MidButton ⿏标滚轮 ⽰例1:⿏标左键 1、在 "widget.h" 头⽂件中声明⿏标按下事件; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c7ccf58f9c7143808fc75e7b3cfda5d2.png) 2、在 "widget.cpp" ⽂件中重新实现 mousePressEvent() 函数; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/88ecdbf217bb4643a1c005a3aa799e09.png) 实现效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e4e59d777bb8494b8ee4910e3a2c9820.png) ⽰例2:⿏标右键: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/664894e988f44b119f796a61b1324f82.png) 鼠标按下事件的实现: ```cpp #include "label.h" #include #include Label::Label(QWidget*parent):QLabel(parent) { } void Label::mousePressEvent(QMouseEvent *event) { if(event->button()==Qt::LeftButton) { qDebug()<<"按下左键"; } else if(event->button()==Qt::RightButton) { qDebug()<<"按下右键"; } //当前event对象就包含了鼠标点击位置的坐标 qDebug()<x()<<","<y(); //globalX和globalY是以屏幕左上方为原点,获取的坐标 qDebug()<globalX()<<","<globalY(); } ``` 实现效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55238fe5714d46f5ab1957db2ec24b8f.png) ⽰例3:⿏标滚轮 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/216e2b77c4b549f3a880d1de8020442b.png) 实现效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a544756c7df44cfbb546e33fc707b752.png) #### 1.4.2 ⿏标释放事件 ⿏标释放事件是通过虚函数 **mouseReleaseEvent()** 来捕获的。mouseReleaseEvent() 函数原型如 下:\[virtual protected\] void QWidget::mouseReleaseEvent(QMouseEvent \*event) ⽰例: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3fd1697fc73849d086bffbbf4d15a39a.png) 执⾏效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/065112d82af24473a39da1af9abff89a.png) #### 1.4.3 ⿏标双击事件 ⿏标双击事件是通过虚函数:**mouseDoubleClickEvent()** 来实现的。mouseDoubleClickEvent() 函数原型如下: \[virtual protected\] void QWidget::mouseDoubleClickEvent(QMouseEvent \*event) ⽰例:⿏标左键双击 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/51559891c6114dd3a690dc179056b86d.png) 执⾏效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c49cf1baa4b14a9abb52d4fb8351f362.png) 鼠标释放和双击 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/079234e356714cd0960a78cc5334104c.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a048f1e38c6243deb5f65d75c52fd55b.png) ```cpp #include "label.h" #include #include Label::Label(QWidget*parent):QLabel(parent) { } void Label::mousePressEvent(QMouseEvent *event) { if(event->button()==Qt::LeftButton) { qDebug()<<"按下左键"; } else if(event->button()==Qt::RightButton) { qDebug()<<"按下右键"; } //当前event对象就包含了鼠标点击位置的坐标 qDebug()<x()<<","<y(); //globalX和globalY是以屏幕左上方为原点,获取的坐标 qDebug()<globalX()<<","<globalY(); } void Label::mouseReleaseEvent(QMouseEvent *event) { if(event->button()==Qt::LeftButton) { qDebug()<<"释放左键"; } else if(event->button()==Qt::RightButton) { qDebug()<<"释放右键"; } } void Label::mouseDoubleClickEvent(QMouseEvent *event) { if(event->button()==Qt::LeftButton) { qDebug()<<"双击左键"; } else if (event->button()==Qt::RightButton) { qDebug()<<"双击右键"; } } ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9d27496e04a74698b821c6140d1e52bc.png) #### .4.4 ⿏标移动事件 ⿏标移动事件是通过虚函数:mouseMoveEvent() 来实现的。同时为了实时捕获⿏标位置信息,需要通过函数 setMouseTracking() 来追踪⿏标的位置。mouseMoveEvent()函数原型如下: \[virtual protected\] void QWidget::mouseMoveEvent(QMouseEvent \*event) setMouseTracking()函数原型如下: void setMouseTracking(bool enable) 说明: setMouseTracking() 函数默认是 false,需要设置为 true,才能实时捕获⿏标位置信息。否则只有当⿏标按下时才能捕获其位置信息。 ⽰例: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e6278636d6e64c9e8dc42e9aaa0f5135.png) 执⾏效果: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5f7fb03aab214460b636a5d19ad1af05.png) eg: ```cpp #include "widget.h" #include "ui_widget.h" #include #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); //把这个选项设置为true,才能实现追踪鼠标的移动位置 this->setMouseTracking(true); } Widget::~Widget() { delete ui; } void Widget::mouseMoveEvent(QMouseEvent *event) { qDebug()<x()<y(); } ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/791be396e0fe41a784da90fb698d0289.png) #### 1.4.5 滚轮事件 在 Qt 中,⿏标滚轮事件是通过 QWheelEvent 类来实现的。滚轮滑动的距离可以通过 delta() 函数获 取。delta() 函数原型如下: int QGraphicsSceneWheelEvent::delta() const **其中返回值代表滚轮滑动的距离** 。正数表⽰滚轮相对于⽤⼾向前滑动,负数表⽰滚轮相对于⽤⼾向后 滑动。 ⽰例: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cb82fb147e7748b784022c4fb006a5f1.png) 执⾏效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cf5a13e19b0448bc85d514738532379d.png) eg: ```cpp #include "widget.h" #include "ui_widget.h" #include #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); total =0; } Widget::~Widget() { delete ui; } void Widget::wheelEvent(QWheelEvent *event) { total+=event->delta(); qDebug()<setupUi(this); //开启定时器事件 //此处timerId是一个定时器的身份标识 timerId = this->startTimer(1000);//每秒减一 } Widget::~Widget() { delete ui; } void Widget::timerEvent(QTimerEvent *event) { //如果一个程序中存在多个定时器(startTimer创建的定时器),此时每个定时器都会触发timeEvent函数 //先判断一下这次触发是否是想要的定时器触发的 if(event->timerId()!=this->timerId)//? { //如果不是我们的定时器触发的,就直接忽略 //当前程序中只有这一个定时器 return; } //是咱们自己搞得定时器 int value = ui->lcdNumber->intValue();//在lcd if(value<=0) { //停止定时器 this->killTimer(this->timerId);// } value-=1; ui->lcdNumber->display(value);//lcd 是display } ``` ### 1.6 事件分发器 #### 1.6.1 概述 在 Qt 中,事件分发器(Event Dispatcher) 是⼀个核⼼概念,⽤于处理 GUI 应⽤程序中的事件。事件分 发器负责将事件从⼀个对象传递到另⼀个对象,直到事件被处理或被取消。每个继承⾃ QObject类 或 QObject类 本⾝都可以在本类中重写 bool event(QEvent \*e) 函数,来实现相关事件的捕获和拦截。 #### 1.6.2 事件分发器⼯作原理 在 Qt 中,我们发送的事件都是传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函 数。所有的事件都会进⼊到这个函数⾥⾯,那么我们处理事件就要重写这个 event() 函数。event() 函 数本⾝不会去处理事件,⽽是根据 事件类型(type值)调⽤不同的事件处理函数。事件分发器就是⼯ 作在应⽤程序向下分发事件的过程中,如下图: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/719415feb1dd4157a9f48a0892455bf8.png) 如上图,事件分发器⽤于分发事件。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是 通过 bool event(QEvent \*e) 函数来实现。其返回值为布尔类型,若为 ture,代表拦截,不向下分 发。 Qt 中的事件是封装在 QEvent类 中,在 Qt 助⼿中输⼊ QEvent 可以查看其所包括的事件类型,如下图⽰ ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1083218f91a541df85c49ed58024166d.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/2d63c761453d4971b4b1512f5a997f97.png) ⽰例: 1、在 "widget.h" 头⽂件中声明 ⿏标点击事件 和 事件分发器;如下图⽰: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ea9f425d36ad4525b732562f8ed98368.png) 2、在 "widget.cpp" ⽂件中实现 ⿏标点击事件 和 拦截事件; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/25511eaae1f641a181df0354d937e3b1.png) 执⾏结果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b40fcbf40f5f4acd989ffa7292b66ee7.png) ### 1.7 事件过滤器 在 Qt 中,⼀个对象可能经常要查看或拦截另外⼀个对象的事件,如对话框想要拦截按键事件,不让别 的组件接收到,或者修改按键的默认值等。通过上⾯的学习,我们已经知道,Qt 创建了 QEvent事件 对象之后,会调⽤QObject 的 event()函数 处理事件的分发。显然,我们可以在 event()函数 中实现拦 截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很 多个event()函数。这当然相当⿇烦,更不⽤说重写 event()函数还得⼩⼼⼀堆问题。好在 Qt 提供了另 外⼀种机制来达到这⼀⽬的:事件过滤器。 事件过滤器是在应⽤程序分发到 event事件分发器 之前,再做⼀次更⾼级的拦截。如下图⽰: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1f44464a7fcf4160af9084b6a89e7044.png) 事件过滤器的⼀般使⽤步骤: 1、安装事件过滤器; 2、重写事件过滤器函数:eventfilter() 。 ⽰例: 1、新建 Qt 项⽬,基类选择 QWidget,同时勾选 UI 界⾯⽂件,如下图⽰; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/92f6fa557ba34acda2cae0e56adf31b9.png) 2、设计 UI ⽂件,如下图⽰; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/75a9a8072beb4f93a66685b2294d0226.png) 3、在项⽬新添加⼀个类:MyLabel; 先选中项⽬名称 QEvent,点击⿏标右键,选择 add new ... ,弹出如下对话框: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c01feb300168489a96baca1836c01a2d.png) 4、选择:Choose ... ,弹出如下界⾯: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fccf10fabff14b8182904f9f6b4cb78b.png) 5、此时项⽬中会新添加以下两个⽂件: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/e305c573cc2841c0a2f9b4ed24127dec.png) 6、在 UI ⽂件中选中 Label,右键 ------\> 提升为... ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/352cd6608a1849b996d9829c07f478ed.png) 7、当点击 "提升为... " 之后,弹出如下对话框: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/41a5fcbfb3964825ad9d4505da1b8791.png) 6、在 "mylabel.h" 中声明 ⿏标点击事件 和 事件分发器; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1c9e324ffb054688b8c4c38789a130ab.png) 7、在 "mylabel.cpp" ⽂件中实现⿏标点击事件和事件分发器; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dc2480db87f44da39ade38e3f606c3cd.png) 8、在 "widget.h" 头⽂件中声明事件过滤器函数; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f1b50349addf4385982eb305644854d0.png) 9、在 "widget.cpp" ⽂件中实现事件过滤器的两个步骤; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9feb290ee0444dcb99c3465c6f9a3014.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cc589e18420844b684e2b1985391b4c9.png) 执⾏结果如下所⽰: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3ae3680403334b87a68c54280554db74.png) 过滤器小结: 事件分发/事件过滤 属于Qt事件机制背后的一些逻辑。 Qt也把这部分内容提供了一些API让程序员有更多的操作空间。 事件分发☞重写event函数,直接获取到所有的事件。 event函数☞杀伤力比较广,不当使用可能对现有逻辑(现有的事件体系造成一些负面影响) 当然,有的场景中,比如要禁用用户的某种操作,可以考虑使用事件过滤机制。 ## 扩展 moveEvent窗口移动时出发的事件 resizeEvent窗口大小改变时触发的事件 ## 2. Qt ⽂件 类比 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内置的类可以很好的配合。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c020cb5915b24a6099fef8c603961216.jpg) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dcd38021f4cb41b1b8e5dbad7a487267.jpg) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1b78b3ff93d0401ca6e890a4f1caf057.jpg) ### 2.1 Qt ⽂件概述 ⽂件操作是应⽤程序必不可少的部分。Qt 作为⼀个通⽤开发库,提供了跨平台的⽂件操作能⼒。 Qt 提供了很多关于⽂件的类,通过这些类能够对⽂件系统进⾏操作,如⽂件读写、⽂件信息获取、⽂件 复制或重命名等。 ### 2.2 输⼊输出设备类 在 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 设备 类的继承关系如下图所⽰: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/140b847beada41de86d9e71568d8547f.png) 上图中各类的说明如下: • QFile 是⽤于⽂件操作和⽂件数据读写的类,使⽤ QFile 可以读写任意格式的⽂件。 • QSaveFile 是⽤于安全保存⽂件的类。使⽤ QSaveFile 保存⽂件时,它会先把数据写⼊⼀个临时⽂ 件,成功提交后才将数据写⼊最终的⽂件。如果保存过程中出现错误,临时⽂件⾥的数据不会被写 ⼊最终⽂件,这样就能确保最终⽂件中不会丢失数据或被写⼊部分数据。 在保存⽐较⼤的⽂件或复 杂格式的⽂件时可以使⽤这个类,例如从⽹络上下载⽂件等。 • QTemporaryFile 是⽤于创建临时⽂件的类。使⽤函数 QTemporaryFile::open() 就能创建⼀个⽂件 名唯⼀的临时⽂件,==在 QTemporaryFile 对象被删除时,临时⽂件被⾃动删除。 == QsaveFile 要写入大量的数据,写文件的时候,往往会把旧的文件先清空,再写。万一新的数据写一半,结果出错了,新的数据用不了旧的数据也没了。 如果你想要写一个文件,会自动的把内容先写到临时文件里(不会破坏原有文件)。等到所有的内容都写完了之后,再把就文件自动删除,并且用新的文件替换旧的文件。这种策略是一个广泛常见的策略。 • QTcpSocket 和 QUdpSocket 是分别实现了 TCP 和 UDP 的类。 • QSerialPort 是实现了串⼝通信的类,通过这个类可以实现计算机与串⼝设备的通信。 • QBluetoothSocket 是⽤于蓝⽛通信的类。⼿机和平板计算机等移动设备有蓝⽛通信模块,笔记本 电脑⼀般也有蓝⽛通信模块。通过QBluetoothSocket类,就可以编写蓝⽛通信程。如编程实现笔 记本电脑与⼿机的蓝⽛通信。 • QProcess 类⽤于启动外部程序,并且可以给程序传递参数。 • QBuffer 以⼀个 QByteArray 对象作为数据缓冲区,将 QByteArray 对象当作⼀个 I/O 设备来读写。 ### 2.3 ⽂件读写类 在 Qt 中,⽂件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些⽤来读写⽂件的⽅法。对于⽂件的操作主要有: • 读数据:QFile 类中提供了多个⽅法⽤于读取⽂件内容;如 read()、readAll()、readLine()等。 • 写数据:QFile 类中提供了多个⽅法⽤于往⽂件中写内容;如 write()、writeData()等。 • 关闭⽂件:⽂件使⽤结束后必须⽤函数 close() 关闭⽂件。 访问⼀个设备之前,需要使⽤ open()函数 打开该设备,⽽且必须指定正确的打开模式,QIODevice 中 所有的打开模式由 QIODevice::OpenMode 枚举变量定义,其取值如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4c782bab62f84996884c487402439178.png) QIODevice::NotOpen 没有打开设备 QIODevice::ReadOnly 以只读⽅式打开设备 QIODevice::WriteOnly 以只写⽅式打开设备 QIODevice::ReadWrite 以读写⽅式打开设备 QIODevice::Append 以追加⽅式打开设备,数据将写到⽂件末尾 QIODevice::Truncate 每次打开⽂件后重写⽂件内容,原内容将被删除 QIODevice::Text 在读⽂件时,⾏尾终⽌符会被转换为 '\\n';当写⼊⽂件时,⾏尾终⽌符会被转换为 本地编码。如 Win32上为'\\r\\n'; QIODevice::Unbuffered ⽆缓冲形式打开⽂件,绕过设备中的任何缓冲区 QIODevice::NewOnly ⽂件存在则打开失败,不存在则创建⽂件 ⽰例1:读取⽂件内容 1. 新建 Qt 项⽬,在 UI ⽂件中拖⼊⼀个 LineEdit,⼀个pushButton,⼀个 TextEdit。当点击按钮 时,弹出窗⼝选择要读取的⽂件,并将读取到的内容在 TextEdit 中显⽰; ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cd35911fbc184aad8980062bba0af908.png) 2. 在 "widget.cpp" ⽂件中实现对应功能; ```cpp #include "widget.h" #include "ui_widget.h" #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); connect(ui->btn,&QPushButton::clicked,[=](){ QString path = QFileDialog::getOpenFileName(this,"打开⽂ 件","C:\\Users\\Lenovo\\Desktop"); ui->lineEdit->setText(path); QFile file(path); //path:代表⽂件路径this //打开⽂件 file.open(QIODevice::ReadOnly); //以只读的⽅式打开⽂件 QString str = file.readAll(); ui->textEdit->setText(str); //关闭⽂件 file.close(); }); } ``` 实现效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f781ebc9fb6949498d28615484c0f81b.png) ⽰例2:写⽂件 在上述⽰例的基础上修改 "widget.cpp" ⽂件; ```cpp Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); connect(ui->btn,&QPushButton::clicked,[=](){ QString path = QFileDialog::getOpenFileName(this,"打开⽂ 件","C:\\Users\\Lenovo\\Desktop"); ui->lineEdit->setText(path); QFile file(path); //path:代表⽂件路径 //打开⽂件 进⾏写操作 file.open(QIODevice::Append); //以追加的⽅式进⾏写 file.write("【这是⽰例!!!】"); //关闭⽂件 file.close(); }); } ``` 实现效果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3b96a8ecbb92465faf7ed2ef0804641c.png) eg:实现简易记事本(打开和保存功能) ```cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); this->setWindowTitle("简单的记事本"); //获取到菜单栏 QMenuBar *menuBar =this->menuBar();//必须是这个不然识别不了 //添加菜单 QMenu*menu = new QMenu("文件"); menuBar->addMenu(menu); //添加菜单项 QAction*action1 = new QAction("打开"); QAction*action2 = new QAction("保存"); menu->addAction(action1); menu->addAction(action2); //指定一个输入框 edit = new QPlainTextEdit();//创建输入框 QFont font; font.setPixelSize(20);//设置样式 edit->setFont(font); this->setCentralWidget(edit); //连接QAction的信号槽 connect(action1,&QAction::triggered,this,&MainWindow::handleAction1); connect(action2,&QAction::triggered,this,&MainWindow::handleAction2); } MainWindow::~MainWindow() { delete ui; } void MainWindow::handleAction1() { //1、先弹出"打开文件"对话框,让用户选择打开那个文件 QString path = QFileDialog::getOpenFileName(this); //2、把文件名显示到状态栏里 QStatusBar *statusBar = this->statusBar(); statusBar->showMessage(path); //3、根据用户选择的路径,构造一个QFile对象,并打开文件 QFile file(path); bool ret = file.open(QIODevice::ReadOnly); if(!ret) { //打开文件夹失败 statusBar->showMessage(path+"打开文件失败"); return; } //4、读取文件 QString text = file.readAll(); //5、关闭文件!! file.close(); //6、读到的内容设置到输入框中 edit->setPlainText(text); } void MainWindow::handleAction2() { //1、先弹出"保存文件"对话框 QString path = QFileDialog::getSaveFileName(this); //2、在状态栏中显示这个文件名 QStatusBar *statusBar = this->statusBar(); statusBar->showMessage(path); //3、根据用户选择的路径,构建一个QFile对象,并打开文件 QFile file(path); bool ret = file.open(QIODevice::WriteOnly); if(!ret) { //打开文件失败 statusBar->showMessage(path+"打开文件失败"); return; } //4、写文件 const QString &text = edit->toPlainText(); file.write(text.toUtf8()); //5、关闭文件 file.close(); } ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7c09dbb00b3f4389b8ab3efc752f21d1.png) ### 2.4 ⽂件和⽬录信息类 QFileInfo 是 Qt 提供的⼀个⽤于获取⽂件和⽬录信息的类,如获取⽂件名、⽂件⼤⼩、⽂件修改⽇期 等。QFileInfo类中提供了很多的⽅法,常⽤的有: • isDir() 检查该⽂件是否是⽬录; • isExecutable() 检查该⽂件是否是可执⾏⽂件; • fileName() 获得⽂件名; • completeBaseName() 获取完整的⽂件名; • suffix() 获取⽂件后缀名; • completeSuffix() 获取完整的⽂件后缀; • size() 获取⽂件⼤⼩; • isFile() 判断是否为⽂件; • fileTime() 获取⽂件创建时间、修改时间、最近访问时间等; ⽰例: 在 "widget.cpp" ⽂件中添加如下代码: ```cpp #include #include #include #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); connect(ui->btn,&QPushButton::clicked,[=](){ QString path = QFileDialog::getOpenFileName(this,"打开⽂ 件","C:\\Users\\Lenovo\\Desktop"); //QFileInfo ⽂件信息类 QFileInfo fileinfo(path); //⽂件名 qDebug() << "⽂件名为:" << fileinfo.fileName().toUtf8().data(); //⽂件后缀名 qDebug() << "后缀名为:" << fileinfo.suffix().toUtf8().data(); //⽂件⼤⼩ qDebug() << "⽂件⼤⼩为:" << fileinfo.size(); //⽂件路径 qDebug() << "⽂件路径为:" << fileinfo.path().toUtf8().data(); //判断是否为⽂件 qDebug() << "是否为⽂件:"<< fileinfo.isFile(); //⽂件创建时间 QDateTime time1 = fileinfo.fileTime(QFileDevice::FileBirthTime); qDebug() << "创建时间为:" << time1.toString("yyyy-MM-dd hh:mm:ss").toUtf8().data(); //⽂件的最后修改⽇期 QDateTime time2 = fileinfo.lastModified(); qDebug() << "最后修改时间为:"<< time2.toString("yyyy-MM-dd hh:mm:ss").toUtf8().data(); //判断是否为⽂件夹 qDebug() << "是否为⽬录:" << fileinfo.isDir(); }); } ``` 实现效果如下 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/24da295166ec42af8111dd070bd2c5ec.png) eg:FileInfo的使用 ```cpp #include "widget.h" #include "ui_widget.h" #include #include #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); } Widget::~Widget() { delete ui; } void Widget::on_pushButton_clicked() { //弹出文件对话框,并获取到文件的属性信息 QString path = QFileDialog::getOpenFileName(this); //构造出一个QFileInfo对象 QFileInfo fileInfo(path); //打印相关属性 qDebug()< //添加头⽂件 class TimeThread : public QThread { Q_OBJECT public: TimeThread(); void run(); //线程任务函数 signals: void sendTime(QString Time); //声明信号函数 }; #endif // TIMETHREAD_H /******************************** widget.h ********************************/ #ifndef WIDGET_H #define WIDGET_H #include #include //添加头⽂件 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_btn_clicked(); void showTime(QString Time); private: Ui::Widget *ui; TimeThread t; //定义线程对象 }; #endif // WIDGET_H /******************************** timethread.cpp ********************************/ #include "timethread.h" #include #include TimeThread::TimeThread() { } void TimeThread::run() { while(1) { QString time = QTime::currentTime().toString("hh:mm:ss"); qDebug() << time; emit sendTime(time); //发送信号 sleep(1); } } /******************************** widget.cpp ********************************/ #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); connect(&t,&TimeThread::sendTime,this,&Widget::showTime); } Widget::~Widget() { delete ui; } void Widget::on_btn_clicked() { t.start(); //开启线程 } void Widget::showTime(QString Time) { ui->label->setText(Time); } ``` 执⾏效果: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7bbf5efe8fa44f1aac3513c4175cc1af.png) 说明: 1、线程函数内部不允许操作 UI 图形界⾯,⼀般⽤数据处理; 2、connect() 函数第五个参数表⽰的为连接的⽅式,且只有在多线程的时候才意义。 connect() 函数第五个参数为 Qt::ConnectionType,⽤于指定信号和槽的连接类型。同时影响信号的 传递⽅式和槽函数的执⾏顺序。Qt::ConnectionType 提供了以下五种⽅式: Qt::AutoConnection 在 Qt 中,会根据信号和槽函数所在的线程⾃动选择连接类型。如果信号和槽函 数在同⼀线程中,那么使⽤ Qt:DirectConnection 类型;如果它们位于不同的 线程中,那么使⽤Qt::QueuedConnection 类型。 Qt::DirectConnection 当信号发出时,槽函数会⽴即在同⼀线程中执⾏。这种连接类型适⽤于信号和 槽函数在同⼀线程中的情况,可以实现直接的函数调⽤,但需要注意线程安全 性。 Qt::QueuedConnection 当信号发出时,槽函数会被插⼊到接收对象所属的线程的事件队列中,等待下 ⼀次事件循环时执⾏。这种连接类型适⽤于信号和槽函数在不同线程中的情 况,可以确保线程安全。 Qt::BlockingQueuedConnec tion 与 Qt:QueuedConnection 类似,但是发送信号的线程会被阻塞,直到槽函数 执⾏完毕,这种连接类型适⽤于需要等待槽函数执⾏完毕再继续的场景,但需 要注意可能引起线程死锁的⻛险。 Qt::UniqueConnection 这是⼀个标志,可以使⽤位或与上述任何⼀种连接类型组合使⽤。 ### 3.4 线程安全 实现线程互斥和同步常⽤的类有: • 互斥锁:QMutex、QMutexLocker • 条件变量:QWaitCondition • 信号量:QSemaphore • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock #### 3.4.1 互斥锁 互斥锁是⼀种保护和防⽌多个线程同时访问同⼀对象实例的⽅法,在 Qt 中,互斥锁主要是通过 QMutex类来处理。 • QMutex 特点:QMutex 是 Qt 框架提供的互斥锁类,⽤于保护共享资源的访问,实现线程间的互斥操作。 ⽤途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。 ```cpp QMutex mutex; mutex.lock(); //上锁 //访问共享资源 //... mutex.unlock(); //解锁 ``` • QMutexLocker 特点:QMutexLocker 是 QMutex 的辅助类,使⽤ RAII(Resource Acquisition Is Initialization)⽅式 对互斥锁进⾏上锁和解锁操作。 ⽤途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。 ```cpp QMutex mutex; { QMutexLocker locker(&mutex); //在作⽤域内⾃动上锁 //访问共享资源 //... } //在作⽤域结束时⾃动解锁 ``` • QReadWriteLocker、QReadLocker、QWriteLocker 特点: QReadWriteLock 是读写锁类,⽤于控制读和写的并发访问。 QReadLocker ⽤于读操作上锁,允许多个线程同时读取共享资源。 QWriteLocker ⽤于写操作上锁,只允许⼀个线程写⼊共享资源。 ⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提 供了更⾼效的并发访问⽅式。 ```cpp QReadWriteLock rwLock; //在读操作中使⽤读锁 { QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁 //读取共享资源 //... } //在作⽤域结束时⾃动解读锁 //在写操作中使⽤写锁 { QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁 //修改共享资源 //... } //在作⽤域结束时⾃动解写锁 ``` ⽰例1: ```cpp ******************************* myThread.h **********************************/ #ifndef MYTHREAD_H #define MYTHREAD_H #include #include class myThread : public QThread { Q_OBJECT public: explicit myThread(QObject *parent = nullptr); void run(); private: static QMutex mutex; //多个线程使⽤⼀把锁 static int num; //多个线程访问⼀个数据 }; #endif // MYTHREAD_H /******************************* myThread.cpp **********************************/ #include "mythread.h" #include QMutex myThread::mutex; int myThread::num = 0; myThread::myThread(QObject *parent) : QThread(parent) { } void myThread::run() { while(1) { this->mutex.lock(); //加锁 qDebug() << "Current Thread: " << this << ", Value: " << this->num++; this->mutex.unlock(); //解锁 QThread::sleep(1); //线程睡眠两秒 } } /******************************* mainwindow.h **********************************/ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H /******************************* mainwindow.cpp **********************************/ #include "mainwindow.h" #include "ui_mainwindow.h" #include "mythread.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); myThread *t1 = new myThread(this); myThread *t2 = new myThread(this); t1->start(); t2->start(); } MainWindow::~MainWindow() { delete ui; } ``` 执⾏效果: 两个线程使⽤⼀把锁,操作⼀个数据,数据会被两个线程依次打印:0、1、2、3、4 ... ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5aa61337fa9941f2b0e72682e683c87d.png) ⽰例2:在上述⽰例的基础上使⽤ QMutexLocker 锁。 ```cpp /******************************* myThread.cpp **********************************/ #include "mythread.h" #include QMutex myThread::mutex; int myThread::num = 0; myThread::myThread(QObject *parent) : QThread(parent) { } void myThread::run() { while (1) { //QMutexLocker:创建的时候加锁,当QMutexLocker局部销毁的时候解锁 { QMutexLocker lock(&this->mutex); qDebug() << "Current Thread: " << this << ", Value: " << this- >num++; } QThread::sleep(1);// 线程睡眠两秒 } } ``` #### 3.4.2 条件变量 在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒。 在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题。 特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。 ⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调 ```cpp QMutex mutex; QWaitCondition condition; //在等待线程中 mutex.lock(); //检查条件是否满⾜,若不满⾜则等待 while (!conditionFullfilled()) { condition.wait(&mutex); //等待条件满⾜并释放锁 } //条件满⾜后继续执⾏ //... mutex.unlock(); //在改变条件的线程中 mutex.lock(); //改变条件 changeCondition(); condition.wakeAll(); //唤醒等待的线程 mutex.unlock(); ``` #### 3.4.3 信号量 有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。 特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。 ⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。 ```cpp QSemaphore semaphore(2); //同时允许两个线程访问共享资源 //在需要访问共享资源的线程中 semaphore.acquire(); //尝试获取信号量,若已满则阻塞 //访问共享资源 //... semaphore.release(); //释放信号量 //在另⼀个线程中进⾏类似操作 ``` ### 4. Qt ⽹络 和多线程类似, Qt 为了⽀持跨平台, 对⽹络编程的 API 也进⾏了重新封装. 注意: • 实际 Qt 开发中进⾏⽹络编程, 也不⼀定使⽤ Qt 装的⽹络 API, 也有⼀定可能使⽤的是系 统原⽣ API 或者其他第三⽅框架的 API. 在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块. 添加之后要⼿动编译⼀下项⽬, 使 Qt Creator 能够加载对应模块的头⽂件. ### 4.1 UDP Socket 4.1.1 核⼼ API 概览 主要的类有两个. QUdpSocket 和 QNetworkDatagram QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件. 名称 类型 说明 对标原⽣ API bind(const QHostAddress\&, quint16) ⽅法 绑定指定的端⼝号. bind receiveDatagram() ⽅法 返回 QNetworkDatagram . 读取 ⼀个 UDP 数据报. recvfrom writeDatagram(const QNetworkDatagram\&) ⽅法 发送⼀个 UDP 数据报. sendto readyRead 信号 在收到数据并准备就绪后触发. ⽆ (类似于 IO 多路复⽤的通 知机制) QNetworkDatagram 表⽰⼀个 UDP 数据报. 名称 类型 说明 对标原⽣ API QNetworkDatagram(const QByteArray\&, const QHostAddress\& , quint16 ) 构造函 数 通过 QByteArray , ⽬标 IP 地址, ⽬标端⼝号 构造⼀个 UDP 数据报. 通常⽤于发送数据时. ⽆ data() ⽅法 获取数据报内部持有的数据. 返回 QByteArray ⽆ senderAddress() ⽅法 获取数据报中包含的对端的 IP 地 址. ⽆, recvfrom 包含了该功能. senderPort() ⽅法 获取数据报中包含的对端的端⼝号. ⽆, recvfrom 包含了该功 能. #### 4.1.2 回显服务器 1. 创建界⾯, 包含⼀个 QListWidget ⽤来显⽰消息. ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dabf3e08f98a41adb565fc19f9d0ba0c.png) 2. 创建 QUdpSocket 成员 修改 widget.h ```cpp class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private: Ui::Widget *ui; QUdpSocket* socket; } ``` 修改 widget.cpp, 完成 socket 后续的初始化 ⼀般来说, 要先连接信号槽, 再绑定端⼝ 如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这 个请求就有可能错过了. ```cpp Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); // 1. 设置窗⼝标题 this->setWindowTitle("服务器"); // 2. 实例化 socket socket = new QUdpSocket(this); // 3. 连接信号槽, 处理收到的请求 connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest); // 4. 绑定端⼝ bool ret = socket->bind(QHostAddress::Any, 9090); if (!ret) { QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString()); return; } } ``` 3. 实现 processRequest , 完成处理请求的过程 • 读取请求并解析 • 根据请求计算响应 • 把响应写回到客⼾端 ```cpp void Widget::processRequest() { // 1. 读取请求 const QNetworkDatagram& requestDatagram = socket->receiveDatagram(); QString request = requestDatagram.data(); // 2. 根据请求计算响应 const QString& response = process(request); // 3. 把响应写回到客⼾端 QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort()); ocket->writeDatagram(responseDatagram); // 显⽰打印⽇志 QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort()) + "] req: " + request + ", resp: " + response; ui->listWidget->addItem(log); } ``` 4. 实现 process 函数 由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容. ```cpp QString Widget::process(const QString& request) { return request; } ``` **"根据请求处理响应" 是服务器开发中的最核⼼的步骤. ⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的.** 此时, 服务器程序编写完毕. 但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤. #### 4.1.3 回显客⼾端 1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget • 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的 sizePolicy 为 Expanding • 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好. • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调). ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1e9821b9313b4ca39521491fe5ff66f0.png) 2. 在 widget.cpp 中, 先创建两个全局常量, 表⽰服务器的 IP 和 端⼝ ```cpp // 提前定义好服务器的 IP 和 端⼝ const QString& SERVER_IP = "127.0.0.1"; const quint16 SERVER_PORT = 9090; ``` 3. 创建 QUdpSocket 成员 修改 widget.h, 定义成员 ```cpp class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private: Ui::Widget *ui; // 创建 socket 成员 QUdpSocket* socket; } ``` 修改 widget.cpp, 初始化 socket ```cpp Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); // 1. 设置窗⼝名字 this->setWindowTitle("客⼾端"); // 2. 实例化 socket socket = new QUdpSocket(this); } ``` 4. 给发送按钮 slot 函数, 实现发送请求 ```cpp void Widget::on_pushButton_clicked() { // 1. 获取到输⼊框的内容 const QString& text = ui->lineEdit->text(); // 2. 构造请求数据 QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT); // 3. 发送请求 socket->writeDatagram(requestDatagram); // 4. 消息添加到列表框中 ui->listWidget->addItem("客⼾端说: " + text); // 5. 清空输⼊框 ui->lineEdit->setText(""); } ``` 5. 再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应. ```cpp connect(socket, &QUdpSocket::readyRead, this, [=]() { const QNetworkDatagram responseDatagram = socket->receiveDatagram(); QString response = responseDatagram.data(); ui->listWidget->addItem(QString("服务器说: ") + response); }); ``` 最终执⾏效果 启动多个客⼾端都可以正常⼯作. ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fc567f0e81794ca696ac75b9fe6f43f8.png) ### 4.2 TCP Socket #### 4.2.1 核⼼ 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. 4.2.2 回显服务器 1. 创建界⾯. 包含⼀个 QListWidget , ⽤于显⽰收到的数据. ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c29698aacf4048299882c59108c27c3a.png) 2. 创建 QTcpServer 并初始化 修改 widget.h, 添加 QTcpServer 指针成员. ```cpp class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private: Ui::Widget *ui; // 创建 QTcpServer QTcpServer* tcpServer; } ``` 修改 widget.cpp, 实例化 QTcpServer 并进⾏后续初始化操作. • 设置窗⼝标题 • 实例化 TCP server. (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁). • 通过信号槽, 处理客⼾端建⽴的新连接. • 监听端⼝ ```cpp Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); // 1. 设置窗⼝标题 this->setWindowTitle("服务器"); // 2. 实例化 TCP server tcpServer = new QTcpServer(this); // 3. 通过信号槽, 处理客⼾端建⽴的新连接. connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection); // 4. 监听端⼝ bool ret = tcpServer->listen(QHostAddress::Any, 9090); if (!ret) { QMessageBox::critical(nullptr, "服务器启动失败!", tcpServer- >errorString()); exit(1); } } ``` 3. 继续修改 widget.cpp, 实现处理连接的具体⽅法 processConnection • 获取到新的连接对应的 socket. • 通过信号槽, 处理收到请求的情况 • 通过信号槽, 处理断开连接的情况 ```cpp void Widget::processConnection() { // 1. 获取到新的连接对应的 socket. QTcpSocket* clientSocket = tcpServer->nextPendingConnection(); QString log = QString("[") + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端上线!"; ui->listWidget->addItem(log); // 2. 通过信号槽, 处理收到请求的情况 connect(clientSocket, &QTcpSocket::readyRead, this, [=](){ // a) 读取请求 QString request = clientSocket->readAll(); // b) 根据请求处理响应 const QString& response = process(request); // c) 把响应写回客⼾端 clientSocket->write(response.toUtf8()); QString log = QString("[") + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] req: " + request + ", resp: " + response; ui->listWidget->addItem(log); }); // 3. 通过信号槽, 处理断开连接的情况 connect(clientSocket, &QTcpSocket::disconnected, this, [=]() { QString log = QString("[") + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!"; ui->listWidget->addItem(log); // 删除 clientSocket clientSocket->deleteLater(); }); } ``` 4. 实现 process ⽅法, 实现根据请求处理响应. 由于我们此处是实现回显服务器. 所以 process ⽅法中并没有包含实质性的内容. ```cpp QString Widget::process(const QString &request) { return request; } ``` "根据请求处理响应" 是服务器开发中的最核⼼的步骤. ⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的. 此时, 服务器程序编写完毕. 但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤. #### 4.2.3 回显客⼾端 1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget • 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的 sizePolicy 为 Expanding • 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好. • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调). ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/22e15fc010d541c6bbee4c6e675e03de.png) 2. 创建 QTcpSocket 并实例化 修改 widget.h, 创建成员. ```cpp 2) 创建 QTcpSocket 并实例化 修改 widget.h, 创建成员. public: Widget(QWidget *parent = nullptr); ~Widget(); private: Ui::Widget *ui; // 新增 QTcpSocket QTcpSocket* socket; }; ``` 修改 widget.cpp, 对 QTcpSocket 进⾏实例化. • 设置窗⼝标题 • 实例化 socket 对象 (⽗元素设为当前控件, 会在⽗元素销毁时被⼀起销毁). • 和服务器建⽴连接. • 等待并确认连接是否出错. ```cpp Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); // 1. 设置窗⼝标题. this->setWindowTitle("客⼾端"); // 2. 实例化 socket 对象. socket = new QTcpSocket(this); // 3. 和服务器建⽴连接. socket->connectToHost("127.0.0.1", 9090); // 4. 等待并确认连接是否出错. if (!socket->waitForConnected()) { QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString()); exit(1); } } ``` 3. 修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器. ```cpp void Widget::on_pushButton_clicked() { // 获取输⼊框的内容 const QString& text = ui->lineEdit->text(); // 清空输⼊框内容 ui->lineEdit->setText(""); // 把消息显⽰到界⾯上 ui->listWidget->addItem(QString("客⼾端说: ") + text); // 发送消息给服务器 socket->write(text.toUtf8()); } ``` 4. 修改 widget.cpp 中的 Widget 构造函数, 通过信号槽, 处理收到的服务器的响应. ```cpp / 处理服务器返回的响应. connect(socket, &QTcpSocket::readyRead, this, [=]() { QString response = socket->readAll(); qDebug() << response; ui->listWidget->addItem(QString("服务器说: ") + response); }); ``` 先启动服务器, 再启动客⼾端(可以启动多个), 最终执⾏效果: 由于我们使⽤信号槽处理同⼀个客⼾端的多个请求, 不涉及到循环, 也就不会使客⼾端之间相互影响 了. ### 4.3 HTTP Client 进⾏ Qt 开发时, 和服务器之间的通信很多时候也会⽤到 HTTP 协议. 通过 HTTP 从服务器获取数据. • 通过 HTTP 向服务器提交数据. 4.3.1 核⼼ API 关键类主要是三个. QNetworkAccessManager , QNetworkRequest , QNetworkReply . QNetworkAccessManager 提供了 HTTP 的核⼼操作. ⽅法 说明 get(const QNetworkRequest\& ) 发起⼀个 HTTP GET 请求. 返回 QNetworkReply 对象. post(const QNetworkRequest\& , const QByteArray\& ) 发起⼀个 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 会在客⼾端收到完整的响应数据之后触 发. #### 4.3.2 代码⽰例 给服务器发送⼀个 GET 请求. 1. 创建界⾯. 包含⼀个 QLineEdit , QPushButton • 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的 sizePolicy 为 Expanding • 再使⽤垂直布局把 QPlainTextEdit 和上⾯的⽔平布局放好. ( QPlainTextEdit 的 readOnly 设为 true ) • 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调). ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fc5ca6ff9b9940689eedf68dc526d230.png) **此处建议使⽤ QPlainTextEdit ⽽不是 QTextEdit . 主要因为 QTextEdit 要进⾏富 ⽂本解析, 如果得到的 HTTP 响应体积很⼤, 就会导致界⾯渲染缓慢甚⾄被卡住.** 2) 修改 widget.h, 创建 QNetworkAccessManager 属性 ```cpp class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; // 新增属性 QNetworkAccessManager* manager; }; ``` 3. 修改 widget.cpp, 创建实例 ```cpp Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); // 实例化属性 manager = new QNetworkAccessManager(this); } ``` 4. 编写按钮的 slot 函数, 实现发送 HTTP 请求功能. ```cpp void Widget::on_pushButton_clicked() { // 1. 获取到输⼊框中的 URL, 构造 QUrl 对象 QUrl url(ui->lineEdit->text()); // 2. 构造 HTTP 请求对象 QNetworkRequest request(url); // 3. 发送 GET 请求 QNetworkReply* response = manager->get(request); // 4. 通过信号槽来处理响应 connect(response, &QNetworkReply::finished, this, [=]() { if (response->error() == QNetworkReply::NoError) { // 响应正确 QString html(response->readAll()); ui->plainTextEdit->setPlainText(html); // qDebug() << html; } else { // 响应出错 ui->plainTextEdit->setPlainText(response->errorString()); } response->deleteLater(); }); } ``` 执⾏程序, 观察效果 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/29750f24c0b9439fb1874ff44b10806e.png) 发送 POST 请求代码也是类似. 使⽤ manager-\>post() 即可. 此处不再演⽰. ### 4.4 其他模块 Qt 中还提供了 FTP, DNS, SSL 等⽹络相关的组件⼯具. 此处不再⼀⼀展开介绍. 有需要的可以⾃⾏ 翻阅官⽅⽂档学习相关 API 的使⽤. ## 5. Qt ⾳视频 ### 5.1 Qt ⾳频 在 Qt 中,⾳频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只⽀持播放 wav 格式的⾳频⽂件。也就是说如果想要添加⾳频效果,那么⾸先需要将 ⾮wav格式 的⾳频⽂件转换为 wav 格式。 通过帮助⼿册查看 QSound 类如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f262013f4bc749d9a800eb5c8533f7c4.png) 注意: 使⽤ QSound 类时,需要添加模块:multimedia. #### 5.1.1 核⼼API概览 play() 开始或继续播放当前源。 #### 5.1.2 ⽰例 ```cpp /********************************* SoundTest.pro *********************************/ QT += core gui multimedia //添加⾳频模块 /********************************* widget.cpp *********************************/ #include "widget.h" #include "ui_widget.h" #include //添加⾳频头⽂件 Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); //实例化对象 QSound *sound = new QSound(":/1.wav",this); connect(ui->btn,&QPushButton::clicked,[=](){ sound->play(); //播放 }); } Widget::~Widget() { delete ui; } ``` ### 5.2 Qt 视频 在 Qt 中,视频播放的功能主要是通过 QMediaPlayer类 和 QVideoWidget类 来实现。在使⽤这两个类 时要添加对应的模块 multimedia 和multimediawidgets 。 #### 5.2.1 核⼼API概览 setMedia() 设置当前媒体源。 setVideoOutput() 将QVideoWidget视频输出附加到媒体播放器。 如果媒体播放器已经附加了视频输出,将更换⼀个新的。 #### 5.2.2 ⽰例 ⾸先在 .pro ⽂件中添加 multimedia 和 multimediawidgets 两个模块;如下图⽰: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/44b8daa52c3d4bc2a7dde9466b9584d7.png) ```cpp /********************************* widget.h *********************************/ #ifndef WIDGET_H #define WIDGET_H #include #include //⽔平布局 #include //垂直布局 #include //显⽰视频 #include //播放声⾳ #include //按钮 #include //设置图标 #include //选择⽂件/⽂件夹 class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); public slots: void chooseVideo(); private: QMediaPlayer *mediaPlayer; QVideoWidget *videoWidget; QVBoxLayout *vbox; //创建两个按钮:选择视频按钮和开播放按钮 QPushButton *chooseBtn,*playBtn; }; #endif // WIDGET_H /********************************* widget.cpp *********************************/ #include "widget.h" #include #include Widget::Widget(QWidget *parent) : QWidget(parent) { //对象实例化 mediaPlayer = new QMediaPlayer(this); videoWidget = new QVideoWidget(this); //设置播放画⾯的窗⼝ videoWidget->setMinimumSize(600,600); //实例化窗⼝布局---垂直布局 this->vbox = new QVBoxLayout(this); this->setLayout(this->vbox); //实例化选择视频按钮 chooseBtn = new QPushButton("选择视频",this); //实例化播放按钮 playBtn = new QPushButton(this); //设置图标代替⽂件 playBtn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay)); //实例化⼀个⽔平布局,将以上控件放⼊⽔平布局中 QHBoxLayout *hbox = new QHBoxLayout; //添加控件 hbox->addWidget(chooseBtn); hbox->addWidget(playBtn); //将播放窗⼝和⽔平布局都添加到垂直布局中 vbox->addWidget(videoWidget); //布局中添加布局 vbox->addLayout(hbox); //将选择视频对应的按钮和槽函数进⾏关联 connect(chooseBtn,&QPushButton::clicked,this,&Widget::chooseVideo); } void Widget::chooseVideo() { //选择视频,返回⼀个播放视频的名字 QString name = QFileDialog::getSaveFileName(this,"选择视 频",".","WMV(*.wmv)"); //设置媒体声⾳ mediaPlayer->setMedia(QUrl(name)); //输出视频画⾯ mediaPlayer->setVideoOutput(videoWidget); //播放 mediaPlayer->play(); } Widget::~Widget() { } ``` eg; 添加音频 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a008251e513f429d8991795da32cb6c0.png) ```cpp #include "widget.h" #include "ui_widget.h" #include Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); sound = new QSound(":/n/a.wav"); } Widget::~Widget() { delete ui; } void Widget::on_pushButton_clicked() { //在这里进行音频播放 sound->play(); } ``` ## 总结 以上就是qt系统相关的内容了。

相关推荐
香蕉可乐荷包蛋13 分钟前
vue3中ref和reactive的使用、优化
前端·javascript·vue.js
耶啵奶膘24 分钟前
css——width: fit-content 宽度、自适应
前端·css
OEC小胖胖26 分钟前
前端框架状态管理对比:Redux、MobX、Vuex 等的优劣与选择
前端·前端框架·web
字节架构前端1 小时前
k8s场景下的指标监控体系构建——Prometheus 简介
前端·架构
奕羽晨1 小时前
关于CSS的一些读书笔记
前端·css
Poetry2372 小时前
大屏数据可视化适配方案
前端
遂心_2 小时前
用React Hooks + Stylus打造文艺范的Todo应用
前端·javascript·react.js
轻语呢喃2 小时前
<a href=‘ ./XXX ’>,<a href="#XXX">,<Link to="/XXX">本质与区别
前端·react.js·html
用户3802258598242 小时前
vue3源码解析:watch的实现
前端·vue.js·源码