如何将 Qt 信号/插槽名称存储到数据结构中?

Posted

技术标签:

【中文标题】如何将 Qt 信号/插槽名称存储到数据结构中?【英文标题】:How to store Qt signal/slot names to a data structure? 【发布时间】:2015-08-05 15:04:34 【问题描述】:

我想将信号或槽的名称存储到容器中,以便我可以检索它并与之建立连接。

一种方法是hack the SIGNAL/SLOT macro 并将信号/插槽的名称存储为字符串,然后使用old connect syntax 进行这样的连接

void connect(QObject *dst, QString slot)

    connect(this, SIGNAL(foo(void)), dst, slot.toLocal8Bit().data());
    // void foo(void) is a member signal of "this" and is known 
    // at compile time
    // but "dst" and "slot" are not known until runtime.

但是,这对我来说似乎有点太老套了。

我知道有another syntax for connect 和an object type for signal/slots,这意味着一个不同的并且(可能)不那么老套的解决方案。 (注意这不是new signal/slot syntax in Qt5)

但问题是,QMetaMethod 没有记录在案的构造函数,它似乎不是可复制构造的。

另一个问题,当信号/插槽的名称是动态的时,你如何建立连接?

对于我的编码环境是 Qt4 和 gcc 4.3.3 以及 gnu++98,因此需要 Qt5 和 C++11 的解决方案虽然是受欢迎的,但对我来说用处不大。

【问题讨论】:

您只需要使用旧语法的第一种方式,只需在每个插槽的名称中添加“1”即可。 @Chernobyl 其实更容易做到connect(aQObject, SLOT(aSlotOfThatQObject)); 我有 Qt5,但我想我找到了解决方案。但是,它假定您的插槽具有签名 void mySlot();;有问题吗? @Armaghast 您可以假设签名相同或兼容。 【参考方案1】:

2 个解决方案,我将寻找第 3 个。

用于示例的简单类:

#include <QObject>
#include <QDebug>

class MyClass : public QObject

    Q_OBJECT
public:
    MyClass() : QObject()  

    void fireSignals()
    
        qDebug() << "signal1 :" ;
        emit signal1();
        qDebug() << "signal2 :" ;
        emit signal2();
        qDebug() << "signal3 :" ;
        emit signal3(3);
    

public slots:
    void slot1()  qDebug() << "slot1"; 
    void slot2()  qDebug() << "slot2"; 
    void slot3()  qDebug() << "slot3"; 
    void slot4(int i)  qDebug() << "slot4" << i; 
signals:
    void signal1();
    void signal2();
    void signal3(int);
;

Qt4/C++98:

我没有 Qt4,请告诉我这个解决方案是否有效。

使用 Qt 5.5、mingw 4.9.2、-std=c++98 测试

它依赖于将信号或插槽名称转换为其索引,然后转换为关联的 QMetaMethod。

注意:可以改为存储QMetaMethod 对象。这种方法更安全,因为检查槽是否存在更早,但在创建QMap 之前需要有一个MyClassinstance,而存储槽名称更灵活。

// Get the index of a method, using obj's metaobject
QMetaMethod fetchIndexOfMethod(QObject* obj, const char* name)

    const QMetaObject* meta_object = obj->metaObject();
    QByteArray normalized_name = QMetaObject::normalizedSignature(name);
    int index = meta_object->indexOfMethod(normalized_name.constData());
    Q_ASSERT(index != -1);
    return meta_object->method(index);


// A QObject::connect wrapper
QMetaObject::Connection dynamicConnection(QObject* source,
                                          const char* signal_name,
                                          QObject* dest,
                                          const char* slot_name)

   return QObject::connect(source, fetchIndexOfMethod(source, signal_name),
                           dest, fetchIndexOfMethod(dest, slot_name));


void first_way()

    qDebug() << "\nFirst way:";
    QMap<QString, const char*> my_slots;
    my_slots["id_slot1"] = "slot1()";
    my_slots["id_slot2"] = "slot2()";
    my_slots["id_slot3"] = "slot3()";
    my_slots["id_slot4"] = "slot4(int)"; // slots with different signatures in the same container

    MyClass object;
    dynamicConnection(&object, "signal1()", &object, my_slots.value("id_slot1"));
    dynamicConnection(&object, "signal1()", &object, my_slots.value("id_slot2"));
    dynamicConnection(&object, "signal2()", &object, my_slots.value("id_slot3"));
    dynamicConnection(&object, "signal3(int)", &object, my_slots.value("id_slot4"));

    object.fireSignals();

Qt5/C++14:

使用 Qt 5.5、mingw 4.9.2、-std=c++14 测试

这是使用指向成员函数的指针。 它是类型安全的,因此您不能在同一个容器中拥有具有不同签名的插槽。

template <typename T, typename R, typename ...Args>
struct PointerToMemberHelper

    using type = R (T::*)(Args...);
;

void second_way()

    qDebug() << "\nSecond way:";

    using MPTR_void = PointerToMemberHelper<MyClass, void>::type;
    using MPTR_int = PointerToMemberHelper<MyClass, void, int>::type;

    QMap<QString, MPTR_void> my_slots("id_slot1", MyClass::slot1,
                                       "id_slot2", MyClass::slot2,
                                       "id_slot3", MyClass::slot3);

    MyClass object;
    QObject::connect(&object, MyClass::signal1, &object, my_slots.value("id_slot1"));
    QObject::connect(&object, MyClass::signal1, &object, my_slots.value("id_slot2"));
    QObject::connect(&object, MyClass::signal2, &object, my_slots.value("id_slot3"));

    MPTR_int my_int_slot = MyClass::slot4; // or auto my_int_slot = ...
    QObject::connect(&object, MyClass::signal3, &object, my_int_slot);

    object.fireSignals();

目前正在尝试基于 lambdas 和 QMetaObject::invokeMethod 获得第三种解决方案,但它不会比第二种解决方案做得更多。

【讨论】:

您的第一个解决方案比我基于字符串的方法更复杂,但它演示了如何获取插槽的QMetaMethod 表示,这比QMetaMethod 强类型的字符串更好。此外,基于字符串的方法在连接发生之前无法检测到错误,QMetaMethod 方法可以在查找插槽时检测到错误。 我对模板不是很熟悉。你能在你的PointerToMemberHelper上解释更多吗? 我想我在这里找到了答案 ***.com/questions/10747810/… 。 using 在这种情况下类似于 typedef 确实,使QMetaMethod 更早更安全,尽管基于字符串的方法允许您在没有MyClass 实例的情况下存储槽,并允许一些更强大(也更危险)的绑定,例如构造插槽名称dynamicConnection( [...], QString("slot%1()").arg(an_int))。我将编辑我的答案以反映这一点。

以上是关于如何将 Qt 信号/插槽名称存储到数据结构中?的主要内容,如果未能解决你的问题,请参考以下文章

使用 qt 中的信号/插槽更新 gui [关闭]

Qt 信号和插槽发送结构数组

Qt:如何将不同类的静态信号连接到插槽?

Qt:将信号/绑定参数适应插槽?

如何使用 Qt Creator 将按钮单击信号(“触发”信号)与工具栏中的用户按钮的动作/插槽功能连接起来?

如何将信号和插槽与 qt 中的另一个对象连接 - 已解决