信号、槽
参考文献
Signals & Slots | Qt Core 6.7.2
备注:本笔记大量内容可能属于官方文档的翻译或概括,但也包含个人的理解。不太清楚应该归为原创/转载/翻译的哪一类,如QT官方有修改或删除的需求请联系。
综述
-
信号槽机制的目的
-
信号槽机制是用来代替回调技术的,当信号发出时,对应的槽会被调用。它比回调技术略慢但更灵活。
-
信号槽使得类被更好的封装。包含信号的类不需要关心是否有槽会接收信号,从而可以封装起来,反过来也是一样。
-
信号槽机制还进行类型检查,通过编译器确保类型安全。信号和槽的参数必须匹配(但槽可以忽略部分参数,使得槽的参数个数比信号要少)。
-
-
信号槽的使用
-
一个类继承QObject或其子类,就可以使用信号槽。
-
一个信号可以连接多个槽,一个槽可以接受多个信号。
-
槽函数还可以当做普通函数使用。
-
信号
-
信号是public函数,可以在任意处使用/发射(emit),但推荐在声明它的类及其子类中emit。
-
当信号被发出时,连接到它的槽函数会像普通函数一样,被立即按照连接先后顺序依次执行;然后再执行信号发出之后的代码。
-
但当使用queued connections时,顺序相反。会先执行emit之后的代码,然后再执行槽。
-
-
信号的实现由moc 自动生成,不要在cpp文件中实现。
-
参数可以有默认值
槽
-
槽可以看做能与信号连接的普通C++函数。它可以以通常方式被调用和执行。
-
槽函数可以重载(同名),QT会自动选择适合的槽函数。
-
信号槽机制可以一定程度上越过访问权限控制,即信号和槽的关系不受访问权限影响。举个极端的例子,信号位于A类中,槽甚至可以是与A没有关系的B类中的private函数。(但是在使用connect建立连接时,还是需要能同时使用信号和槽)
-
参数可以用默认值
连接
有多种形式,见格式说明
示例和格式说明
-
类、信号、槽的声明
-
使用Q_OBJECT、public/private... slots和signals
-
继承QObject或其子类
-
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
// Note. The Q_OBJECT macro starts a private section.(Q_OBJECT宏指令使用了private:)
// To declare public members, use the 'public:' access modifier.(用public:覆盖)
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;
};
-
槽的定义:与普通函数一样
-
发出信号:
-
使用emit关键字 + 函数调用
-
emit valueChanged(value);
-
void Counter::setValue(int value)
{
if (value != m_value) {
//这里检查不等于是防止不断发出信号导致死循环,比如一个对象的valuechanged与自身的setvalue连接
m_value = value;
emit valueChanged(value);
}
}
-
连接信号槽
-
使用connect,有多种方式
-
需要对象、信号、槽
-
Counter a, b;
QObject::connect(&a, &Counter::valueChanged,
&b, &Counter::setValue);
a.setValue(12); // a.value() == 12, b.value() == 12
b.setValue(48); // a.value() == 12, b.value() == 48
-
连接的多种形式
假设void destroyed(QObject* = nullptr); void objectDestroyed(QObject* obj = nullptr);
-
函数指针
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
这种形式的好处是编译器可以检查和进行隐式类型转换
-
lambda表达式
connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });
当sender或者this提供的上下文被销毁时,将会断开对lambda的连接。所以需要确保信号发出时所有用到的对象都还有效。
-
SIGNAL、SLOT宏 可以是
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*)));
因为槽的参数不能比信号多。使用这种方式时,不会检查参数;如果出错,会产生运行时错误。
-
在以上形式中,都使用this指针提供执行的线程等上下文信息。
-
进阶使用
-
获取发送者的信息
-
QObject::sender() 在被信号激活的槽中使用可以获取发送者的指针,否则返回空指针;返回值可能会因为断开连接或者发送者被销毁而失效
-
或通过lambda形式在可以直接访问发送者的上下文中使用connect
connect(action, &QAction::triggered, engine, [=]() { engine->processAction(action->text()); });
-
-
避免与其他信号槽机制的库关键字冲突
-
cmake
connect(action, &QAction::triggered, engine, [=]() { engine->processAction(action->text()); });
qmake
CONFIG += no_keywords
-
然后把关键字对应替换为Q_SIGNALS/Q_SIGNAL、Q_SLOTS/Q_SLOT和Q_EMIT
-
-
基于QT的库如何避免被QT_NO_KEYWORDS影响
-
使用上面Q_形式的关键字代替signals和slots,并可以在构建库时对预处理器使用QT_NO_SIGNALS_SLOTS_KEYWORDS来保证没有使用信号槽关键字。
-
这样做可以排除信号槽关键字,而不影响其他QT关键字的使用。
-