信号与槽机制
- 🌟 信号函数
- 🌟 槽函数
- 🌟 连接函数
- 🌸 QObejct::connect函数剖析
- 🌟 官方文档中给出的定义
- 🌟《Qt 5.9 C++开发指南》中的定义
🌟 信号函数
信号是一种特殊的成员函数,用于在特定事件发生时发出通知。一个类可以定义一个或多个信号,并在适当的时候发射(emit)这些信号。信号通常声明为 signals 关键字的一部分。
- 范例:
MyPushButton是一个自定义按钮控件类,继承于QPushButton。
class MyPushButton : public QPushButton
{
Q_OBJECT
explicit MyPushButton(QWidget *parent = nulltpr);
signals://声明信号关键字
//点击信号,是Qt预定义提供的信号
void clicked();
//发送按钮名称信号,自定义的信号
void sendBtnNameSignal(const QString &btnName);
};
- 发信号需要使用一个关键字:emit
void MyPushButton::MyPushButton(QWidget *parent)
: QPushButton(parent)
{
emit sendBtnNameSignal("btn");//在构造中发出发送按钮名称信号
}
🌟 槽函数
槽是普通的成员函数,用于响应信号。槽函数的特殊之处在于它们可以连接到一个或多个信号,以便在相关的信号被发射时被调用。
- 范例:
MyTextEdit是一个自定义富文本编辑控件类,继承于QTextEdit。
class MyTextEdit: public QTextEdit
{
Q_OBJECT
//slots:是声明槽函数的关键字,public slots:表明这个一个公有成员槽函数
public slots:
void SetBtnNameSlot(const QString &btnName);//设置按钮名称槽函数
};
🌟 连接函数
设置完信号函数和槽函数之后,它们还无法进行信息传递,还需要添加一个connect()函数,连接这两个不同类的成员函数。
- 范例:
MyWidget是一个自定义界面类,继承于QWidge。我在其中定义了一个MyPushButton 类和一个MyTextEdit类。
class MyPushButton;
class MyTextEdit;
class MyWidget: public QWidge
{
Q_OBJECT
explicit MyWidget(QWidge *parent = nullptr){
m_btn = new MyPushButton();
m_text = new MyTextEdit();
//前提:是QObject的子类,上一篇blog已经说过原因。
//connec函数连接信号函数和槽函数
connect(m_btn,&MyPushButton::sendBtnNameSignal,
m_text,&MyTextEdit::SetBtnNameSlot,Qt::UniqueConnection);
}
private :
MyPushButton *m_btn = nullptr;
MyTextEdit *m_text = nullptr;
};
通过设置连接函数,两个不同对象中的成员函数得以进行消息传递。
🌸 QObejct::connect函数剖析
✨函数原型:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
通过传入指向发送者对象的指针和该对象的信号函数的地址以及指向接收者对象的指针和该对象的槽函数地址。
- 通常,可以使用SIGNAL宏和SLOT宏将信号、槽函数包起来,这种写法要求至少写出信号函数和槽函数的传参类型。
- 一个信号可以连接多个槽函数,同样,一个槽函数可以连接多个信号函数。
- connect函数本身是线程安全的,但考虑到可能存在复杂的多线程环境,应当谨慎处理对象生存周期、跨线程连接和多线程并发访问等问题。
- 关于第五个参数Qt::ConnectionType,缺省情况下会使用Qt::AutoConnection.这个参数决定了接收到信号后是立即执行槽函数,还是排队执行槽函数。
ConnectionType Value Description
Qt::AutoConnection 0 (默认值)如果接收器存放在发出信号的线程中,则使用QT :: DirectConnection。否则,使用QT :: QueuedConnection。发射信号时确定连接类型。
Qt::DirectConnection 1 槽函数会在信号发出被响应时立即执行,并且,槽函数会被放在信号线程中执行。
Qt::QueuedConnection 2 当信号发射时,槽函数的调用请求会被放入接收对象所在线程的事件队列中,等待事件循环处理。适用于跨线程通信。
Qt::BlockingQueuedConnection 3 与qt :: QueuedConnection相同,只是信号函数会阻塞直到槽函数返回。如果接收者对象和发送者对象在同一个线程中,则不得使用此连接,否则应用程序将死锁。
Qt::UniqueConnection 0x80 用于确保信号与槽之间的连接是唯一的。如果已经存在相同的信号与槽连接,再次使用 Qt::UniqueConnection 连接时,连接操作将会失败,从而避免建立重复的连接
- 如果在消息传递时使用了自定义的数据类型,需要先将该类型注册到元对象系统中,否则connect会失败。
//注册QVector<AgendaInfo>到元类型中
qRegisterMetaType<QVector<AgendaInfo>>("QVector<AgendaInfo>");
✨函数原型:
[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
- 这个函数原型中,支持将信号发送给一个QObejct的类的成员函数,并且在连接时无需列出函数的传参类型,Qt会为你去做检查的工作。
- 因此,这个函数原型可以支持将匿名函数(lambda)作为连接的槽函数。
QObject::connect(socket, &QTcpSocket::connected, this, [=] () {
socket->write("GET " + page + "\r\n");
}, Qt::AutoConnection);
- Note: Qt::UniqueConnections do not work for lambdas, non-member functions and functors; they only apply to connecting to member functions.[译文]:Qt::UniqueConnections 这种连接方式不支持用于连接槽函数为匿名函数、非成员函数,connect函数会报错。
🌟 官方文档中给出的定义
Signals and slots are used for communication between objects. The signals and slots mechanism is a central feature of Qt and probably the part that differs most from the features provided by other frameworks. Signals and slots are made possible by Qt's meta-object system. [译文]:信号和插槽用于对象之间的通信。信号和插槽机制是QT的主要特征,可能是与其他框架提供的功能不同的部分。信号和插槽通过QT的元对象系统使其成为可能。🌟《Qt 5.9 C++开发指南》中的定义
Qt 使用信号与槽的机制实现对象间通信,它隐藏了复杂的底层实现,完成信号与槽的关联后,发射信号时并不需要知道Qt是如何找到槽函数的。Qt信号与槽机制与 Delphi 和 C++ Builder 的“事件一一响应” 较类似,但是更加灵活。
某些开发架构使用回调函数(callback)实现对象间通信。与回调函数相比,信号与槽的执行速度稍微慢 ,因为需要查找连接的对象和槽函数 ,但是这种区别在应用程序运行时是感觉不到的,而其提供的灵活性却比回调函数强很多。