目录
概述
信号
槽
带有默认参数的信号和槽
高级信号和插槽使用
使用Qt与第三方信号和插槽
基于字符串的连接和基于仿函数的连接的区别
类型检查和隐式类型转换
连接到Lambda表达式
将c++对象连接到QML对象
使用槽位中的默认参数连接参数较少的信号
选择的重载信号和插槽
信号和槽用于对象之间的通信。信号和槽机制是Qt的核心特性,可能也是与其他框架提供的特性最不同的部分。信号和槽是由Qt的元对象系统实现的。
在GUI编程中,当我们更改一个小部件时,我们通常希望通知另一个小部件。更一般地说,我们希望任何类型的对象都能够相互通信。例如,如果用户单击Close按钮,我们可能希望调用窗口的Close()函数。
其他工具包使用回调实现这种通信。回调是一个指向函数的指针,所以如果你想让一个处理函数通知你一些事件,你可以把一个指向另一个函数(回调)的指针传递给处理函数。然后,处理函数在适当的时候调用回调。虽然确实存在使用此方法的成功框架,但回调可能不直观,并且可能在确保回调参数的类型正确性方面存在问题。
在Qt中,我们有一个回调技术的替代方案:我们使用信号和槽。当一个特定的事件发生时,就会发出一个信号。Qt的小部件有许多预定义的信号,但是我们总是可以子类化小部件,向它们添加我们自己的信号。槽是响应特定信号而调用的函数。Qt的小部件有许多预定义的槽,但是通常的做法是子类化小部件并添加您自己的插槽,这样您就可以处理您感兴趣的信号。
信号和槽机制是类型安全的:信号的签名必须与接收槽的签名匹配。(实际上,一个slot的签名可能比它接收到的信号短,因为它可以忽略额外的参数。)由于签名是兼容的,所以当使用基于函数指针的语法时,编译器可以帮助我们检测类型不匹配。基于字符串的SIGNAL和SLOT语法将在运行时检测类型不匹配。信号和槽是松耦合的:发出信号的类既不知道也不关心哪个槽接收到信号。Qt的信号和槽机制确保如果你将一个信号连接到一个槽,槽将在正确的时间用信号的参数调用。信号和槽可以接受任意数量的任何类型的参数。它们是完全类型安全的。
所有从QObject或其子类(例如,QWidget)继承的类都可以包含信号和槽。当对象以其他对象可能感兴趣的方式改变其状态时,就会发出信号。这就是对象所做的所有通信。它不知道也不关心是否有东西在接收它发出的信号。这是真正的信息封装,并确保对象可以用作软件组件。
槽可以用来接收信号,但也是普通的成员函数。就像一个对象不知道是否有任何东西接收到它的信号一样,一个槽也不知道是否有任何信号连接到它上面。这确保了真正独立的组件可以用Qt创建。
您可以将任意数量的信号连接到一个插槽,并且一个信号可以连接到任意数量的插槽。甚至可以将一个信号直接连接到另一个信号。(这将在第一个信号发出时立即发出第二个信号。)
信号和槽一起构成了一个强大的组件编程机制。
当对象的内部状态以某种方式发生变化,对象的客户端或所有者可能会感兴趣时,对象就会发出信号。信号是公共访问函数,可以从任何地方发出,但我们建议只从定义信号及其子类的类发出信号。
当发出信号时,连接到它的槽通常立即执行,就像普通的函数调用一样。当这种情况发生时,信号和槽机制完全独立于任何GUI事件循环。一旦所有槽都返回,emit语句之后的代码就会执行。当使用queued连接时,情况略有不同;在这种情况下,emit关键字后面的代码将立即继续执行,而槽将稍后执行。
如果多个插槽连接到一个信号,则在信号发出时,这些插槽将按照它们连接的顺序依次执行。
信号是由moc自动生成的,不能在.cpp文件中实现。它们永远不能有返回类型(即使用void)。
关于参数的注意事项:我们的经验表明,如果信号和槽不使用特殊类型,它们的可重用性会更好。如果QScrollBar::valueChanged()使用一个特殊类型,比如假设的QScrollBar::Range,那么它只能连接到专门为QScrollBar设计的槽。将不同的输入部件连接在一起是不可能的。
当一个连接到插槽的信号被发出时,就会调用这个插槽。Slots是普通的c++函数,可以正常调用;它们唯一的特殊之处在于信号可以与它们相连。
由于slot是普通的成员函数,因此在直接调用时遵循普通的c++规则。然而,作为插槽,它们可以被任何组件调用,而不管其访问级别如何,都可以通过信号插槽连接调用。这意味着从任意类的实例发出的信号可能导致在不相关类的实例中调用私有槽。
您还可以将插槽定义为虚拟的,我们发现这在实践中非常有用。
与回调相比,信号和槽稍微慢一些,因为它们提供了更大的灵活性,尽管对于实际应用程序来说差异并不大。一般来说,发出连接到某些插槽的信号比直接调用接收器(使用非虚拟函数调用)慢大约10倍。这是定位连接对象、安全地遍历所有连接(即检查后续接收器在发射期间没有被销毁)以及以通用方式编组任何参数所需的开销。虽然10个非虚函数调用听起来很多,但它的开销比任何new或delete操作都要少得多。只要在后台执行需要new或delete的字符串、向量或列表操作,信号和槽开销只占整个函数调用成本的很小一部分。当您在插槽中执行系统调用时也是如此;或者间接调用十多个函数。信号和插槽机制的简单性和灵活性是值得的,您的用户甚至不会注意到这些开销。
请注意,在与基于qt的应用程序一起编译时,定义称为信号或槽的变量的其他库可能会导致编译器警告和错误。要解决这个问题,请#undef有问题的预处理器符号。
一个小例子
一个最小的c++类声明可能是:
class Counter
{
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
void setValue(int value);
private:
int m_value;
};
一个基于qobject的小类可能是这样的:
#include
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
基于qobject的版本具有相同的内部状态,并提供访问状态的公共方法,但除此之外,它还支持使用信号和插槽进行组件编程。这个类可以通过发出一个信号valueChanged()来告诉外界它的状态发生了变化,并且它有一个槽,其他对象可以向它发送信号。
所有包含信号或槽的类必须在声明的顶部提到Q_OBJECT。它们还必须(直接或间接)派生自QObject。
插槽由应用程序程序员实现。下面是Counter::setValue()槽的一种可能实现:
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
emit行从对象发出信号valueCh