Qt进阶实战:事件处理、文件操作、多线程与网络编程全解析

Qt是一款功能强大的跨平台C++图形用户界面框架,除了基础的界面绘制,其在事件处理、文件操作、多线程网络通信及音视频播放等方面均提供了完善的API支持。本文将结合具体实例,详细讲解Qt进阶开发中的核心知识点,从基础事件捕获到复杂的网络通信、多线程同步,每部分均搭配完整代码与效果说明,适合Qt初学者进阶学习,也可作为日常开发参考。

本文基于Qt 5.14.2版本开发,所有示例均经过实测可正常运行,涉及的核心模块包括Qt GUI、Qt Core、Qt Network、Qt Multimedia等,开发工具为Qt Creator。

一、Qt事件处理机制

Qt的事件处理是GUI开发的核心,所有用户交互(如按键、鼠标操作)都会触发对应的事件,Qt通过事件分发器、事件过滤器等机制实现事件的传递与拦截,形成了一套完整的事件处理流程。

1.1 按键事件(QKeyEvent)

按键事件通过QKeyEvent类封装,用于捕获键盘按键的按下与释放操作。Qt定义了Qt::Key枚举,包含所有键盘按键的常量(如Qt::Key_A、Qt::Key_Enter等),可通过Qt助手搜索"Qt::Key"查看完整列表。

1.1.1 单个按键捕获

捕获单个按键需重写QWidget的keyPressEvent()虚函数,步骤如下:

  1. 头文件声明:在widget.h中声明keyPressEvent()函数。
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    // 声明按键按下事件
    void keyPressEvent(QKeyEvent *event);

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
  1. 源文件实现:在widget.cpp中重写函数,判断按键类型并处理。
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)
{
    // 判断是否按下A键
    if(event->key() == Qt::Key_A)
    {
        qDebug() << "A按键被按下";
    }
    // 可添加其他按键判断
    else if(event->key() == Qt::Key_Enter)
    {
        qDebug() << "回车键被按下";
    }
}

效果:运行程序后,点击对应按键,在Qt Creator的"应用程序输出"窗口中会打印对应的提示信息。

1.1.2 组合按键捕获

组合按键(如Ctrl+A、Shift+B)需结合Qt::KeyboardModifier枚举判断修改键,该枚举定义了Shift、Ctrl、Alt等修改键的常量,可通过Qt助手搜索"Qt::KeyboardModifier"查看详情。

示例:捕获Ctrl+A组合键,修改keyPressEvent()函数如下:

cpp 复制代码
void Widget::keyPressEvent(QKeyEvent *event)
{
    // 判断是否按下Ctrl键(修改键)
    if(event->modifiers() == Qt::ControlModifier)
    {
        // 判断是否同时按下A键
        if(event->key() == Qt::Key_A)
        {
            qDebug() << "Ctrl+A被按下";
        }
    }
}

注意:若需捕获多修改键组合(如Ctrl+Shift+A),可使用"|"运算符拼接修改键常量,例如event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)

1.2 鼠标事件(QMouseEvent)

鼠标事件通过QMouseEvent类封装,支持捕获鼠标单击、释放、双击、移动及滚轮滚动等操作,核心虚函数包括mousePressEvent()、mouseReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent()、wheelEvent()。

1.2.1 鼠标单击、释放与双击

鼠标左键、右键、滚轮的单击可通过Qt::MouseButton枚举判断,常用常量为Qt::LeftButton(左键)、Qt::RightButton(右键)、Qt::MidButton(滚轮)。

示例:捕获鼠标左键单击、释放及双击事件:

  1. 头文件声明:在widget.h中声明相关事件函数。
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    // 鼠标按下事件
    void mousePressEvent(QMouseEvent *event);
    // 鼠标释放事件
    void mouseReleaseEvent(QMouseEvent *event);
    // 鼠标双击事件
    void mouseDoubleClickEvent(QMouseEvent *event);

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
  1. 源文件实现
cpp 复制代码
#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 *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug() << "鼠标左键被按下";
    }
}

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

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

鼠标移动事件通过mouseMoveEvent()函数捕获,默认情况下,只有鼠标按下时才会触发该事件;若需实时捕获鼠标移动(未按下时),需调用setMouseTracking(true)开启鼠标追踪。

示例:实时打印鼠标坐标:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 开启鼠标追踪,实时捕获移动
    setMouseTracking(true);
}

// 鼠标移动事件
void Widget::mouseMoveEvent(QMouseEvent *event)
{
    // 获取鼠标在窗口内的坐标
    int x = event->x();
    int y = event->y();
    qDebug() << "鼠标坐标:[" << x << "," << y << "]";
}
1.2.3 鼠标滚轮事件

鼠标滚轮事件通过wheelEvent()函数捕获,可通过QWheelEvent的delta()函数获取滚轮滚动距离,正数表示向前滚动,负数表示向后滚动。

cpp 复制代码
#include <QWheelEvent>

// 鼠标滚轮事件
void Widget::wheelEvent(QWheelEvent *event)
{
    int delta = event->delta();
    if(delta > 0)
    {
        qDebug() << "滚轮向前滚动";
    }
    else
    {
        qDebug() << "滚轮向后滚动";
    }
}

1.3 定时器(QTimerEvent与QTimer)

Qt中定时器用于周期性执行任务,提供两种实现方式:QTimerEvent(基于事件)和QTimer(基于信号槽,更简洁)。

1.3.1 QTimerEvent实现定时器

QTimerEvent通过startTimer()开启定时器(返回定时器ID),重写timerEvent()函数处理定时任务,适用于简单的定时场景。

示例:两个定时器分别实现1秒、2秒计数:

  1. 头文件声明
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    // 重写定时器事件
    void timerEvent(QTimerEvent *e);

private:
    Ui::Widget *ui;
    // 定义两个定时器ID
    int timer_id1 = 0;
    int timer_id2 = 0;
};

#endif // WIDGET_H
  1. 源文件实现
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 开启定时器,参数为定时时间(毫秒)
    timer_id1 = startTimer(1000); // 1秒
    timer_id2 = startTimer(2000); // 2秒
}

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

void Widget::timerEvent(QTimerEvent *e)
{
    // 判断定时器ID,处理对应任务
    if(e->timerId() == timer_id1)
    {
        static int num1 = 1;
        qDebug() << "1秒定时器:" << num1++;
    }
    else if(e->timerId() == timer_id2)
    {
        static int num2 = 1;
        qDebug() << "2秒定时器:" << num2++;
    }
}
1.3.2 QTimer实现定时器(推荐)

QTimer基于信号槽,使用更灵活,支持设置单次触发、暂停/停止等操作,需包含头文件。

示例:实现"开始/停止"计数功能:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化QTimer对象
    QTimer *timer = new QTimer(this);
    // 设置定时时间(毫秒)
    timer->setInterval(1000);

    // 开始按钮绑定
    connect(ui->btn_start, &QPushButton::clicked, [=](){
        timer->start(); // 开启定时器
    });

    // 停止按钮绑定
    connect(ui->btn_stop, &QPushButton::clicked, [=](){
        timer->stop(); // 停止定时器
    });

    // 定时触发的任务(timeout信号)
    static int num = 1;
    connect(timer, &QTimer::timeout, [=](){
        qDebug() << "计数:" << num++;
        ui->label->setText(QString::number(num)); // 显示到界面
    });
}

关键API:

  • setInterval(int msec):设置定时时间(毫秒);

  • setSingleShot(bool singleShot):设置单次触发(true时,定时器只执行一次);

  • start()/stop():开启/停止定时器;

  • timeout():定时时间到触发的信号。

1.4 事件分发器(event()函数)

Qt中所有事件都会先经过event()函数(事件分发器),该函数负责将事件分发给对应的事件处理函数(如keyPressEvent())。重写event()函数可实现事件拦截,阻止事件向下分发。

核心要点:

  • event()函数返回bool类型,true表示拦截事件,不再向下分发;false表示不拦截,交给父类处理。

  • 通过event->type()判断事件类型(QEvent::Type枚举),如QEvent::MouseButtonPress(鼠标按下)、QEvent::KeyPress(按键按下)。

示例:拦截鼠标按下事件,阻止mousePressEvent()执行:

cpp 复制代码
#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;
}

// 重写事件分发器
bool Widget::event(QEvent *ev)
{
    // 判断是否为鼠标按下事件
    if(ev->type() == QEvent::MouseButtonPress)
    {
        qDebug() << "Event中拦截鼠标按下事件";
        return true; // 拦截,不向下分发
    }
    // 其他事件交给父类处理
    return QWidget::event(ev);
}

// 该函数不会被执行(事件被拦截)
void Widget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "鼠标左键被按下";
}

1.5 事件过滤器

事件过滤器用于拦截其他组件的事件,无需继承目标组件,只需给目标组件安装过滤器,重写eventFilter()函数即可,适用于多组件事件拦截场景,比事件分发器更灵活。

使用步骤:

  1. 给目标组件调用installEventFilter(this)安装过滤器;

  2. 重写eventFilter(QObject *obj, QEvent *e)函数,判断组件和事件类型,实现拦截逻辑。

示例:拦截Label组件的鼠标按下事件:

  1. 头文件声明
cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    // 声明事件过滤器函数
    bool eventFilter(QObject *obj, QEvent *e);

private:
    Ui::Widget *ui;
};

#endif // WIDGET_H
  1. 源文件实现
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent>
#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 给Label安装事件过滤器
    ui->label->installEventFilter(this);
}

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

bool Widget::eventFilter(QObject *obj, QEvent *e)
{
    // 判断是否是目标组件(Label)和目标事件(鼠标按下)
    if(obj == ui->label && e->type() == QEvent::MouseButtonPress)
    {
        qDebug() << "拦截Label的鼠标按下事件";
        return true; // 拦截事件
    }
    // 其他事件交给父类处理
    return QWidget::eventFilter(obj, e);
}

二、Qt文件操作

Qt提供QFile、QFileInfo等类用于文件操作,支持文件读写、路径获取、文件信息查询等功能,跨平台兼容性强,无需关注不同系统的路径格式差异。

2.1 QFile文件读写

QFile用于操作本地文件,支持文本文件、二进制文件的读写,需包含头文件,常用打开模式如下:

  • QIODevice::ReadOnly:只读模式;

  • QIODevice::WriteOnly:只写模式(覆盖原有内容);

  • QIODevice::Append:追加模式(在文件末尾写入);

  • QIODevice::ReadWrite:读写模式。

2.1.1 读文件示例

读取本地文本文件内容并显示到界面:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QFile>
#include <QFileDialog>
#include <QDebug>

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

    // 点击按钮,选择文件并读取
    connect(ui->btn_open, &QPushButton::clicked, [=](){
        // 打开文件对话框,选择文本文件
        QString path = QFileDialog::getOpenFileName(this, "打开文件", "C:\\Users\\Lenovo\\Desktop", "文本文件(*.txt)");
        if(path.isEmpty()) return; // 取消选择则返回

        // 显示文件路径
        ui->lineEdit->setText(path);

        // 实例化QFile对象
        QFile file(path);
        // 以只读模式打开文件
        bool isOpen = file.open(QIODevice::ReadOnly);
        if(isOpen)
        {
            // 读取文件内容(读取所有内容)
            QByteArray data = file.readAll();
            // 转换为QString并显示到TextEdit
            ui->textEdit->setText(QString(data));
        }
        else
        {
            qDebug() << "文件打开失败:" << file.errorString();
        }
        // 关闭文件(必须执行)
        file.close();
    });
}
2.1.2 写文件示例

向文件中追加内容:

cpp 复制代码
// 点击按钮,向文件追加内容
connect(ui->btn_write, &QPushButton::clicked, [=](){
    QString path = ui->lineEdit->text();
    if(path.isEmpty()) return;

    QFile file(path);
    // 以追加模式打开文件
    if(file.open(QIODevice::Append))
    {
        // 写入内容(QString转换为QByteArray)
        QString content = "\n【追加的内容】";
        file.write(content.toUtf8());
        qDebug() << "写入成功";
    }
    else
    {
        qDebug() << "文件打开失败:" << file.errorString();
    }
    file.close();
});

2.2 QFileInfo文件信息查询

QFileInfo用于获取文件的详细信息(文件名、大小、创建时间、后缀名等),需包含头文件,常用API如下:

  • fileName():获取文件名(含后缀);

  • suffix():获取文件后缀名;

  • size():获取文件大小(字节);

  • path():获取文件路径(不含文件名);

  • isFile():判断是否为文件;

  • isDir():判断是否为目录;

  • lastModified():获取文件最后修改时间;

  • fileTime(QFileDevice::FileBirthTime):获取文件创建时间。

示例:查询文件信息:

cpp 复制代码
#include <QFileInfo>
#include <QDateTime>

// 点击按钮,查询文件信息
connect(ui->btn_info, &QPushButton::clicked, [=](){
    QString path = ui->lineEdit->text();
    if(path.isEmpty()) return;

    QFileInfo fileInfo(path);
    qDebug() << "文件名:" << fileInfo.fileName();
    qDebug() << "后缀名:" << fileInfo.suffix();
    qDebug() << "文件大小:" << fileInfo.size() << "字节";
    qDebug() << "文件路径:" << fileInfo.path();
    qDebug() << "是否为文件:" << fileInfo.isFile();
    qDebug() << "创建时间:" << fileInfo.fileTime(QFileDevice::FileBirthTime).toString("yyyy-MM-dd hh:mm:ss");
    qDebug() << "最后修改时间:" << fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss");
});

三、Qt多线程编程

在Qt中,多线程通过QThread类实现,用于解决耗时任务(如大数据处理、网络请求)阻塞UI界面的问题。Qt多线程的核心是"线程与UI分离",线程中禁止直接操作UI组件,需通过信号槽通信。

3.1 QThread核心API

API 说明
run() 线程入口函数,需重写,线程任务在此执行
start() 开启线程,自动调用run(),不可直接调用run()
currentThread() 返回当前执行线程的指针
sleep(int sec)/msleep(int msec)/usleep(int usec) 线程休眠,单位分别为秒、毫秒、微秒
wait() 阻塞线程,等待线程执行完成,防止内存泄漏
terminate() 强制终止线程(不推荐,可能导致资源泄漏)
finished() 信号,线程执行完成后触发

3.2 多线程创建步骤

  1. 自定义线程类,继承QThread;

  2. 重写run()函数,编写线程任务;

  3. 实例化自定义线程对象,调用start()开启线程;

  4. 通过信号槽实现线程与UI的通信。

3.2.1 示例:多线程定时发送时间给UI
  1. 创建自定义线程类(TimeThread)
cpp 复制代码
// timethread.h
#ifndef TIMETHREAD_H
#define TIMETHREAD_H

#include <QThread>
#include <QObject>

class TimeThread : public QThread
{
    Q_OBJECT
public:
    TimeThread();
    // 重写run()函数
    void run() override;

signals:
    // 发送时间的信号(线程中不可操作UI,通过信号槽传递)
    void sendTime(QString time);
};

#endif // TIMETHREAD_H
cpp 复制代码
// timethread.cpp
#include "timethread.h"
#include <QTime>
#include <QDebug>

TimeThread::TimeThread()
{

}

void TimeThread::run()
{
    // 线程任务:每秒获取一次当前时间,发送信号
    while(1)
    {
        QString time = QTime::currentTime().toString("hh:mm:ss");
        emit sendTime(time); // 发送信号
        sleep(1); // 休眠1秒
    }
}
  1. UI界面关联线程
cpp 复制代码
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "timethread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    // 接收线程发送的时间,更新UI
    void showTime(QString time);

private:
    Ui::Widget *ui;
    TimeThread *t; // 线程对象
};

#endif // WIDGET_H
cpp 复制代码
// widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 实例化线程对象
    t = new TimeThread;
    // 关联信号槽(线程发送信号,UI接收并更新)
    connect(t, &TimeThread::sendTime, this, &Widget::showTime);
    // 开启线程
    t->start();
}

Widget::~Widget()
{
    delete ui;
    // 等待线程完成,释放资源
    t->wait();
    delete t;
}

// 更新UI显示时间
void Widget::showTime(QString time)
{
    ui->label->setText(time);
}

3.3 线程安全与同步

多线程同时操作共享资源时,会出现数据竞争问题(如两个线程同时修改同一个变量),Qt提供多种同步机制解决该问题,常用的有互斥锁、读写锁、条件变量、信号量。

3.3.1 互斥锁(QMutex与QMutexLocker)

QMutex通过上锁/解锁控制共享资源的访问,确保同一时间只有一个线程操作共享资源;QMutexLocker是QMutex的辅助类,采用RAII机制,自动上锁/解锁,避免忘记解锁导致死锁。

示例:多线程操作共享变量(使用QMutexLocker):

cpp 复制代码
// mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
    void run() override;

private:
    static QMutex mutex; // 静态互斥锁,多个线程共用
    static int num; // 共享变量
};

#endif // MYTHREAD_H
cpp 复制代码
// mythread.cpp
#include "mythread.h"
#include <QDebug>

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

MyThread::MyThread()
{

}

void MyThread::run()
{
    while(1)
    {
        // QMutexLocker自动上锁,作用域结束自动解锁
        QMutexLocker locker(&mutex);
        qDebug() << "线程:" << this << ",共享变量值:" << num++;
        sleep(1);
    }
}

主函数中开启两个线程:

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

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MyThread *t1 = new MyThread;
    MyThread *t2 = new MyThread;
    t1->start();
    t2->start();

    return a.exec();
}

效果:两个线程依次修改共享变量,不会出现数据混乱。

3.3.2 其他同步机制
  • 读写锁(QReadWriteLock):区分读操作和写操作,允许多个线程同时读,只允许一个线程写,提升读操作的并发效率,搭配QReadLocker(读锁)和QWriteLocker(写锁)使用。

  • 条件变量(QWaitCondition):用于线程间的通信与同步,让线程等待某个条件满足后再执行,常与QMutex配合使用。

  • 信号量(QSemaphore):控制同时访问共享资源的线程数量,适用于资源有限的场景(如限制2个线程同时访问某个文件)。

四、Qt网络编程

Qt对网络编程进行了封装,提供Qt Network模块,支持UDP、TCP、HTTP等常见网络通信协议,跨平台无需修改代码。使用前需在.pro文件中添加QT += network

4.1 UDP通信

UDP是无连接、不可靠的传输协议,适用于实时性要求高(如语音、视频)、数据量小的场景,核心类为QUdpSocket和QNetworkDatagram。

4.1.1 UDP回显服务器

回显服务器功能:接收客户端发送的数据,原样返回给客户端。

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QUdpSocket>
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("UDP服务器");

    // 实例化QUdpSocket
    socket = new QUdpSocket(this);
    // 绑定端口(9090),允许所有IP访问
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if(!ret)
    {
        QMessageBox::critical(this, "错误", "端口绑定失败!");
        return;
    }

    // 关联readyRead信号(收到数据触发)
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
}

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

// 处理客户端请求
void Widget::processRequest()
{
    // 读取客户端发送的数据报
    QNetworkDatagram datagram = socket->receiveDatagram();
    // 获取客户端IP、端口和数据
    QHostAddress clientIP = datagram.senderAddress();
    quint16 clientPort = datagram.senderPort();
    QString request = datagram.data();

    // 打印日志
    QString log = QString("[%1:%2] 收到请求:%3").arg(clientIP.toString()).arg(clientPort).arg(request);
    ui->listWidget->addItem(log);

    // 回显数据(原样返回)
    QNetworkDatagram responseDatagram(request.toUtf8(), clientIP, clientPort);
    socket->writeDatagram(responseDatagram);

    // 打印回显日志
    log = QString("[%1:%2] 发送响应:%3").arg(clientIP.toString()).arg(clientPort).arg(request);
    ui->listWidget->addItem(log);
}
4.1.2 UDP客户端
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QUdpSocket>

// 服务器IP和端口(本地测试)
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);
    this->setWindowTitle("UDP客户端");

    // 实例化QUdpSocket
    socket = new QUdpSocket(this);

    // 关联readyRead信号(接收服务器响应)
    connect(socket, &QUdpSocket::readyRead, this, [=](){
        QNetworkDatagram datagram = socket->receiveDatagram();
        QString response = datagram.data();
        ui->listWidget->addItem("服务器:" + response);
    });

    // 发送按钮绑定
    connect(ui->btn_send, &QPushButton::clicked, [=](){
        QString text = ui->lineEdit->text();
        if(text.isEmpty()) return;

        // 发送数据到服务器
        QNetworkDatagram datagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
        socket->writeDatagram(datagram);

        // 显示发送日志
        ui->listWidget->addItem("客户端:" + text);
        ui->lineEdit->clear();
    });
}

4.2 TCP通信

TCP是面向连接、可靠的传输协议,适用于数据传输可靠性要求高(如文件传输、登录验证)的场景,核心类为QTcpServer(服务器)和QTcpSocket(客户端/连接)。

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("TCP服务器");

    // 实例化QTcpServer
    tcpServer = new QTcpServer(this);
    // 绑定端口(9090)
    bool ret = tcpServer->listen(QHostAddress::Any, 9090);
    if(!ret)
    {
        QMessageBox::critical(this, "错误", "服务器启动失败!");
        exit(1);
    }

    // 关联newConnection信号(有新客户端连接触发)
    connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
}

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

// 处理客户端连接
void Widget::processConnection()
{
    // 获取客户端连接的socket
    QTcpSocket *clientSocket = tcpServer->nextPendingConnection();
    // 打印客户端上线日志
    QString log = QString("[%1:%2] 客户端上线").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort());
    ui->listWidget->addItem(log);

    // 关联readyRead信号(收到客户端数据)
    connect(clientSocket, &QTcpSocket::readyRead, [=](){
        // 读取数据
        QString request = clientSocket->readAll();
        // 打印日志
        log = QString("[%1:%2] 收到请求:%3").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort()).arg(request);
        ui->listWidget->addItem(log);
        // 回显数据
        clientSocket->write(request.toUtf8());
        // 打印回显日志
        log = QString("[%1:%2] 发送响应:%3").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort()).arg(request);
        ui->listWidget->addItem(log);
    });

    // 关联disconnected信号(客户端断开连接)
    connect(clientSocket, &QTcpSocket::disconnected, [=](){
        // 打印客户端下线日志
        log = QString("[%1:%2] 客户端下线").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort());
        ui->listWidget->addItem(log);
        // 释放socket资源
        clientSocket->deleteLater();
    });
}
4.2.2 TCP客户端
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QTcpSocket>
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("TCP客户端");

    // 实例化QTcpSocket
    socket = new QTcpSocket(this);
    // 连接服务器(IP:127.0.0.1,端口:9090)
    socket->connectToHost("127.0.0.1", 9090);
    // 等待连接结果
    if(!socket->waitForConnected())
    {
        QMessageBox::critical(this, "错误", "连接服务器失败!");
        exit(1);
    }

    // 关联readyRead信号(接收服务器响应)
    connect(socket, &QTcpSocket::readyRead, [=](){
        QString response = socket->readAll();
        ui->listWidget->addItem("服务器:" + response);
    });

    // 发送按钮绑定
    connect(ui->btn_send, &QPushButton::clicked, [=](){
        QString text = ui->lineEdit->text();
        if(text.isEmpty()) return;

        // 发送数据到服务器
        socket->write(text.toUtf8());
        // 显示发送日志
        ui->listWidget->addItem("客户端:" + text);
        ui->lineEdit->clear();
    });
}

4.3 HTTP客户端

Qt通过QNetworkAccessManager、QNetworkRequest、QNetworkReply类实现HTTP请求,支持GET、POST等请求方式,适用于与Web服务器通信(如获取网页内容、接口调用)。

示例:发送GET请求获取网页内容:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>

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

    // 实例化QNetworkAccessManager
    manager = new QNetworkAccessManager(this);

    // 发送按钮绑定
    connect(ui->btn_send, &QPushButton::clicked, [=](){
        // 获取URL
        QString urlStr = ui->lineEdit->text();
        if(urlStr.isEmpty()) return;
        QUrl url(urlStr);

        // 构造HTTP请求
        QNetworkRequest request(url);
        // 设置请求头(可选)
        request.setHeader(QNetworkRequest::UserAgentHeader, "Qt HTTP Client");

        // 发送GET请求
        QNetworkReply *reply = manager->get(request);

        // 关联finished信号(请求完成触发)
        connect(reply, &QNetworkReply::finished, [=](){
            if(reply->error() == QNetworkReply::NoError)
            {
                // 请求成功,读取响应内容
                QString html = reply->readAll();
                ui->plainTextEdit->setPlainText(html);
            }
            else
            {
                // 请求失败,显示错误信息
                ui->plainTextEdit->setPlainText("请求失败:" + reply->errorString());
            }
            // 释放reply资源
            reply->deleteLater();
        });
    });
}

五、Qt音视频播放

Qt通过Multimedia模块实现音视频播放,音频播放使用QSound类,视频播放使用QMediaPlayer+QVideoWidget组合,使用前需添加对应的模块。

5.1 音频播放(QSound)

QSound仅支持WAV格式音频,使用前需在.pro文件中添加QT += multimedia,包含头文件。

示例:播放WAV音频:

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

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

    // 实例化QSound,参数为音频文件路径(可使用资源文件)
    QSound *sound = new QSound(":/res/1.wav", this);

    // 播放按钮绑定
    connect(ui->btn_play, &QPushButton::clicked, [=](){
        sound->play(); // 播放音频
    });

    // 停止按钮绑定
    connect(ui->btn_stop, &QPushButton::clicked, [=](){
        sound->stop(); // 停止音频
    });
}

5.2 视频播放(QMediaPlayer+QVideoWidget)

视频播放需结合QMediaPlayer(负责播放控制)和QVideoWidget(负责视频显示),使用前需在.pro文件中添加QT += multimedia multimediawidgets

示例:视频播放功能:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMediaPlayer>
#include <QVideoWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("Qt视频播放器");

    // 1. 实例化核心组件
    QMediaPlayer *player = new QMediaPlayer(this);
    QVideoWidget *videoWidget = new QVideoWidget(this);
    // 设置视频显示组件(将视频渲染到QVideoWidget上)
    player->setVideoOutput(videoWidget);

    // 2. 布局设置(视频显示区+控制按钮区)
    QVBoxLayout *vLayout = new QVBoxLayout(this);
    QHBoxLayout *hLayout = new QHBoxLayout();

    // 控制按钮实例化
    QPushButton *btn_open = new QPushButton("打开视频", this);
    QPushButton *btn_play = new QPushButton("播放", this);
    QPushButton *btn_pause = new QPushButton("暂停", this);
    QPushButton *btn_stop = new QPushButton("停止", this);

    // 按钮添加到水平布局
    hLayout->addWidget(btn_open);
    hLayout->addWidget(btn_play);
    hLayout->addWidget(btn_pause);
    hLayout->addWidget(btn_stop);

    // 视频组件和控制区添加到垂直布局
    vLayout->addWidget(videoWidget);
    vLayout->addLayout(hLayout);
    this->setLayout(vLayout);

    // 3. 按钮绑定逻辑
    // 打开视频文件
    connect(btn_open, &QPushButton::clicked, [=](){
        QString videoPath = QFileDialog::getOpenFileName(this, "选择视频文件", 
                                                        "", "视频文件(*.mp4 *.avi *.mkv)");
        if(videoPath.isEmpty()) return;

        // 设置视频源(支持本地文件路径或网络URL)
        player->setSource(QUrl::fromLocalFile(videoPath));
        // 自动播放(可选)
        player->play();
    });

    // 播放
    connect(btn_play, &QPushButton::clicked, [=](){
        player->play();
    });

    // 暂停
    connect(btn_pause, &QPushButton::clicked, [=](){
        player->pause();
    });

    // 停止
    connect(btn_stop, &QPushButton::clicked, [=](){
        player->stop();
        // 停止后重置视频显示位置(可选)
        videoWidget->update();
    });

    // 4. 播放状态监听(可选,优化用户体验)
    connect(player, &QMediaPlayer::playbackStateChanged, [=](QMediaPlayer::PlaybackState state){
        // 根据播放状态设置按钮可用性
        if(state == QMediaPlayer::PlayingState)
        {
            btn_play->setEnabled(false);
            btn_pause->setEnabled(true);
            btn_stop->setEnabled(true);
        }
        else if(state == QMediaPlayer::PausedState)
        {
            btn_play->setEnabled(true);
            btn_pause->setEnabled(false);
            btn_stop->setEnabled(true);
        }
        else if(state == QMediaPlayer::StoppedState)
        {
            btn_play->setEnabled(true);
            btn_pause->setEnabled(false);
            btn_stop->setEnabled(false);
        }
    });

    // 5. 错误监听(可选)
    connect(player, &QMediaPlayer::errorOccurred, [=](QMediaPlayer::Error error, const QString &errorString){
        QMessageBox::critical(this, "播放错误", "错误信息:" + errorString);
    });
}

Widget::~Widget()
{
    delete ui;
}
5.2.1 关键说明
  • 格式支持:Qt自带的多媒体模块对视频格式支持有限,若需播放更多格式(如RMVB),需集成FFmpeg(需自行编译配置,适合进阶开发)。

  • 组件搭配:QMediaPlayer负责音频/视频解码与播放控制,QVideoWidget负责视频渲染,二者必须通过setVideoOutput()绑定。

  • 布局注意:需给QVideoWidget设置布局,否则视频无法正常显示(示例中使用QVBoxLayout+QHBoxLayout实现自适应布局)。

5.2.2 常见问题解决
  • 视频无法播放:检查.pro文件是否添加QT += multimedia multimediawidgets,且Qt版本与系统环境匹配(64位系统需使用64位Qt)。

  • 无画面有声音:确认QMediaPlayer与QVideoWidget已通过setVideoOutput()绑定,且视频文件路径正确。

  • 控件显示异常:检查布局是否正确设置,避免QVideoWidget未被添加到主布局中。

六、总结

本文围绕Qt进阶开发的核心场景,详细讲解了事件处理、文件操作、多线程、网络编程及音视频播放五大模块,每个知识点均搭配完整可运行的示例代码,覆盖从基础用法到实际场景的应用。Qt的核心优势在于跨平台兼容性和完善的API封装,开发者可根据需求灵活选用对应模块:

  • 交互开发:重点掌握事件处理机制(事件分发器、事件过滤器),应对复杂的用户交互场景。

  • 本地文件操作:使用QFile+QFileInfo组合,高效实现文件读写与信息查询,无需关注跨平台路径差异。

  • 耗时任务处理:通过QThread实现多线程,结合互斥锁、信号槽解决线程安全与UI通信问题。

  • 网络通信:UDP适合实时性场景,TCP适合可靠性场景,HTTP适合与Web接口交互,按需选择协议。

  • 音视频开发:基础场景使用QSound+QMediaPlayer,复杂格式需集成FFmpeg扩展功能。

后续可结合实际项目需求,深入学习Qt进阶特性(如自定义控件、数据库操作、Qt Quick等),进一步提升开发效率与项目质量。

相关推荐
草原上唱山歌2 小时前
C++如何调用Python代码
开发语言·c++·python
木子啊2 小时前
PHP中间件:ThinkCMF 6.x核心利器解析
开发语言·中间件·php
会开花的二叉树2 小时前
深入理解Reactor模式
网络
崇山峻岭之间2 小时前
Matlab学习记录40
开发语言·学习·matlab
Java后端的Ai之路2 小时前
【Python教程11】-文件
开发语言·python
黛玉晴雯子0012 小时前
Kubernets-组件与网络与原理(持续更新)
网络
先做个垃圾出来………2 小时前
SortedList(2)
开发语言
云栖梦泽2 小时前
易语言开发从入门到精通:补充篇·文件批量操作深度实战·常用格式处理·自动化脚本开发·性能优化
开发语言
寄存器漫游者2 小时前
数据结构 二叉树核心概念与特性
数据结构·算法