Qt事件处理全解析

Qt事件

事件介绍

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

常见事件描述:

|--------|------------------------|
| 事件名称 | 描述 |
| 鼠标事件 | 鼠标左键、右键、滚轮、鼠标的移动、按下和松开 |
| 键盘事件 | 按键类型、按键按下、按键松开 |
| 定时器事件 | 定时时间到达 |
| 进⼊离开事件 | ⿏标的进⼊和离开 |
| 滚轮事件 | ⿏标滚轮滚动 |
| 绘屏事件 | 重绘屏幕的某些部分 |
| 显⽰隐藏事件 | 窗⼝的显⽰和隐藏 |
| 移动事件 | 窗⼝位置的变化 |
| 窗⼝事件 | 是否为当前窗⼝ |
| ⼤⼩改变事件 | 窗⼝⼤⼩改变 |
| 焦点事件 | 键盘焦点移动 |
| 拖拽事件 | ⽤⿏标进⾏拖拽 |

事件的处理

事件处理一般常用的方法为:重写相关的Event函数
在 Qt 中,⼏乎所有的 Event 函数都是虚函数,所以可以重新实现。如:在实现⿏标的进⼊和离开事件时,直接重新实现 enterEvent() 和 leaveEvent() 即可

复制代码
#include "mylabel.h"
#include <QDebug>
#include <QMouseEvent>

MyLabel::MyLabel(QWidget *parent)
    : QLabel{parent}
{}

void MyLabel::enterEvent(QEnterEvent *event)
{
    qDebug()<<"鼠标进入";
}

void MyLabel::mousePressEvent(QMouseEvent *ev)
{
    QString pos = QString("鼠标按下的位置为(%1,%2)").arg(ev->x()).arg(ev->y());
    qDebug()<<pos;
}

按键事件

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

单个按键
复制代码
#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 *e)
{
    if(e->key()==Qt::Key_A){
        qDebug()<<"A按键被按下了";
    }
}
组合按键


Qt::KeyboardModifier 中定义了在处理键盘事件时对应的修改件。在Qt中,键盘事件可以与修改键一起使用,已实现服一些复杂的交互操作。

复制代码
#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 *e)
{
    // 判断Ctrl键是否按下
    if(e->modifiers()==Qt::ControlModifier)
    {
        // 判断A键是否按下
        if(e->key()==Qt::Key_A){
            qDebug()<<"Ctrl+A按键被按下了";
        }
    }

}

鼠标事件

在Qt中,鼠标事件使用QMouseEvent类来实现的。当窗口中按下鼠标或者移动鼠标的时候,都会产生鼠标事件。

利用QMouseEvent类可以获取鼠标的哪个键被按下以及鼠标的当前位置等信息。在Qt帮助文档中查找QMouseEvent类

鼠标单击事件

在Qt中,鼠标按下是通过虚函数mousePressEvent()来捕获的
Qt::LeftButton ⿏标左键
Qt::RightButton ⿏标右键
Qt::MidButton ⿏标滚轮

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

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

void Widget::mousePressEvent(QMouseEvent *e)
{
    if(e->button()==Qt::LeftButton){
        qDebug()<<"鼠标左键被按下了";
    }
}
鼠标释放事件

鼠标释放事件是通过虚函数moustReleaseEvent()来捕获的。

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

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

void Widget::mouseReleaseEvent(QMouseEvent *e)
{
    if(e->button()==Qt::LeftButton){
        qDebug()<<"鼠标左键被释放";
    }
}
鼠标双击事件

鼠标双击事件是通过虚函数:mouseDoubleClickEvent()来实现。

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

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

void Widget::mouseDoubleClickEvent(QMouseEvent *e)
{
    if(e->button()==Qt::LeftButton){
        qDebug()<<"鼠标双击";
    }
}
鼠标移动事件

鼠标移动事件是通过虚函数:mouseMoveEvent()来实现的。同时为了实时捕获鼠标位置信息,需要通过函数setMouseTracking()来追踪鼠标的位置。

setMouseTracking()函数默认是false,需要设置为true,才能实时捕获鼠标位置信息。否则只有当鼠标按下的时候才能捕获其位置信息。

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMouseEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setMouseTracking(true);
}

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

void Widget::mouseMoveEvent(QMouseEvent *e)
{
    qDebug()<<"["<<e->x()<<","<<e->y()<<"]";
}
滚轮事件

在Qt中,鼠标滚轮事件是通过QWheelEvent类来实现的。滚轮滑动的距离可以通过delta函数获取。

不过在,Qt6中,已经移除了delta()方法,引入了angleDelta()方法替代它

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QWheelEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setMouseTracking(true);
}

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

void Widget::wheelEvent(QWheelEvent *e)
{
    static int x = 0;
    x += e->angleDelta().y();  // y()对应垂直滚轮的移动距离
    if(e->angleDelta().y() > 0){
        // 滚轮向上滚动
        qDebug() << "向上滚动,累计值:" << x;
    }else{
        // 滚轮向下滚动
        qDebug() << "向下滚动,累计值:" << x;
    }
}

定时器

Qt中进行窗口程序的处理过程中,要经常周期性的执行某些操作,或者制作一些动画效果,使用定时器就能完成。所谓定时器就是在间隔一定时间后,去指定某一个任务。定时日在很多场景下都会使用到。

Qt中的定时器分为QTimerEvent和QTimer这两个类

  • QTimerEvent类用来描述一个定时器事件。在使用需要通过startTimeer()函数来开启一个定时器。这个函数需要输入一个以韩淼为单位的整数作为参数来表明设定的事件,它返回的整型值代表这个定时器。当定时器溢出时就可以在timerEvent函数中获取该定时器的编号来进行相关操作。
  • QTImer类实现一个定时器,他提供了更高层次的编程接口,可以使用信号和槽,还可以设置只运行一侧的定时器。
QTimerEvent类

示例1:在UI界面上防止两个Label控件,一个让其1秒数字累加一次,一个让其2秒数字累加一次。

复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    time_id0 = startTimer(1000);
    time_id1 = startTimer(2000);
}

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

void Widget::timerEvent(QTimerEvent *e)
{
    if(e->timerId()==time_id0){
        static int num1 = 1;
        ui->label->setText(QString::number(num1++));
    }
    if(e->timerId()==time_id1){
        static int num2 = 1;
        ui->label_2->setText(QString::number(num2++));
    }
}
QTimer类

示例:在UI界面防止一个Label标签,分别是"开始","停止",当点击"开始"按钮的时候,开始每个1s计数一次,点击"停机"按钮的时候,暂停计数

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QTimer* time = new QTimer(this);
    connect(ui->btn_start,&QPushButton::clicked,this,[=](){
        time->start(1000);
    });
    connect(time,&QTimer::timeout,this,[=](){
        static int num = 1;
        ui->label->setText(QString::number(num++));
    });
    connect(ui->btn_stop,&QPushButton::clicked,this,[=](){
        time->stop();
    });
}

Widget::~Widget()
{
    delete ui;
}
获取系统日期以及事件

在Qt中,获取系统的日期以及实时事件可以通过QTimer类和QDataTime类

QDataTime类提供了字符串格式的时间。字符串形式的时间输出格式由toString()方法中的format参数列表决定

示例:获取系统日期以及实时时间

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QTimer>
#include <QDateTime>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QTimer* time = new QTimer(this);
    connect(ui->btn_start,&QPushButton::clicked,this,[=](){
        time->start(1000);
    });
    connect(ui->btn_stop,&QPushButton::clicked,this,[=](){
        time->stop();
    });
    connect(time,&QTimer::timeout,this,&Widget::TimeUpDate);
}

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

void Widget::TimeUpDate()
{
    QString str = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    ui->label->setText(str);
}

事件分发器

在Qt中,事件分发器是一个核心概念,用于处理GUI应用程序中的事件。事件分发器负责讲事件从一个对象传递到另一个对象,直接时间被处理或者被取消。每个继承在QObject类或都可以在本类中重写bool event函数,来实现相关的事件的捕获和拦截

事件分发器工作原理

在Qt中,我们发送的事件都是传给了QObject对象,更具体点是传给了QObject对象的event函数。所有的事件都会进入到这个函数里面,那么我们处理时间就要重写这个event函数。event函数本身不会去处理事件,而是根据事件类型调用不同的事件处理函数。事件分发器就是工作在应用程序下分发时间的过程中。

事件分发器用于分发时间。在此过程中,事件分发器也可以做拦截操作。事件分发器主要是通过bool event函数来实现的。返回值是布尔类型,true表示拦截,不向下分发。

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent>
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

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

void Widget::mousePressEvent(QMouseEvent *e)
{
    if(e->button()==Qt::LeftButton){
        qDebug()<<"鼠标左键被按下";
    }
}

bool Widget::event(QEvent *event)
{
    if(event->type()==QEvent::MouseButtonPress){
        qDebug()<<"event中鼠标左键被按下";
        return true;
    }
    return QWidget::event(event);
}

事件过滤器

在 Qt 中,⼀个对象可能经常要查看或拦截另外⼀个对象的事件,如对话框想要拦截按键事件,不让的组件接收到,或者修改按键的默认值等。通过上⾯的学习,我们已经知道,Qt 创建了 QEvent事件对象之后,会调⽤QObject 的 event()函数 处理事件的分发。显然,我们可以在 event()函数 中实现拦截的操作。由于 event()函数是 protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个event()函数。这当然相当⿇烦,更不⽤说重写 event()函数还得⼩⼼⼀堆问题。好在 Qt 提供了另外⼀种机制来达到这⼀⽬的:事件过滤器。

事件过滤器是在应⽤程序分发到 event事件分发器 之前,再做⼀次更⾼级的拦截

事件过滤器的一般使用步骤:

  1. 安装时间过滤器

  2. 重写时间过滤器函数: eventfilter

    #include "widget.h"
    #include "ui_widget.h"
    #include <QMouseEvent>
    #include <QDebug>
    #include <QEvent>

    Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    {
    ui->setupUi(this);
    ui->label->installEventFilter(this);

    }

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

    bool Widget::eventFilter(QObject watched, QEvent event)
    {
    if(watched==ui->label){
    if(event->type()==QMouseEvent::MouseButtonPress){
    QMouseEvent
    e = static_cast<QMouseEvent
    >(event);
    QString str = QString("事件过滤器 鼠标按下 [%1,%2]").arg(e->x()).arg(e->y());
    qDebug()<<str;
    return true;
    }
    }
    return QWidget::eventFilter(watched,event);
    }

    #include "mylabel.h"
    #include <QMouseEvent>

    myLabel::myLabel(QWidget *parent)
    : QLabel{parent}
    {}

    void myLabel::mousePressEvent(QMouseEvent *e)
    {
    QString str = QString("鼠标按下,[%1,%2]").arg(e->x()).arg(e->y());
    qDebug()<<str.toUtf8().data();

    }

    bool myLabel::event(QEvent e)
    {
    // 如果鼠标按下,在event事件分发时做拦截操作
    if(e->type()==QEvent::MouseButtonPress){
    QMouseEvent
    event = static_cast<QMouseEvent*>(e);
    QString str = QString("Event鼠标按下,[%1,%2]").arg(event->x()).arg(event->y());
    qDebug()<<str.toUtf8().data();
    return true;
    }
    return QLabel::event(e);
    }

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 中,⽂件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了⼀些⽤来读写⽂件的⽅法。对于⽂件的操作主要有:

  • 读文件:QFIle类中提供了多种方法用于读取文件内容:如read()、readAll()、readLine()等等
  • 写数据:QFIle类提供了多个方法用户向文件中写内容;如write()、writeData()等
  • 关闭文件:文件使用结束之后必须用函数close()关闭文件

访问一个设备之前,需要使用open函数打开该设备,而且必须指定正确的打开模式,QIODevice中所有的打开模式有QIODevice::OpenMode枚举变量定义

示例1:读取文件内容

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QFileDialog>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->btn,&QPushButton::clicked,this,[=](){
        QString path = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\Lenovo\\Desktop");
        ui->lineEdit->setText(path);
        QFile file(path);
        file.open(QIODeviceBase::ReadOnly);
        QString content = file.readAll();
        ui->textEdit->setText(content);
        file.close();
    });
}

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

实例2:向文件中写入内容

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QFileDialog>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->btn,&QPushButton::clicked,this,[=](){
        QString path = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\Lenovo\\Desktop");
        ui->lineEdit->setText(path);
        QFile file(path);
        file.open(QIODeviceBase::Append);
        file.write("新增内容");
        file.close();
    });
}

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

文件和目录信息类

QFileInfo是Qt提供的一个用户获取文件和目录信息的类,如获取文件名,文件大小,文件修改日期等。QFileInfo类中提供了很多的方法。

  • isDir()检查该文件是否是目录

  • isExecutable()检查该文件是否为可执行文件

  • fileName()获取文件名

  • completaBasename()获取完整的文件名

  • suffix()获取文件后缀名

  • completeSuffix()获取完整的文件后缀

  • size()获取文件大小

  • isFile()判断是否为文件

  • fileTime()获取文件创建时间、修改时间、最近访问时间

    #include "widget.h"
    #include "ui_widget.h"
    #include <QPushButton>
    #include <QFileDialog>
    #include <QFileInfo>
    #include <QDateTime>

    Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    {
    ui->setupUi(this);
    connect(ui->btn,&QPushButton::clicked,this,={
    QString path = QFileDialog::getOpenFileName(this,"打开文件","C:\Users\Lenovo\Desktop");
    ui->lineEdit->setText(path);
    QFileInfo fileInfo(path);
    qDebug()<<"文件名为:"<<fileInfo.fileName();
    qDebug()<<"后缀名为:"<<fileInfo.suffix();
    qDebug()<<"文件大小为:"<<fileInfo.size();
    qDebug()<<"文件路径为:"<<fileInfo.path();
    qDebug()<<"是否为文件"<<fileInfo.isFile();
    QDateTime time1 = fileInfo.fileTime(QFileDevice::FileBirthTime);
    qDebug()<<"创建时间为:"<<time1.toString("yyyy-MM-dd hh:mm:ss");
    QDateTime time2 = fileInfo.lastModified();
    qDebug()<<"最近修改时间为:"<<time2.toString("yyyy-MM-dd hh:mm:ss");
    qDebug() << "是否为目录:" << fileInfo.isDir();
    });
    }

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

Qt多线程

Qt多线程概述

在Qt中,多线程的处理一般是通过QThread来实现的

QThread代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据

QThread对象管理程序中的一个控制线程

常用API

|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| run() | 线程的入口函数 |
| start() | 通过调用run()开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做。 |
| currentThread() | 返回一个指向管理当前执行线程的QThread指针 |
| isRunning() | 如果线程正在运行,返回true;否则,返回false |
| sleep()/msleep()/usleep() | 是线程休眠,单位为s/ms/us |
| wait() | 阻塞线程,知道以下任何一个条件 与此 QThread 对象关联的线程已经完成执⾏(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。 已经过了⼏毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回 false。这提供了与 POSIX pthread_join() 函数类似的功能。 |
| terminate() | 中值线程的执行。线程可以立即中值,也可以不终止。这取决于操作系统的调度策略。在terminate()之后使用QThread::wait()来确保 |
| finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。 |

使用线程

创建线程的步骤:

  1. 自定义一个类,继承自QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数主要是重写父类中的run()函数

  2. 线程处理函数里面写入需要执行的复杂数据处理

  3. 启动线程不能直接调用run()函数,需要使用对象来调用start()函数来实现线程启动

  4. 线程处理函数执行结束后可以定义一个信号来告诉主线程

  5. 最后关闭线程

    // widget.cc
    #include "widget.h"
    #include "ui_widget.h"
    #include <QDebug>

    Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    {
    ui->setupUi(this);
    connect(&t,&TimeThread::sendTime,this,&Widget::showTime);
    }

    Widget::~Widget()
    {
    t.stop(); // 通知线程退出
    t.wait(); // 等待线程安全结束
    delete ui;
    }

    void Widget::on_pushButton_clicked()
    {
    t.start(); //开启线程
    }

    void Widget::showTime(QString Time)
    {
    ui->label->setText(Time);
    }

    //timethread.cc
    #include "timethread.h"
    #include <QTime>
    #include <QDebug>

    TimeThread::TimeThread() {}

    void TimeThread::run()
    {
    running = true;
    while(running)
    {
    QString time = QTime::currentTime().toString("hh:mm:ss");
    emit sendTime(time);
    sleep(1);
    }
    }

    void TimeThread::stop()
    {
    running = false;
    }

  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 | 这是⼀个标志,可以使⽤位或与上述任何⼀种连接类型组合使⽤ |

线程安全

实现线程互斥和同步的常用类有:

  • 互斥锁:QMutex,QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker,QReadWriteLock
互斥锁

互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在Qt中,互斥锁主要是通过QMutex类来处理的

  • QMutex

特点:QMutex是Qt框架的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。

用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全

  • QMutexLocker

特点:QMutexLocker是QMutex的辅助类,使用RAII方式对互斥锁进行上锁和解锁操作

用途:简化对互斥锁的上锁和解锁操作,避免顽疾解锁导致死锁的问题

  • QReadWriteLocker,QReadLocker,QWriteLocker

特点:

QReadWriteLock是读写锁类,用于控制读和写的并发访问

QReadLocker用于读操作上锁,允许多个线程同时读取共享资源

QWriteLocker用于写操作上锁,只运行一个线程写入共享资源。
⽤途:在某些情况下,多个线程可以同时读取共享数据,但只有⼀个线程能够进⾏写操作。读写锁提供了更⾼效的并发访问⽅式
示例1 :两个线程使⽤⼀把锁,操作⼀个数据,数据会被两个线程依次打印:0、1、2、3、4 ...

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

QMutex myThread::mutex;
int myThread::num = 0;

myThread::myThread(QObject* parent):
    QThread(parent)
{

}

void myThread::run()
{
    while(running){
        mutex.lock();
        qDebug() << "Current Thread: " << this << ", Value: " << this->num++;
        mutex.unlock();
        QThread::sleep(2);
    }
}

void myThread::stop()
{
    running = false;
}

#include "widget.h"
#include "ui_widget.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    t1 = new myThread(this);
    t2 = new myThread(this);
    t1->start();
    t2->start();
}

Widget::~Widget()
{
    t1->stop();
    t1->wait();

    t2->stop();
    t2->wait();
    delete ui;
}


示例2:在上述⽰例的基础上使⽤ QMutexLocker 锁

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

QMutex myThread::mutex;
int myThread::num = 0;

myThread::myThread(QObject* parent):
    QThread(parent)
{

}

void myThread::run()
{
    while(running){
        {
            QMutexLocker lock(&this->mutex);
            qDebug() << "Current Thread: " << this << ", Value: " << this->num++;
        }
        QThread::sleep(1);
    }
}

void myThread::stop()
{
    running = false;
}
条件变量

在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒
在 Qt 中,专⻔提供了 QWaitCondition类 来解决像上述这样的问题
特点:QWaitCondition 是 Qt 框架提供的条件变量类,⽤于线程之间的消息通信和同步。
⽤途:在某个条件满⾜时等待或唤醒线程,⽤于线程的同步和协调

信号量

有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量
特点:QSemaphore 是 Qt 框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。
⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题。

Qt网络

在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块.
添加之后要⼿动编译⼀下项⽬, 使 Qt Creator 能够加载对应模块的头⽂件.

UDP Socket

核⼼ 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 包含了该功 能. |

回显服务器
复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>

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;
    }
}

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

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());
    socket->writeDatagram(responseDatagram);
    // 显⽰打印⽇志
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
                  QString::number(requestDatagram.senderPort())
                  + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
}

QString Widget::process(const QString& reqeust)
{
    return reqeust;
}
回显客户端
复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>

const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 1. 设置窗⼝名字
    this->setWindowTitle("客⼾端");
    // 2. 实例化 socket
    socket = new QUdpSocket(this);
    connect(socket, &QUdpSocket::readyRead, this, [=]() {
        const QNetworkDatagram responseDatagram = socket->receiveDatagram();
        QString response = responseDatagram.data();
        ui->listWidget->addItem(QString("服务器说: ") + response);
    });
}

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



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("");

}

TCP Socket

核⼼ API 概览

|--------------------------------------------|----|-----------------------------------------------------------------------------------|--------------------------|
| 名称 | 类型 | 说明 | 对标原⽣ 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 多路复⽤中的通知机制) |

回显服务器
复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 1. 设置窗口标题
    this->setWindowTitle("服务器");
    // 2. 实例化TCP
    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);
    }

}

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

void Widget::processConnection()
{
    // 1. 获取到新的连接对应的 socket
    QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
    QString log = QString("[") + clientSocket->peerAddress().toString()+ ":"
                  + QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";
    // 2. 通过信号槽,处理收到的请求
    connect(clientSocket,&QTcpSocket::readyRead,this,[=](){
        QString request = clientSocket->readAll();
        const QString& response = process(request);
        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();
    });
}

QString Widget::process(const QString &reqeust)
{
    return reqeust;
}
回显客户端
复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 1. 设置窗口标题
    this->setWindowTitle("客户端");
    // 2. 实例化TCP
    socket = new QTcpSocket(this);
    // 3. 和服务器建立连接
    socket->connectToHost("127.0.0.1",9090);
    if (!socket->waitForConnected()) {
        QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
        exit(1);
    }
    connect(socket, &QTcpSocket::readyRead, this, [=]() {
        QString response = socket->readAll();
        qDebug() << response;
        ui->listWidget->addItem(QString("服务器说: ") + response);
    });
}

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

void Widget::on_pushButton_clicked()
{
    // 1. 获取输入框中的内容
    const QString& message = ui->lineEdit->text();
    // 2. 把消息显示在屏幕上
    ui->listWidget->addItem("客户端说:"+message);
    // 3. 发送消息
    socket->write(message.toUtf8());
    // 4. 清楚输入框
    ui->lineEdit->setText("");
}

HTTP Client

通过 HTTP 从服务器获取数据.
通过 HTTP 向服务器提交数据.

核⼼ API

关键类主要是三个. QNetworkAccessManager , QNetworkRequest , QNetworkReply .

|-----------------------------------------------------|------------------------------------------|
| ⽅法 | 说明 |
| get(const QNetworkRequest& ) | 发起⼀个 HTTP GET 请求. 返回 QNetworkReply 对象 |
| post(const QNetworkRequest& , const QByteArray& ) | 发起⼀个 HTTP POST 请求. 返回 QNetworkReply 对 象. |

QNetworkRequest 表⽰⼀个 HTTP 请求(不含 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 会在客⼾端收到完整的响应数据之后触

Qt ⾳视频

在 Qt 中,⾳频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只⽀持播放 wav 格式的⾳频⽂件。也就是说如果想要添加⾳频效果,那么⾸先需要将 ⾮wav格式 的⾳频⽂件转换为 wav 格 式。

复制代码
#include "widget.h"
#include "ui_widget.h"


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    player = new QMediaPlayer(this);
    // 设置音频文件(确保资源文件中存在 1.wav)
    player->setSource(QUrl(":/1.wav"));

    connect(ui->btn, &QPushButton::clicked, [=](){
        player->play();
    });
}

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

Qt视频

核⼼API概览

|------------------|-----------------------------------------------------|
| setMedia() | 设置当前媒体源 |
| setVideoOutput() | 将QVideoWidget视频输出附加到媒体播放器。 如果媒体播放器已经附加了视频输出,将更换⼀个新的 |

相关推荐
Brookty2 小时前
【Java学习】定时器Timer(源码详解)
java·开发语言·学习·多线程·javaee
艾莉丝努力练剑2 小时前
【C++STL :vector类 (二) 】攻克 C++ Vector 的迭代器失效陷阱:从源码层面详解原理与解决方案
linux·开发语言·c++·经验分享
weixin_417257064 小时前
Qt解决不同线程,调用对方的函数
开发语言·qt
梦终剧4 小时前
【Android之路】 Kotlin 的 data class、enum class、sealed interface
android·开发语言·kotlin
半夏知半秋4 小时前
基于skynet框架业务中的gateway实现分析
服务器·开发语言·后端·学习·gateway
Leo6553510 小时前
JDK8 的排序、分组求和,转换为Map
java·开发语言
磨十三11 小时前
C++ 标准库排序算法 std::sort 使用详解
开发语言·c++·排序算法
两只程序猿13 小时前
数据可视化 | Violin Plot小提琴图Python实现 数据分布密度可视化科研图表
开发语言·python·信息可视化
野生技术架构师13 小时前
1000 道 Java 架构师岗面试题
java·开发语言