我的信号/插槽连接不起作用

时间:2014-10-17 09:37:49

标签: qt signals-slots qt-signals qt-slot qt-connection

我反复看到有人没有调用插槽的问题。我想收集一些最常见的原因。所以也许我可以帮助别人并避免许多多余的问题。

信号/插槽连接无法正常工作的原因是什么?如何避免这些问题?

3 个答案:

答案 0 :(得分:32)

有一些规则可以简化信号和插槽的使用寿命,并涵盖连接故障的最常见原因。如果我忘记了什么,请告诉我。

1)检查调试控制台输出:

发生执行错误时,调试输出可以显示原因。

2)使用信号和插槽的完整签名:

而不是

connect(that, SIGNAL(mySignal), this, SLOT(mySlot));

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

并检查您的拼写和大小写。

3)使用现有的重载:

仔细检查您是否正在使用所需的信号和插槽过载,以及您实际使用的过载是否存在。

4)您的信号和插槽必须兼容:

这尤其意味着参数必须属于同一类型(可以容忍引用)并且具有相同的顺序。

编译时语法也需要相同数量的参数。旧的运行时语法允许将信号连接到具有较少参数的插槽。

5)始终检查连接方法的返回值(程序员从不忽略返回值):

而不是

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

总是使用像

这样的东西
bool success = connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));
Q_ASSERT(success);

或者,如果您喜欢抛出异常或实现完整的错误处理。您也可以使用这样的宏:

#ifndef QT_NO_DEBUG
#define CHECK_TRUE(instruction) Q_ASSERT(instruction)
#else
#define CHECK_TRUE(instruction) (instruction)
#endif 

CHECK_TRUE(connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int))));

6)您需要一个排队连接的事件循环:

即。当你连接不同线程拥有的两个对象的信号/槽(所谓的排队连接)时,你需要在槽的线程中调用exec();

事件循环也需要实际提供。只要插槽的线程卡在某种繁忙的循环中,就不会执行排队连接!

7)您需要为排队连接注册自定义类型:

因此,在排队连接中使用自定义类型时,必须为此目的注册它们。

首先使用以下宏声明类型:

Q_DECLARE_METATYPE(MyType)

然后使用以下调用之一:

qRegisterMetaType<MyTypedefType>("MyTypedefType"); // For typedef defined types
qRegisterMetaType<MyType>(); // For other types

8)优先于旧的运行时检查语法的新编译时语法:

而不是

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));

使用此语法

connect(that, &ThatObject::mySignal, this, &ThisObject::mySlot));

在编译期间检查信号和插槽,甚至不需要将目标作为实际插槽。

如果信号过载,请使用以下语法:

connect(that, static_cast<void (ThatObject::*)(int)> &ThatObject::mySignal), this, &ThisObject::mySlot); // <Qt5.7
connect(that, qOverload<int>::of(&ThatObject::mySignal), this, &ThisObject::mySlot); // >=Qt5.7 & C++11
connect(that, qOverload<int>(&ThatObject::mySignal), this, &ThisObject::mySlot); // >=Qt5.7 & C++14

也不要混合const / non-const信号/插槽用于该语法(通常信号和插槽将是非const)。

9)您的课程需要Q_OBJECT宏:

在使用“信号”和“插槽”规范的类中,您需要添加一个Q_OBJECT宏,如下所示:

class SomeClass
{
   Q_OBJECT

signals:
   void MySignal(int x);
};

class SomeMoreClass
{
   Q_OBJECT

public slots:
   void MySlot(int x);
};

此宏将必要的元信息添加到类中。

10)你的物品必须活着:

只要发送方对象或接收方对象被销毁,Qt就会自动丢弃该连接。

如果未发出信号:发件人对象是否仍然存在? 如果未调用插槽:接收器对象是否仍然存在?

要检查两个对象的生命周期,请在构造函数/析构函数中使用调试器断点或某些qDebug()输出。

11)它仍然不起作用:

对你的连接做一个非常快速和脏的检查,你自己使用一些伪参数发出信号,看看它是否被调用:

connect(that, SIGNAL(mySignal(int)), this, SLOT(mySlot(int)));
emit that->mySignal(0); // Ugly, don't forget to remove it immediately

最后,当然可能不会发出信号。如果您遵循上述规则,那么您的程序逻辑可能出现问题。阅读文档。使用调试器。如果现在有其他方式,请在stackoverflow上询问。

答案 1 :(得分:3)

在我的实践中,我遇到了在接收信号的对象中错误地覆盖eventFilter的情况。一些新手程序员忘记在函数末尾返回“ false”。因此,不允许MetaCall事件传递给接收对象。在这种情况下,接收对象不会处理信号。

答案 2 :(得分:0)

简短答案

您(几乎)不必再为此担心了。始终使用QMetaMethod / Pointer作为connect的成员原型,因为如果信号和插槽不兼容,它将在编译时失败。

connect(sourceObject, &SourceClass::signal, destObject, &DestClass::slot);

仅当sourceObjectdestObject为空(这是预期的)时,该原型才会在运行时失败。但是参数不兼容会在编译过程中出现

只有在极少数情况下才需要使用较旧的SIGNAL / SLOT基于文字的语法,因此这应该是您的最后选择。

兼容性

如果满足以下条件,则签名是兼容的:

  • 您正在将信号连接到插槽或信号
  • 目标信号/插槽与源信号具有相同的数量自变量
  • 如果使用了源信号的参数,则可以将其隐式转换为目标信号/插槽中的相应参数(按顺序匹配)
例子
  • 确定-signalA(int, std::string) => signalC(int, std::string)
    • 请注意,我们正在连接信号
  • 确定-signalA(int, std::string) => slotB(int, std::string)
  • 确定-signalA(int, std::string) => slotB(int)
    • 字符串参数被忽略
  • 确定-signalA(int, std::string) => slotB()
    • 所有参数均被忽略
  • 确定-signalA(int, const char*) => slotB(int, QString)
    • QString(const char*)隐式转换
  • 失败-signalA(int, std::string) => slotB(std::string)
    • int不能隐式转换为std::string
  • 失败-signalA(int, std::string) => slotB(std::string, int)
    • 顺序错误
  • 失败-signalA(int, std::string) => slotB(int, std::string, int)
    • 右侧的参数过多