深入Qt–信号槽机制
信号槽简介
信号-槽是Qt框架中最核心的机制,也是每个Qt开发者必须掌握的技能。
信号-槽的使用方法,是在普通的函数声明之前,加上signal、slot标记,然后通过connect函数把信号与槽 连接起来。
信号-槽要分成两种来看待,一种是同一个线程内的信号-槽,另一种是跨线程的信号-槽。
所谓信号槽,实际就是观察者模式 (发布 - 订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
信号槽规则:只有 QObject 及其派生类才能使用信号和槽机制,且在类之中还需要使用 Q_OBJECT 宏。
信号创建规则:
信号使用 signals 关键字声明,在其后面有一个冒号“:”,在其前面不能有 public、private、protected 访问控制符,信号默认是 public 的。
信号只需像函数那样声明即可,其中可以有参数,参数的主要作用是用于和槽的通信,这就像普通函数的参数传递规则一样。信号虽然像函数,但是对他的调用方式不一样,信号需要使用 emit 关键字发射。
信号只需声明,不能对其进行定义,信号是由 moc 自动生成的。
信号的返回值只能是 void 类型的。
槽创建规则:
声明槽需要使用 slots 关键字,在其后面有一个冒号“:”,且槽需使用 public、private、protected 访问控制符之一。
槽就是一个普通的函数,可以像使用普通函数一样进行使用,槽与普通函数的主要区别是,槽可以与信号关联。
发射信号规则:
发射信号需要使用 emit 关键字,注意,在 emit 后面不需要冒号。
emit 发射的信号使用的语法与调用普通函数相同,比如有一个信号为 void f(int),则发送的语法为:emit f(3);
当信号被发射时,与其相关联的槽函数会被调用(注意:信号和槽需要使用QObject::connect 函数进行关联之后,发射信号后才会调用相关联的槽函数)。
因为信号位于类之中,因此发射信号的位置需要位于该类的成员函数中或该类能见到信号的标识符的位置。
信号和槽的关系:
槽的参数的类型需要与信号参数的类型相对应,
槽的参数不能多余信号的参数,因为若槽的参数更多,则多余的参数不能接收到信号传递过来的值,若在槽中使用了这些多余的无值的参数,就会产生错误。
若信号的参数多余槽的参数,则多余的参数将被忽略。
一个信号可以与多个槽关联,多个信号也可以与同一个槽关联,信号也可以关联到另一个信号上。
若一个信号关联到多个槽时,则发射信号时,槽函数按照关联的顺序依次执行。
若信号连接到另一个信号,则当第一个信号发射时,会立即发射第二个信号。
信号槽连接
要把信号成功连接到槽,它们的参数必须具有相同的顺序和相同的类型,或者允许信号的参数比槽多,槽会自动忽略掉多出来的参数而进行调用。
信号和槽使用 QObject 类中的成员函数 connect 进行关联,该函数有多个重载版本。
第一种形式
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method,
Qt::ConnectionType type = Qt::AutoConnection)
各参数意义如下
sender:表示需要发射信号的对象。
signal:表示需要发射的信号,该参数必须使用 SIGNAL()宏。在5.x以上支持lamda。
receiver:表示接收信号的对象。
method:表示与信号相关联的槽函数,这个参数也可以是信号,从而实现信号与信号的关联。该参数若是槽,需使用 SLOT()宏,若是信号需使用 SIGNAL 宏。
返回值的类型为 QMetaObject::Connection,如果成功将信号连接到槽,则返回连接的句柄,否则,连接句柄无效,可通过将句柄转换为 bool 来检查该句柄是否有效。该返回值可用于 QObject::disconnect()函数的参数,以断开该信号和槽的关联。
type:用于指明信号和槽的关联方式,它决定了信号是立即传送到一个槽还是在稍后时间排队等待传送。关联方式使用枚举 Qt::ConnectionType 进行描述,下表为其取值及意义:
-
Qt::AutoConnection(自动关联,默认值)。若接收者驻留在发射信号的线程中(即信号和槽在同一线程中),则使用 Qt :: DirectConnection,否则,使用 Qt :: QueuedConnection。当信号发射时确定使用哪种关联类型。
-
Qt::DirectConnection 直接关联。当信号发射后,立即调用槽。在槽执行完之后,才会执行发射信号之后的代码(即 emit 关键字之后的代码)。该槽在信号线程中执行。
-
Qt::QueuedConnection 队列关联。当控制权返回到接收者线程的事件循环后,槽才会被调用,也就是说 emit 关键字后面的代码将立即执行,槽将在稍后执行,该槽在接收者的线程中执行。
-
Qt::BlockingQueuedConnection 阻塞队列关联。和 Qt :: QueuedConnection 一样,只是信号线程会一直阻塞,直到槽返回。如果接收者驻留在信号线程中,则不能使用此连接,否则应用程序将会死锁。
-
Qt::UniqueConnection 唯一关联。这是一个标志,可使用按位或与上述任何连接类型组合。当设置 Qt :: UniqueConnection 时,则只有在不重复的情况下才会进行连接,如果已经存在重复连接(即,相同的信号指向同一对象上的完全相同的槽),则连接将失败,此时将返回无效的 QMetaObject::Connection。
第二种形式
QMetaObject::Connection connect( const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) const。
第三种形式
template
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)。
第四种形式
template<typename PointerToMemberFunction , typename Functor>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
该函数的第三个参数支持仿函数、全局函数、静态函数、Lambda 表达式,但是不能是类的非静态成员函数。
第五种形式
static QMetaObject :: Connection QObject :: connect(const QObject * sender, const QMetaMethod&signal,const QObject * receiver, const QMetaMethod& method,Qt :: ConnectionType type = Qt :: AutoConnection)。
信号槽断连
信号和槽使用 QObject 类中的成员函数 disconnect 函数断开其关联,该函数有多个重载版本。
方式1
static bool QObject::disconnect( const QObject *sender, const char *signal, const QObject *receiver, const char *method)
signal、method都是字符串,需要使用 SIGNAL 和 SOLT 宏。
方式2
static bool QObject::disconnect ( const QMetaObject::Connection &connection)
该函数断开使用 connect 函数返回的信号和槽的关联,若操作失败则反回 false。
方式3
static bool QObject::disconnect(const QObject sender, PointerToMemberFunction signal, const QObjectreceiver, PointerToMemberFunction method)
signal、method是指针类型,对应于连接的第三种形式。
该函数不能断开信号连接到一般函数或 Lambda 表达式之间的关联,此时需要使用形式 2 来断开这种关联。
方式4
static bool QObject::disconnect( const QObject sender, const QMetaMethod &signal, const QObjectreceiver, const QMetaMethod &method)
方式5
bool QObject::disconnect(const char *signal = Q_NULLPTR, const QObject *receiver = Q_NULLPTR, const char *method = Q_NULLPTR) const
方式6
bool QObject::disconnect(const QObject *receiver, const char *method = Q_NULLPTR) const。
信号槽的局限性
信号和槽机制尽量少用需要高效实时性高的地方。
信号和槽机制和普通函数的调用相同,如果使用不当的话,在程式执行时也有可能产生死循环。
如果一个信号和多个槽相联系的话,那么,当这个信号被发射时,和之相关的槽被激活的顺序将是随机的。
宏定义不能用在signal和slot的参数中。
构造函数不能用在signals或slots声明区域内。
函数指针不能作为信号或槽的参数。
信号和槽不能有缺省参数。
信号和槽不能携带模板类参数
嵌套的类不能位于信号或槽区域内,也不能有信号或槽。
友元声明不能位于信号或槽声明区内。