【翻译】信号和槽

信号和槽用于对象之间的通信。信号和槽机制是Qt的一个核心特性,是Qt与其他框架提供的特性最为不同的部分。Qt的元对象系统使信号和槽成为可能

一、介绍

在GUI编程中,当更改一个小部件时,我们通常希望通知另一个小部件。更一般地说,我们希望任何类型的对象都能够相互通信。例如,如果用户单击“关闭”按钮,我们可能希望调用窗口的close()函数。

其他工具包使用回调实现这种通信。回调是指向函数的指针,因此如果希望处理函数通知您某个事件,请将指向另一个函数(回调)的指针传递给处理函数。处理函数然后在适当的时候调用回调。虽然使用此方法的成功框架确实存在,但是回调可能是非直观的,并且在确保回调参数的类型正确性方面可能会遇到问题

在Qt中,有一种回调技术的替代方法:使用信号和槽。当特定事件发生时,会发出一个信号。槽是响应特定信号而调用的函数。Qt的小部件有许多预定义的槽,但通常的做法是对小部件进行子类化并添加自己的槽,以便处理感兴趣的信号。

1、信号和插槽机制是类型安全的:编译器可以帮助我们在使用基于函数指针的语法时检测类型不匹配;基于字符串的信号和插槽语法将在运行时检测类型不匹配。

2、信号和插槽是松散耦合的:发出信号的类既不知道也不关心哪些槽接收信号。Qt的信号槽机制确保如果将信号连接到槽,那么将在正确的时间使用信号参数调用该槽。信号和槽可以接受任意数量的任何类型的参数。它们是完全类型安全的。

3、从QObject或其子类继承的所有类都可以包含信号和槽。

4、槽可以用来接收信号,但它们也是正常的成员函数。就像一个对象不知道是否有任何东西接收到它的信号一样,槽也不知道是否有任何信号连接到它

5、可以将任意多个信号连接到单个槽,也可以将信号连接到任意多个槽。甚至可以将一个信号直接连接到另一个信号。(这将在第一个信号发出时立即发出第二个信号。)

信号和槽共同构成了强大的Qt组件编程机制。

二、信号

1、信号是公共访问函数,可以从任何地方发出,但建议仅从定义信号及其子类的类发出信号

2、当信号发出时,与之相连的槽通常会立即执行,就像普通的函数调用一样。发生这种情况时,信号和槽机制完全独立于任何GUI事件循环。一旦所有槽都返回,将执行emit语句后面的代码。当使用排队连接时,情况略有不同;在这种情况下,emit关键字后面的代码将立即继续,稍后将执行插槽。

3、如果多个槽连接到一个信号,则在发出信号时,这些槽将按照它们连接的顺序依次执行。

4、信号由moc自动生成,不得在.cpp文件中实现。它们不能有返回类型(即使用void)。

例:一个信号及moc中实现的代码:

signals:
    void signalTest(double a);

// SIGNAL 0
void Widget::signalTest(double _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

三、槽

1、当一个连接到它的信号被发射时,一个槽被调用。槽是普通的C++函数,可以正常调用,它们唯一的特点是可以连接到它们。

2、由于槽是普通成员函数,所以在直接调用时,它们遵循正常的C++规则。但是,作为槽,任何组件都可以通过信号槽连接调用它们,而不管其访问级别如何。这意味着从任意对象发出的信号可以导致在不相关类的实例中调用私有槽(基于字符串的信号槽才会如此)

3、可以将插槽定义为virtual的,这在实践中非常有用。

4、与回调相比,信号和槽稍微慢了一点,因为它们提供了更大的灵活性,尽管对于实际应用程序来说差异不大。一般来说,发射连接到某些槽的信号比直接调用非虚函数调用的接收器慢大约10倍。这是定位连接对象安全地迭代所有连接(即检查后续接收对象在发射过程中没有被破坏)以及以通用方式整理任何参数所需的开销。虽然十个非虚拟函数调用听起来可能很多,但它的开销要比任何new或delete操作小得多。一旦执行了一个字符串、向量或列表操作,而该操作在幕后需要new或delete,那么信号和插槽开销只占整个函数调用开销的一小部分。信号槽机制的简单性和灵活性非常值得付出代价,用户甚至不会注意到这一点。

四、具有默认参数的信号和槽

信号和槽可以包含参数,并且参数可以具有默认值。如QObject::destroyed():

  void destroyed(QObject* = 0);

当QObject被删除时,它会发出这个QObject::destroyed()信号。我们想要捕捉这个信号,不管在哪里,我们可能有一个悬空引用到被删除的QObject,所以我们可以清理它。合适的槽声明可能是:

  void objectDestroyed(QObject* obj = 0);

以下连接是可以的:

  connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
  connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed())); //槽的参数个数少于信号参数个数
  connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));//省略默认参数

此连接不行:

   connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*))); //错误,槽的参数个数不能多于信号参数个数 

五、信号连接类型

  • Qt::AutoConnection:(默认)如果接收者与发射者位于相同的的线程中,则使用Qt::DirectConnection。否则,将使用Qt::QueuedConnection。连接类型在发出信号时确定。
  • Qt::DirectConnection:当发出信号时,将立即调用槽。
  • Qt::QueuedConnection:队列连接,非实时调用,添加到事件队列,当执行到相应的事件时调用槽。在接收者所在的线程执行槽。线程是安全的。
  • Qt::BlockingQueuedConnection:与Qt::QueuedConnection相同,只是信令线程阻塞,直到槽返回。如果发送着和接收者位于相同的线程中,则不能使用此连接,否则应用程序将死锁。
  • Qt::UniqueConnection这是一个标志,可以与上述任何一种连接类型结合使用位或操作。设置Qt::UniqueConnection时,如果连接已存在就不再连接了。

猜你喜欢

转载自blog.csdn.net/kenfan1647/article/details/115003497