Qt 是否可以定义一个函数指针来代替信号?
Qt is it possible to define a function pointer in place of a signal?
我有很多信号,它们都具有相同的参数但执行不同的功能。
所有信号的连接和断开代码都相同,信号连接到的插槽处理程序也是如此。
而不是一遍又一遍地编写这段代码。我想使用函数指针或类似的东西来分配给信号,然后有一个执行连接或断开连接的公共代码块。
以下代码只是为了说明我所描述的内容,它无效且无法编译。
void (*pfnSignal)(quint8, QString);
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = signalA;
break;
case SIGNAL_B:
pfnSignal = signalB;
break;
default:
pfnSignal = NULL;
}
if ( pfnSignal != NULL ) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
在 Qt5 中,这很容易完成,因为它允许使用 new pointer to member function syntax.
进行连接
// Using decltype to avoid figuring out the ugly pointer-to-member-function syntax.
// Assumes all signals have the same arguments.
decltype<&ThatClass::someSignal> pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = &ThatClass::signalA;
break;
case SIGNAL_B:
pfnSignal = &ThatClass::signalB;
break;
}
if (pfnSignal) {
connect(pobjRef, pfnSignal, this, &ThisClass::handler);
}
但实际上,这对于 Qt4 甚至是可能的,因为 SIGNAL
宏的类型是 const char*
.
const char *pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = SIGNAL(signalA(quint8, QString));
break;
case SIGNAL_B:
pfnSignal = SIGNAL(signalB(quint8, QString));
break;
}
if (pfnSignal) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
实际上,Thomas McGuire 比我快。 (该死的。)不过,我想添加这个答案是因为:
提供了完整的示例。
它使用仿函数而不是 object/member 信号处理程序的函数指针。
因此,它可能是 Thomas McGuire 的答案的补充。
在 Qt 5 之前,信号由 char*
描述,这应该很容易处理。因此,我假设您的问题是关于自 Qt 5 以来的新 API。
如果您使用正确的方法指针类型,这应该也能正常工作。我为 QPushButton
and QCheckBox
for demonstration because both are derived from QAbstractButton
这样做,它又具有两个具有相同签名的信号。恕我直言,信号的相等签名对于您的解决方案是强制性的。
#include <QtWidgets>
enum SigType { None, Click, Toggle };
template <typename FUNCTOR>
void installSignalHandler(
QAbstractButton *pQBtn,
SigType sigType,
FUNCTOR sigSlot)
{
void (QAbstractButton::*pSignal)(bool) = nullptr;
switch (sigType) {
case Click: pSignal = &QAbstractButton::clicked; break;
case Toggle: pSignal = &QAbstractButton::toggled; break;
}
if (pSignal) QObject::connect(pQBtn, pSignal, sigSlot);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
QVBoxLayout qVBox(&qWin);
QPushButton qBtn1("Button 1 -> Click");
qVBox.addWidget(&qBtn1);
QPushButton qBtn2("Button 2 -> Toggle");
qVBox.addWidget(&qBtn2);
QPushButton qBtn3("Button 3 -> None");
qVBox.addWidget(&qBtn3);
QCheckBox qTgl1("Toggle 1 -> Click");
qVBox.addWidget(&qTgl1);
QCheckBox qTgl2("Toggle 2 -> Toggle");
qVBox.addWidget(&qTgl2);
QCheckBox qTgl3("Toggle 3 -> None");
qVBox.addWidget(&qTgl3);
qWin.show();
// install signal handlers
installSignalHandler(&qBtn1, Click,
[](bool) { qDebug() << "Button 1 received clicked."; });
installSignalHandler(&qBtn2, Toggle,
[](bool) { qDebug() << "Button 2 received toggled."; });
installSignalHandler(&qBtn3, None, // will be actually never called
[](bool) { qDebug() << "Button 3 received none."; });
installSignalHandler(&qTgl1, Click,
[](bool) { qDebug() << "CheckBox 1 received clicked."; });
installSignalHandler(&qTgl2, Toggle,
[](bool) { qDebug() << "CheckBox 2 received toggled."; });
installSignalHandler(&qTgl2, None, // will be actually never called
[](bool) { qDebug() << "CheckBox 3 received none."; });
// run-time loop
return app.exec();
}
在 Windows 10(64 位)上使用 VisualStudio 和 Qt 5.6 进行编译和测试:
C++11 允许您编写非常简洁的 Qt 代码。
利用 range-based for loops 迭代指针。这些可以是指向小部件的指针、指向方法的指针等:
for (auto signal : {&Class::signal1, &Class:signal2})
QObject::connect(sender, signal, receiver, slot);
利用 lambda expressions 捕获常量参数值:
auto const cMySlot = [&](void (Sender::*signal)(int)){
QObject::connect(sender, signal, receiver, slot);
然后:
for (auto signal : {&Class::signal1, &Class:signal2}) cMySlot(signal);
完整示例:
// https://github.com/KubaO/Whosebugn/tree/master/questions/signals-simpler-43631464
#include <QtWidgets>
#include <initializer_list>
class Receiver : public QLabel {
Q_OBJECT
public:
Receiver(QWidget * parent = {}) : QLabel{parent} {}
Q_SLOT void intSlot(int val) {
setText(QStringLiteral("int = %1").arg(val));
}
};
class Sender : public QWidget {
Q_OBJECT
QFormLayout m_layout{this};
QPushButton btn1{"Send 1"}, btn2{"Send 5"}, btn3{"Send 10"};
public:
Sender(QWidget * parent = {}) : QWidget{parent} {
m_layout.setMargin(1);
for (auto w : {&btn1, &btn2, &btn3}) m_layout.addWidget(w);
auto const clicked = &QPushButton::clicked;
connect(&btn1, clicked, this, [this]{ emit signal1(1); });
connect(&btn2, clicked, this, [this]{ emit signal2(5); });
connect(&btn3, clicked, this, [this]{ emit signal3(10); });
}
Q_SIGNAL void signal1(int);
Q_SIGNAL void signal2(int);
Q_SIGNAL void signal3(int);
};
using Widgets = std::initializer_list<QWidget*>;
int main(int argc, char **argv)
{
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
Sender sender;
Receiver receiver;
for (auto w : Widgets{&sender, &receiver}) layout.addWidget(w);
// Factor out connection
auto const cIntSlot = [&](void (Sender::*signal)(int)){
QObject::connect(&sender, signal, &receiver, &Receiver::intSlot);
};
// Factor out connection on a list
for (auto signal : {&Sender::signal1, &Sender::signal2, &Sender::signal3})
cIntSlot(signal);
win.show();
return app.exec();
}
#include "main.moc"
我有很多信号,它们都具有相同的参数但执行不同的功能。
所有信号的连接和断开代码都相同,信号连接到的插槽处理程序也是如此。
而不是一遍又一遍地编写这段代码。我想使用函数指针或类似的东西来分配给信号,然后有一个执行连接或断开连接的公共代码块。
以下代码只是为了说明我所描述的内容,它无效且无法编译。
void (*pfnSignal)(quint8, QString);
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = signalA;
break;
case SIGNAL_B:
pfnSignal = signalB;
break;
default:
pfnSignal = NULL;
}
if ( pfnSignal != NULL ) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
在 Qt5 中,这很容易完成,因为它允许使用 new pointer to member function syntax.
进行连接// Using decltype to avoid figuring out the ugly pointer-to-member-function syntax.
// Assumes all signals have the same arguments.
decltype<&ThatClass::someSignal> pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = &ThatClass::signalA;
break;
case SIGNAL_B:
pfnSignal = &ThatClass::signalB;
break;
}
if (pfnSignal) {
connect(pobjRef, pfnSignal, this, &ThisClass::handler);
}
但实际上,这对于 Qt4 甚至是可能的,因为 SIGNAL
宏的类型是 const char*
.
const char *pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = SIGNAL(signalA(quint8, QString));
break;
case SIGNAL_B:
pfnSignal = SIGNAL(signalB(quint8, QString));
break;
}
if (pfnSignal) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
实际上,Thomas McGuire 比我快。 (该死的。)不过,我想添加这个答案是因为:
提供了完整的示例。
它使用仿函数而不是 object/member 信号处理程序的函数指针。
因此,它可能是 Thomas McGuire 的答案的补充。
在 Qt 5 之前,信号由 char*
描述,这应该很容易处理。因此,我假设您的问题是关于自 Qt 5 以来的新 API。
如果您使用正确的方法指针类型,这应该也能正常工作。我为 QPushButton
and QCheckBox
for demonstration because both are derived from QAbstractButton
这样做,它又具有两个具有相同签名的信号。恕我直言,信号的相等签名对于您的解决方案是强制性的。
#include <QtWidgets>
enum SigType { None, Click, Toggle };
template <typename FUNCTOR>
void installSignalHandler(
QAbstractButton *pQBtn,
SigType sigType,
FUNCTOR sigSlot)
{
void (QAbstractButton::*pSignal)(bool) = nullptr;
switch (sigType) {
case Click: pSignal = &QAbstractButton::clicked; break;
case Toggle: pSignal = &QAbstractButton::toggled; break;
}
if (pSignal) QObject::connect(pQBtn, pSignal, sigSlot);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
QVBoxLayout qVBox(&qWin);
QPushButton qBtn1("Button 1 -> Click");
qVBox.addWidget(&qBtn1);
QPushButton qBtn2("Button 2 -> Toggle");
qVBox.addWidget(&qBtn2);
QPushButton qBtn3("Button 3 -> None");
qVBox.addWidget(&qBtn3);
QCheckBox qTgl1("Toggle 1 -> Click");
qVBox.addWidget(&qTgl1);
QCheckBox qTgl2("Toggle 2 -> Toggle");
qVBox.addWidget(&qTgl2);
QCheckBox qTgl3("Toggle 3 -> None");
qVBox.addWidget(&qTgl3);
qWin.show();
// install signal handlers
installSignalHandler(&qBtn1, Click,
[](bool) { qDebug() << "Button 1 received clicked."; });
installSignalHandler(&qBtn2, Toggle,
[](bool) { qDebug() << "Button 2 received toggled."; });
installSignalHandler(&qBtn3, None, // will be actually never called
[](bool) { qDebug() << "Button 3 received none."; });
installSignalHandler(&qTgl1, Click,
[](bool) { qDebug() << "CheckBox 1 received clicked."; });
installSignalHandler(&qTgl2, Toggle,
[](bool) { qDebug() << "CheckBox 2 received toggled."; });
installSignalHandler(&qTgl2, None, // will be actually never called
[](bool) { qDebug() << "CheckBox 3 received none."; });
// run-time loop
return app.exec();
}
在 Windows 10(64 位)上使用 VisualStudio 和 Qt 5.6 进行编译和测试:
C++11 允许您编写非常简洁的 Qt 代码。
利用 range-based for loops 迭代指针。这些可以是指向小部件的指针、指向方法的指针等:
for (auto signal : {&Class::signal1, &Class:signal2}) QObject::connect(sender, signal, receiver, slot);
利用 lambda expressions 捕获常量参数值:
auto const cMySlot = [&](void (Sender::*signal)(int)){ QObject::connect(sender, signal, receiver, slot);
然后:
for (auto signal : {&Class::signal1, &Class:signal2}) cMySlot(signal);
完整示例:
// https://github.com/KubaO/Whosebugn/tree/master/questions/signals-simpler-43631464
#include <QtWidgets>
#include <initializer_list>
class Receiver : public QLabel {
Q_OBJECT
public:
Receiver(QWidget * parent = {}) : QLabel{parent} {}
Q_SLOT void intSlot(int val) {
setText(QStringLiteral("int = %1").arg(val));
}
};
class Sender : public QWidget {
Q_OBJECT
QFormLayout m_layout{this};
QPushButton btn1{"Send 1"}, btn2{"Send 5"}, btn3{"Send 10"};
public:
Sender(QWidget * parent = {}) : QWidget{parent} {
m_layout.setMargin(1);
for (auto w : {&btn1, &btn2, &btn3}) m_layout.addWidget(w);
auto const clicked = &QPushButton::clicked;
connect(&btn1, clicked, this, [this]{ emit signal1(1); });
connect(&btn2, clicked, this, [this]{ emit signal2(5); });
connect(&btn3, clicked, this, [this]{ emit signal3(10); });
}
Q_SIGNAL void signal1(int);
Q_SIGNAL void signal2(int);
Q_SIGNAL void signal3(int);
};
using Widgets = std::initializer_list<QWidget*>;
int main(int argc, char **argv)
{
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
Sender sender;
Receiver receiver;
for (auto w : Widgets{&sender, &receiver}) layout.addWidget(w);
// Factor out connection
auto const cIntSlot = [&](void (Sender::*signal)(int)){
QObject::connect(&sender, signal, &receiver, &Receiver::intSlot);
};
// Factor out connection on a list
for (auto signal : {&Sender::signal1, &Sender::signal2, &Sender::signal3})
cIntSlot(signal);
win.show();
return app.exec();
}
#include "main.moc"