Qt篇: 自定义事件

在Qt框架中虽然提供了非常多的事件对象,但是在项目开发中,开发新功能或者自定义新组件时,自定义事件是至关重要的,能够掌握自定义事件对象本质,能够轻松驾驭各种功能的扩展和新自定义组件开发。

事件发送

Qt允许在程序中自主的发送事件,事件发送分为两种类型:阻塞型事件发送和非阻塞型事件发送,在QApplication中分别提供了这两种事件发送类型的静态函数

  • 阻塞型事件发送:事件发送后需要等待事件处理完成;
// sendEvent中事件对象的生命周期由Qt程序管理
// 支持 栈事件对象 和 堆事件对象 的发送
[static] bool sendEvent(QObject *receiver, QEvent *event)
sendEvent UML
  • 非阻塞型事件发送:事件发送后会立即返回,被发送出去的事件会在事件队列中等待处理。
// postEvent中事件对象的生命周期由Qt平台管理
// 只能发送堆事件对象,事件被处理后由Qt平台销毁
[static] void postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
postEvent UML

可以从下面的例子中体会sendEvent和postEvent的使用,两种方式使用结果显而易见:

// widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPushButton>

class Widget : public QWidget
{
    Q_OBJECT

    QPushButton button;

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

protected:
    bool event(QEvent *event);

private:
    void testSendEvent();
    void testPostEvent();

private slots:
    void onButtonClick();
};

#endif // WIDGET_H
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QApplication>
#include <QDebug>
#include <QMouseEvent>

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    this->resize(300, 300);
    button.setParent(this);
    button.setText("Click me");
    button.resize(100, 30);
    button.move(10, 10);
    connect(&button, QPushButton::clicked, this, Widget::onButtonClick);
}

Widget::~Widget(){}

void Widget::onButtonClick(){
//    testSendEvent();
    testPostEvent();
}

void Widget::testSendEvent(){
    qDebug() << "Send event begin...";
    QMouseEvent evt(QEvent::MouseButtonPress, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
    QApplication::sendEvent(this, &evt);
    qDebug() << "Send event end...";
}

void Widget::testPostEvent() {
    qDebug() << "Post event begin...";

    // 使用postEvent,事件对象必须是堆对象,否则会程序崩溃
    // QMouseEvent evt(QEvent::MouseButtonPress, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
    QMouseEvent *evt = new QMouseEvent(QEvent::MouseButtonPress, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
    QApplication::postEvent(this, evt);
    qDebug() << "Post event end...";
}

bool Widget::event(QEvent *event) {
    if(event->type() == QEvent::MouseButtonPress) {
        qDebug() << "event: " << event;
    }
    return QWidget::event(event);
}

sendEevent()调用结果:

sendEvent说明

postEvent()调用结果:

postEvent结果

自定义事件创建

Qt除了平台的默认事件外,还支持自定义事件类设定和使用。自定义事件必须遵循以下几点准则,缺一不可:

  • 自定义事件类必须继承QEvent
  • 自定义事件类必须拥有全局唯一的Type值,用来标记事件的ID,这是Qt平台规定必须要有;
  • 程序中必须提供自定义事件对象的处理方法。
QEvent::User说明

关于如何定义事件ID,其实很简单,只要遵循Qt平台的规则即可。Qt提供了QEvent::User和 QEvent::MaxUser枚举,这意思是表示Qt允许开发者们可以把事件ID设定在QEvent::User之后的值(QEvent::User + Value),但不能超过最大值QEvent::MaxUser。

class TestEvent : public QEvent {
  
public:
  static const Type type = static_cast<Type>(QEvent::User + 0x01);
  // do something...
}

自定义事件类对象创建后,千万别忘记了处理自定义事件类的方法。方法有两种:

  • 事件过滤器安装到目标对象,在eventFilter()函数中编写自定义事件的处理逻辑;
  • 在目标对象的类中重写事件处理函数,在event()函数中编写自定义事件的处理逻辑。

模拟一个点击按钮,将数据更新到QLineEdit中。通过自定义事件类来进行数据的传递,当然这仅仅这是一个模拟举例,自定义事件类的封装很强大,甚至可以作为多线程与UI组件交互的桥梁,后续文章会介绍到。

新增一个StringEvent事件类:

// stirngevent.h

#ifndef STRINGEVENT_H
#define STRINGEVENT_H

#include <QEvent>
#include <QString>

class StringEvent : public QEvent
{
    QString m_data;

public:
    const static Type type = static_cast<Type>(QEvent::User + 0x01);
    StringEvent(QString data);
    QString data();
};

#endif // STRINGEVENT_H
// stringevent.cpp

#include "stringevent.h"

StringEvent::StringEvent(QString data):QEvent(type) {
    m_data = data;
}

QString StringEvent::data() {
    return m_data;
}

StringEvent的使用:

#include "widget.h"
#include <QApplication>
#include <QDebug>
#include "stringevent.h"

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    this->resize(300, 300);

    QVBoxLayout *vLayout = new QVBoxLayout(this);
    m_button.setText("Click me");
    connect(&m_button, QPushButton::clicked, this, Widget::onButtonClick);
    vLayout->addWidget(&m_lineEdit);
    vLayout->addWidget(&m_button);

    m_button.installEventFilter(this);
}

Widget::~Widget(){}

void Widget::onButtonClick(){
    qDebug() << "Button click begin...";
    StringEvent strEvt("Vincent Lin...");
    QApplication::sendEvent(&m_button, &strEvt);
    qDebug() << "Button click end...";
}

bool Widget::eventFilter(QObject *watched, QEvent *event) {
    if( watched == &m_button && event->type() == StringEvent::type ) {
        StringEvent *strEvent = dynamic_cast<StringEvent*>(event);
        QString data = strEvent->data();
        m_lineEdit.setText(data);
        return true;
    }
    return QWidget::eventFilter(watched, event);
}