QT 基于QMetaObject:InvokeMethod的事件订阅发布

文章目录


前言

        在QT中,会出现同父容器的两个控件之间需要进行通讯,可以通过信号与槽函进行通讯,也可以通过指针来获取对应控件,博主在gitee上逛的时候,发现一份代码,基于QMetaObject::InvokeMethod便可以非常方便的在多个容器间进行通讯。

一、QT QMetaObject::InvokeMethod

        在QT中,QMetaObject包含了QObject子类的所有元数据,也就是QObject信息的一些描述信息:例如QT中特有的信号与槽信息,成员函数。然后通过元对象系统,我们可以查询QObject的某个派生类的类名、有哪些信号、槽、属性、可调用方法等信息,然后可以使用QMetaObject::invokeMethod()调用QObject的某个注册到元对象系统中的方法

1.函数原型

bool QMetaObject::invokeMethod(QObject *obj, const char *member, 
                            Qt::ConnectionType type, 
                            QGenericReturnArgument ret,
                            QGenericArgument val0 = QGenericArgument(nullptr), 
                            QGenericArgument val1 = QGenericArgument(), 
                            QGenericArgument val2 = QGenericArgument(), 
                            QGenericArgument val3 = QGenericArgument(), 
                            QGenericArgument val4 = QGenericArgument(),
                            QGenericArgument val5 = QGenericArgument(),
                            QGenericArgument val6 = QGenericArgument(), 
                            QGenericArgument val7 = QGenericArgument(),
                            QGenericArgument val8 = QGenericArgument(),
                            QGenericArgument val9 = QGenericArgument())

  2.函数介绍

在上面函数中,其中共有5种

QObject *obj:为被调用对象的指针。

const char*member:为调用对象成员变量方法。

Qt::Connectionype type:为连接该对象的方法,可以通过这里确定是同步还是异步调用,

默认为Qt::AutoConnect。

QGenericReturnArgument ret:为调用的成员方法返回值。

QGenericArgument val0~val9:为传入被调用函数的形参。最多存在10个

注意,要调用的类型必须是信号、槽,以及Qt元对象系统能识别的类型, 如果不是信号和槽,可以使用qRegisterMetaType()来注册数据类型。此外,使用Q_INVOKABLE来声明函数,也可以正确调用

注:详情参见Qt invokeMethod 异步调用 - 简书概述 程序中,我们经常会调用函数,如果调用的函数耗时较长,同步调用会造成主程序的堵塞。Qt中提供了一个便捷的函数QMetaObject::invokeMethod,方便我们异...https://www.jianshu.com/p/c77e28a81708

二、事件订阅与发布

        从上面的简介中,我们得知,只要我们获取了对象的指针,我们便可以通过invokeMethod来调用该对象中的槽函数,或者被Q_INVOKABLE申明的成员变量,并且通过后面的传参,来传递数据信息给被调用对象。

        那么我们可以通过一个函数来实现事件的订阅,订阅时传递该类的this指针,以及需要订阅的事件名,再封装一个函数来实现事件的发布,用于传递消息给对应的事件的订阅者。该功能就类似于报社与订报纸的人,不同的报社会出版不同的报纸,不同的人会去订阅不同的报纸,假设有10个人订阅了A报社的报纸,10个人订阅了B报社的报纸,那么当A报社发布了新报纸后,订阅了A报社报纸的人便可以通过邮箱收到A报社发布的报纸来获取A报社记录的新闻,当B报社发布了新报纸后,订阅了B报社的人便可以通过邮箱收到B报社发布的报纸来获取B报社记录的新闻。

1.事件的订阅

bool PSEventController::subscribe(QObject* listener, const QByteArray& eventName)
{
    QWriteLocker locker(&ps_Lock_);
    if (psEvents_pool_.contains(eventName)) {
        if (-1 != psEvents_pool_[eventName].indexOf(listener)) {
            ps_LastError_ = QString("This object is subscribed to this eventName");
            return false;
        }
        psEvents_pool_[eventName].push_back(listener);
        return true;
    } else {
        psEvents_pool_.insert(eventName, { listener });
        return true;
    }
}

        当一个类调用事件订阅后,第一个参数传入该类的的对象指针,第二个参数用于传入订阅的事件名,之后便将订阅者指针保存起来,用于发布者投递事件,然后订阅的事件名作为订阅的事件名以及对应的槽函数名的依据。        

2.事件的发布

bool PSEventController::publish(const QByteArray& eventName, Qt::ConnectionType connectionType,
    QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3,
    QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7,
    QGenericArgument val8, QGenericArgument val9)
{
    QReadLocker locker(&ps_Lock_);
    if (!psEvents_pool_.contains(eventName)) {
        ps_LastError_ = QString("No objects subscribe to this eventName");
        return false;
    }
    auto methodName = methodFormatting(eventName);
    QStringList errors;
    for (auto listener : psEvents_pool_[eventName]) {
        if (!listener)
            continue;
        auto ret = QMetaObject::invokeMethod(listener, methodName, connectionType,
            val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
        if (!ret)
            errors.append(QString("%1:%2").arg(listener->metaObject()->className()).arg(listener->objectName()));
    }
    if (errors.isEmpty())
        return true;
    ps_LastError_ = QString("%1 execution failed:[\n").arg(QString(eventName));
    for (auto& err : errors)
        ps_LastError_ += QString("%1;\n").arg(err);
    ps_LastError_ += "]\n";
    return false;
}

        当一个类通过publish发布事件时,第一个传入的参数为发布的事件名,第二个参数一般填autoConnect让其自动选择,然后如果有发布内容,则在val0~val9中传递需要发布的内容。之后,便会在所有订阅该事件的订阅者中调用该订阅者对应事件名的槽函数,将val0~val9的内容通过订阅者的指针,对应槽函数,通过invokeMethod发送给订阅者。

三、举例

        1.订阅

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

    PSEventController::subscribe(this, "showText");
}

void Widget::on_psEvent_showText(QString msg)
{
    ui->label->setText(msg);
}

        如上代码所示,Widget订阅了showText的事件,并且对应的槽函数为on_psEvent_showText(QString msg),当有发布者发布showText事件并传递参数时,widget会调用槽函数并获取QString类型数据,

        2.发布

    PSEventController::publish("showText", Q_ARG(QString, text));

        发布者通过上诉代码,发布showText事件,并且通过Q_ARG表示传递参数为QString 类型,数据为text。据我使用的情况,容器,结构体,自定义类都可以作为参数传递。

        该代码为gitee上一个作者所写,大佬代码仓库:https://gitee.com/feng_yan_yu/qt-pub-subicon-default.png?t=M4ADhttps://gitee.com/feng_yan_yu/qt-pub-sub


总结

        基于以上事件的发布、订阅,便可以非常方便的在多个容器之间进行数据传递,并且对于同一个事件,可以有多个订阅者,也可以有多个发布者。

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值