如何将 Qt signal/slot 名称存储到数据结构中?
How to store Qt signal/slot names to a data structure?
我想将信号或槽的名称存储到容器中,以便我可以检索它并与之建立连接。
一种方法是hack the SIGNAL/SLOT macro and store the signal/slot's name as strings, and then use the the 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 and there is also an object type for signal/slots, which implies a different and (potentially) less hacky solution. (note that this is not the new signal/slot syntax in Qt5)
但问题是,QMetaMethod
没有记录在案的构造函数,它似乎是可复制构造的。
还有一个问题,当signal/slots的名字是动态的时候,你如何建立联系?
我的编码环境是 Qt4 和 gcc 4.3.3 gnu++98
,所以需要 Qt5 和 C++11 的解决方案虽然受欢迎,但对我来说用处不大。
2 个解决方案,我正在寻找第 3 个。
用于示例的简单 class:
#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
之前需要一个 MyClass
实例,同时存储插槽名称更灵活。
// 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();
}
目前正在尝试基于 lambda 和 QMetaObject::invokeMethod
使第三个解决方案起作用 - 但它不会比第二个解决方案做得更多。
我想将信号或槽的名称存储到容器中,以便我可以检索它并与之建立连接。
一种方法是hack the SIGNAL/SLOT macro and store the signal/slot's name as strings, and then use the the 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 and there is also an object type for signal/slots, which implies a different and (potentially) less hacky solution. (note that this is not the new signal/slot syntax in Qt5)
但问题是,QMetaMethod
没有记录在案的构造函数,它似乎是可复制构造的。
还有一个问题,当signal/slots的名字是动态的时候,你如何建立联系?
我的编码环境是 Qt4 和 gcc 4.3.3 gnu++98
,所以需要 Qt5 和 C++11 的解决方案虽然受欢迎,但对我来说用处不大。
2 个解决方案,我正在寻找第 3 个。
用于示例的简单 class:
#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
之前需要一个 MyClass
实例,同时存储插槽名称更灵活。
// 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();
}
目前正在尝试基于 lambda 和 QMetaObject::invokeMethod
使第三个解决方案起作用 - 但它不会比第二个解决方案做得更多。