重学Qt——事件处理

事件处理

Qt事件系统

事件驱动的原理

GUI应用程序是基于事件驱动的,用户与窗口系统的交互动作(如点击鼠标、按键操作、改变窗口大小等)会产生相应的事件。应用程序需要对这些事件进行响应和处理,以实现特定的功能。

  • 事件的产生通常来自于用户与应用程序的交互或是定时器、系统信号等。这些事件会被派发到相应的QWidget对象
  • 事件通过事件处理机制在QWidget及其子类之间进行分发和处理。每个QWidget都可以处理自己感兴趣的事件。
  • QWidget是所有Qt界面组件的基类,定义了大量与事件处理相关的数据类型和接口函数。

事件与信号的关系

事件和信号是紧密相关的。当一个事件发生时,通常会触发一个信号,然后可以通过槽函数来处理这个信号及其相关的事件。

事件拦截和过滤

  1. 事件拦截:在某些情况下,你可能希望在事件被目标组件处理之前对其进行拦截和处理。这可以通过重写QWidget的事件处理函数来实现。
  2. 事件过滤:事件过滤允许你在全局范围内拦截和修改事件,而不仅仅是针对特定的组件。这可以通过继承QObject的子类并使用installEventFilter函数来实现。

拖放操作相关事件的处理

Qt提供了对拖放操作的支持,通过处理拖放相关的事件(如拖动开始、拖动结束、放置等),可以实现丰富的拖放功能。

事件处理的基本过程

事件的产生

在Qt中,事件是对象,是QEvent类或其派生类的实例。

事件的分类

按事件的来源,可将事件划分为三类:

  • 自生事件(Spontaneous Event):由窗口系统产生的事件,例如键盘和鼠标事件。这些事件会进入系统队列,然后被应用程序的事件循环逐个处理。
  • 发布事件(Posted Event):由Qt或应用程序产生的事件。例如,定时器发生定时溢出时Qt发布的定时器事件。应用程序使用静态函数QCoreApplication::postEvent()产生发布事件,它会进入Qt事件队列,然后由应用程序的事件循环进行处理。
  • 发送事件(Sent Event):由Qt或应用程序定向发送给某个对象的事件。这类事件由应用程序使用静态函数QCoreApplication::sendEvent()产生,并由对象的event()函数直接处理。
事件派发

事件处理机制

  1. 自生事件和发布事件的处理是异步的,即事件进入队列后由系统去处理,程序不会在产生事件的地方停止进行等待。
  2. 应用程序调用QCoreApplication::postEvent()发布事件后,这个函数会立刻退出,不会等到事件处理完之后再退出。
  3. 窗口系统产生的自生事件自动进入系统队列,应用程序发布的事件进入Qt事件队列。

示例:各类事件的使用

cpp 复制代码
#include <QApplication>
#include <QWidget>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QVBoxLayout>
#include <QLabel>

class EventExampleWidget : public QWidget {
    Q_OBJECT

public:
    EventExampleWidget() {
        setFixedSize(400, 300);
        setWindowTitle("Qt Event Example");
        layout = new QVBoxLayout(this);
        messageLabel = new QLabel("Please interact with the window.", this);
        layout->addWidget(messageLabel);
    }

protected:
    void mousePressEvent(QMouseEvent *event) override {
        if (event->button() == Qt::LeftButton) {
            messageLabel->setText("Mouse Pressed: (" + QString::number(event->pos().x()) + ", " + QString::number(event->pos().y()) + ")");
        }
    }

    void keyPressEvent(QKeyEvent *event) override {
        messageLabel->setText("Key Pressed: " + event->text());
    }

    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);
        painter.setBrush(Qt::lightGray);
        painter.drawRect(rect());
    }

private:
    QVBoxLayout *layout;
    QLabel *messageLabel;
};

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

    EventExampleWidget window;
    window.show();

    return app.exec();
}

#include "main.moc"

代码解析

  1. 创建主窗口:继承自 QWidget 的 EventExampleWidget 类。
  2. 构造函数:
    • 设置窗口大小和标题。
    • 创建布局和标签用于显示消息。
  3. 事件处理:
    • 鼠标按压事件:覆盖 mousePressEvent,检查鼠标按键并显示按下的位置。
    • 键盘按下事件:覆盖 keyPressEvent,显示按下的键。
    • 绘图事件:覆盖 paintEvent,绘制窗口背景。
  4. 主函数:
    • 创建 QApplication 和 EventExampleWidget 实例并显示。

运行结果:

GUI应用程序的main()函数结构

  • GUI应用程序的main()函数通常创建QApplication对象来初始化应用程序,并创建窗口对象来显示界面。
  • 通过调用w.show()显示窗口,最后通过调用a.exec()启动应用程序的事件循环。

QApplication::exec()的功能

  • 检查系统队列和Qt事件队列中是否有未处理的事件。
  • 派发(dispatch)自生事件和发布事件给相应的接收对象进行处理。
  • 对相同事件进行合并处理,优化事件处理效率。

事件类型与处理模式

  • 自生事件和发布事件:由应用程序的事件循环处理。
  • 发送事件:由应用程序直接派发给某个对象,以同步模式运行。

事件处理的重要性

  • 通常情况下,应用程序能够及时处理队列中的事件,用户操作响应迅速。但在执行大量计算或数据传输等长时间占用CPU的代码时,可能会出现界面响应迟滞或无响应的情况。

涉及大量事件时的解决方案:

  • 多线程方法:将界面更新与网络数据传输等任务分别放在不同的线程中处理,避免界面无响应。
  • 调用QCoreApplication的processEvents()函数:在长时间占用CPU的代码段中偶尔调用该函数,以派发事件队列中的未处理事件,避免程序停滞。

示例:processEvents

cpp 复制代码
#include <QApplication>
#include <QPushButton>
#include <QProgressBar>
#include <QVBoxLayout>
#include <QWidget>
#include <QThread>
#include <QDebug>

class LongTask : public QThread {
    Q_OBJECT

public:
    void run() override {
        for (int i = 0; i <= 100; ++i) {
            QThread::sleep(1); // 模拟耗时操作
            emit progressUpdated(i);
        }
        emit taskFinished();
    }

signals:
    void progressUpdated(int value);
    void taskFinished();
};

class MyWindow : public QWidget {
    Q_OBJECT

public:
    MyWindow() {
        auto *layout = new QVBoxLayout(this);
        auto *button = new QPushButton("Start Long Task", this);
        progressBar = new QProgressBar(this);
        progressBar->setRange(0, 100);

        layout->addWidget(button);
        layout->addWidget(progressBar);

        connect(button, &QPushButton::clicked, this, &MyWindow::startTask);
    }

public slots:
    void startTask() {
        LongTask *task = new LongTask;

        connect(task, &LongTask::progressUpdated, this, &MyWindow::updateProgress);
        connect(task, &LongTask::taskFinished, task, &QObject::deleteLater);

        task->start();

        // 在主线程中处理事件
        while (task->isRunning()) {
            QApplication::processEvents();
        }
    }

    void updateProgress(int value) {
        progressBar->setValue(value);
    }

private:
    QProgressBar *progressBar;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyWindow window;
    window.show();
    return app.exec();
}

#include "main.moc"

运行结果:

代码解析:

  • LongTask 类 :继承自 QThread,用于模拟一个长时间运行的任务。在任务中,定期发出 progressUpdated() 信号更新进度。
  • MyWindow 类:创建一个带有按钮和进度条的窗口。点击按钮时,启动长时间任务。
  • startTask() 方法 :当任务正在运行时,通过调用 QApplication::processEvents() 处理其他事件,例如界面更新和用户输入。
  • updateProgress() 方法 :处理来自 LongTask 的进度更新信号,更新进度条。
事件类

事件是 QEvent类或其派生类的实例,大多数的事件有其专门的类。

QEvent是所有事件类的基类,但它不是一个抽象类,它也可以用于创建事件。

QEvent 主要的接口函数

函数 返回类型 功能描述
QEvent(Type type) - 构造函数,创建一个指定类型的事件。
Type type() const Type 返回事件的类型。
bool spontaneous() const bool 指示事件是否为自发事件。
void setSpontaneous(bool spontaneous) void 设置事件是否为自发事件。
void accept() void 标记事件为已接受,不再传播。
bool isAccepted() const bool 返回事件是否被接受。
void ignore() void 标记事件为未被接受,可以继续传播。
bool isIgnored() const bool 返回事件是否被忽略。

常见的事件类型及其所属的事件类

事件类 事件类型 事件描述
QMouseEvent 鼠标事件 描述鼠标事件,如点击、移动、释放等。
QWheelEvent 滚轮事件 描述鼠标滚轮事件。
QHoverEvent 悬停事件 描述鼠标悬停在某个对象上时的事件。
QEnterEvent 进入事件 描述鼠标或焦点进入某个对象时的事件。
QEvent 基础事件类 所有 Qt 事件类的基类,用于处理事件。
QKeyEvent 键盘事件 描述键盘事件,如按键按下、释放等。
QFocusEvent 焦点事件 描述对象获得或失去焦点时的事件。
QShowEvent 显示事件 描述对象被显示时的事件。
QHideEvent 隐藏事件 描述对象被隐藏时的事件。
QMoveEvent 移动事件 描述对象移动时的事件。
QCloseEvent 关闭事件 描述窗口被关闭时的事件。
QPaintEvent 绘制事件 描述需要重新绘制对象时的事件。
QResizeEvent 大小调整事件 描述对象大小改变时的事件。
QStatusTipEvent 状态提示事件 描述状态提示信息出现的事件。
QHelpEvent 帮助事件 描述用户请求帮助时的事件。
QDragEnterEvent 拖放进入事件 描述对象被拖拽进入时的事件。
QDragLeaveEvent 拖放离开事件 描述对象被拖拽离开时的事件。
QDragMoveEvent 拖放移动事件 描述对象拖拽过程中移动时的事件。
QDropEvent 拖放释放事件 描述对象被放下(如文件拖放到应用程序中)时的事件。
QTouchEvent 触摸事件 描述触摸屏幕上的触摸事件。
QGestureEvent 手势事件 描述识别到的手势事件。
QNativeGestureEvent 本地手势事件 描述平台特定的手势事件。
QActionEvent 动作触发事件 描述动作触发的事件,例如用户点击菜单项或工具栏按钮等。
事件处理

事件处理的基本过程

  • 事件处理的基本概念:任何从QObject派生的类都可以处理事件,其中主要是窗口类和界面组件类(从QWidget派生)需要处理事件,因为事件大多通过界面操作产生。
  • 事件处理的基本流程:类接收到应用程序派发来的事件后,首先由函数event()处理。
  • QEvent类的两个重要函数:accept()和ignore(),前者表示事件接收者会处理事件,后者表示事件接收者不接受此事件。被接受的事件由事件接收者处理,被忽略的事件则传播到父容器组件。

QWidget类的典型事件处理函数

  1. QWidget类概述:QWidget是界面组件类的基类,它重新实现了函数event(),并针对典型事件定义了专门的事件处理函数。
  2. 事件处理函数的自动运行:函数event()会根据事件类型自动运行相应的事件处理函数。例如,针对鼠标移动和绘图事件,会分别运行mouseMoveEvent()和paintEvent()函数。
  3. 典型事件处理函数的特性:这些函数是受保护的虚函数,不能被外部类调用,但可以被派生类重新实现。
  4. 自定义类的事件处理:如果自定义的类从QWidget派生,并需要重新处理某个典型事件,可以重新实现对应的事件处理函数。例如,要在窗口上绘制背景图片,可以重新实现paintEvent()函数。

QWidget类中定义的典型事件处理函数

列举并简要说明QWidget类中定义的典型事件处理函数,如paintEvent()、mouseMoveEvent()等,并指出这些函数的主要作用。

同时说明一个事件类可能会处理多个类型的事件,因此这些函数的参数event的类型可能相同。

事件与信号

事件和信号的基本概念

  • 事件通常是由窗口系统或应用程序产生的。
  • 信号是Qt定义或用户自定义的,用于表示某种动作或状态改变。

Qt中的信号与事件的关系

  • Qt为界面组件定义的信号通常是对事件的封装。例如,QPushButton的clicked()信号是对鼠标按钮释放事件(QEvent::MouseButtonRelease)的封装。Qt为了处理界面组件的交互操作,需要选择合适的信号,并为这些信号编写槽函数。槽函数是响应信号执行的代码块。

自定义信号和事件处理的方法

  1. 当需要处理某些Qt界面组件未封装的事件时,可以从该组件继承并定义一个新的类。
  2. 在新类中,可以自定义信号和事件处理函数,以实现处理特定事件的需求。例如,为QLabel添加处理鼠标双击事件的信号。
事件过滤器

事件处理的基本概念

在界面中,事件是用户与应用程序交互的基础,如点击、键盘输入等。事件产生后,会被派发给相应的接收者,并由接收者的event()函数或其他特定的事件处理函数进行处理。

传统事件处理方式

为了处理特定的事件,通常需要定义一个新的类,从父类继承,并在新类中编写程序直接处理事件或将事件转换为信号。

这种方式需要在类的定义和实现上工作。

如果不想重新定义类,可以考虑事件过滤器。

事件过滤器的概念和工作原理

  • 事件过滤器是Qt中QObject提供的一种处理事件的方法。它允许将一个对象的事件委托给另一个对象来监视并处理
  • 工作原理:当一个对象(被监视对象)使用installEventFilter()函数将自己注册到另一个对象(监视对象,即事件过滤器)时,该监视对象将能够接收并处理来自被监视对象的事件。

如何实现事件过滤器功能

  1. 设置事件过滤器:被监视的对象通过调用installEventFilter()函数,将某个对象设置为自己的事件过滤器。
  2. 编写事件处理逻辑:作为事件过滤器的对象需要重写eventFilter()函数。此函数接收两个参数:被监视对象和产生的事件。根据事件的性质,函数可以选择结束事件处理(返回true)或将事件继续传播给其他对象(返回false)。

优点和使用场景

事件过滤器提供了一种灵活的方式来处理Qt界面中的事件,尤其适用于以下场景:

  • 当不想或不能为每个事件都重新定义一个新类时。
  • 当需要监控和干预其他组件的事件时。

拖放事件

拖放操作概述

拖放操作是GUI应用程序中常用的一种操作方式,包括拖动和放置两个步骤。被拖动的组件称为拖动点,接收拖动操作的组件称为放置点。拖动点和放置点可以是不同的组件或应用程序,也可以是同一组件内的节点。

拖放操作的基本原理

拖放操作通过一系列的事件处理来实现,主要包括以下几个步骤:

  1. 拖动点启动拖动操作:通过mousePressEvent()和mouseMoveEvent()事件处理函数检测鼠标左键按下并移动,创建QDrag对象描述拖动操作,并创建QMimeData对象存储拖动操作的格式信息和数据。
  2. 放置点处理放置操作:当拖动操作移动到放置点范围内时,首先触发dragEnterEvent()事件,通过QDrag对象的mimeData数据判断拖动操作的来源和参数,决定是否接受此拖动操作。被接受的拖动操作触发dropEvent()事件处理函数,处理放置时的具体操作。

相关类和属性

  1. QWidget类:有一个属性acceptDrops,如果设置为true,则对应的组件可以作为放置点。默认值为false。
  2. QAbstractItemView类:定义了更多与拖动操作相关的函数,其子类如QListWidget、QTreeWidget、QTableWidget等既可以作为拖动点,也可以作为放置点。

实现拖放操作的注意事项

要实现完整的拖放操作需要对各种事件进行处理,拖动点和放置点最好是各自实现相关事件处理的类。如果要在同一个窗口上实现这些事件的处理,需要用到事件过滤器。

拖放事件允许用户在应用程序之间或在应用程序内部拖动和放置数据。Qt 提供了完整的支持来处理拖放操作。

拖放事件种类

拖放(Drag and Drop)是一种用户界面交互方式,允许用户通过拖动和放置来移动数据。

Qt 提供了一系列事件和类来处理拖放操作,主要涉及以下几种事件:

事件名称 描述 处理方法
QDragEnterEvent 当一个拖动项进入目标控件时触发。 重写 dragEnterEvent,检查 mimeData 的格式并决定是否接受。
QDragMoveEvent 拖动项在目标控件内移动时触发。 重写 dragMoveEvent,可以更新状态提示或视觉反馈。
QDropEvent 当用户将拖动项放下时触发。 重写 dropEvent,处理 mimeData 中的数据。
QDragLeaveEvent 拖动项离开目标控件区域时触发。 重写 dragLeaveEvent,清理任何视觉反馈或状态提示。
相关推荐
小宋0014 小时前
QT中控件qss样式修改
开发语言·qt
yuechuji0018 小时前
三、MPR(三平面重建)和三视图
qt
Hua-Jay9 小时前
OpenCV联合C++/Qt 学习笔记(二十二)----相机模型与投影及单目相机标定
c++·笔记·qt·opencv·学习·计算机视觉
小短腿的代码世界11 小时前
QCefView架构深度解析:从Chromium嵌入到Qt信号槽集成的完整技术链路
qt·架构
byxdaz12 小时前
Qt修改操作系统的日期与时间
qt
小短腿的代码世界12 小时前
Qt属性系统揭秘:从Q_PROPERTY宏到动态元对象系统的完整架构解析
开发语言·qt·架构
丁劲犇12 小时前
QodeAssist:为msys2 ucrt64 Qt Creator 注入 AI 灵魂的开源插件
开发语言·人工智能·qt
listhi52012 小时前
基于QT的串口心电波形实时显示系统
开发语言·qt
charlie1145141911 天前
现代Qt开发教程(新手篇)2.3——QImage、QPixmap、QIcon 图像处理基础
开发语言·图像处理·qt