内容摘要:文章中主要介绍了Qt中的信号槽, 主要内容包括: 信号槽的本质, 信号槽的关系, 标准信号槽的使用, 自定义信号槽的使用, 信号槽的拓展, Lambda表达式。
信号槽机制是Qt最引以为豪的机制之一,信号槽的本质实际上就是一种观察者模式(发送-订阅)。当某一个事件发生后,例如:有一个按钮组件,当它检测到自己被点击之后,就会发出一个信号(signal),这个信号本身是没有目的性的(类似于广播),但是当有一个对象对这个信号感兴趣时,这个对象就会调用连接(connect)函数,意思是,对象会将自己想要的信号与自己绑定,以此来共同处理这个信号。也就是说,当一个信号产生时,被连接的槽函数会自动回调。
信号就是当用户对窗口或者组件进行了相关操作,然后窗口和组件就会产生特定的事件,这个时候Qt的对应窗口和组件就会发出信号。
从上面的描述中我们可以得出一个结论:事件就是信号!!!比如:
单击、双击
女朋友,“我饿了”,于是我带她去吃饭点击刷新
鼠标的点击与释放
键盘的输入
那么信号是以什么样的形式呈现给使用者的呢?
在Qt中槽函数是一种具有特殊功能的函数,实际上也是普通的成员函数,只不过它们还有一个作用就是对于Qt框架中的信号进行处理。
下面举一个例子进行说明:
女朋友说,“我饿了”,于是我带她去吃饭。上面这个例子就说明了,女朋友发出了一个信号,我接收到了并且做出处理,带她去吃饭。
实例对象 | 角色 | 描述 |
|---|---|---|
女朋友 | 信号发出者 | 信号携带的信息: 我饿了 |
我 | 信号接收者 | 处理女朋友发射的信号: 带她去吃饭 |
注意:Qt中槽函数属于某一个类的实例对象!
其实在Qt中,槽和函数本身并没有任何联系。但是由于某种特性需求我们需要将他们两者联系到一起,而我们这里用到的是QObject类中的connect()函数。
连接信号和槽的connect()函数原型如下, 其中PointerToMemberFunction是一个指向函数地址的指针:
QMetaObject::Connection QObject::connect(
const QObject *sender, PointerToMemberFunction signal,
const QObject *receiver, PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection);
参数:
- sender: 发出信号的对象
- signal: 属于sender对象, 信号是一个函数, 这个参数的类型是函数
指针, 信号函数地址
- receiver: 信号接收者
- method: 属于receiver对象, 当检测到sender发出了signal信号,
receiver对象调用method方法,信号发出之后的处理动作
// 参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:
connect(const QObject *sender, &QObject::signal,
const QObject *receiver, &QObject::method);使用connect()函数进行信号槽连接的注意事项:
那么系统自带的信号和槽函数应该怎么寻找呢?这里我们可以借助帮助文档,例如我们试着看一下 按钮的点击信号(QPushButton):

然后我们输入QPushButton:

然后我们看一下右边文档的目录中有没有signals,但是我们发现好像并没有,此时我们再看一下它的父类继承下来的那些信号:

我们可以看见,QPushButton的父类是继承了信号的:

掌握了标准信号、标准信号槽的使用以及connect()函数的作用之后,我们来看一看怎么它们的使用方式。
下面举一个例子:
功能实现: 点击窗口上的按钮, 关闭窗口
功能分析:
- 按钮: 信号发出者 -> QPushButton 类型
- 窗口: 信号的接收者和处理者 -> QWidget 类型需要使用的标准信号、槽函数:
// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭窗口的槽函数
[slot] bool QWidget::close();然后我们使用connect()进行连接:
// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);注意:connnect()函数一般写在窗口的构造函数中,这相当于在事件发生之前就在Qt框架中进行注册。这样的话,在程序运行的过程中如果产生了信号,那么就会调用槽函数进行处理,如果没有信号产生的话,槽函数就不会被调用。
当Qt提供的一些标准信号和槽函数不能满足我们的需求时,我们可以自己定义信号和槽函数,同样使用connect()函数进行连接。
但是有一些自定义信号和槽函数需要注意的事项:
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
class MyMainWindow : public QWidget
{
Q_OBJECT
......
}// 举例: 信号重载
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject
{
Q_OBJECT
signals:
void testsignal();
// 参数的作用是数据传递, 谁调用信号函数谁就指定实参
// 实参最终会被传递给槽函数
void testsignal(int a);
};槽函数就是信号的处理动作,本质可以当做普通的成员函数来使用。如果标准槽函数
总结:
信号函数: void testsig(int a, double b);
槽函数: void testslot(int a);// 槽函数书写格式举例
// 类中的这三个函数都可以作为槽函数来使用
class Test : public QObject
{
public:
void testSlot();
static void testFunc();
public slots:
void testSlot(int id);
};下面举一个例子:
// class GirlFriend --卸载头文件当中
class GirlFriend : public QObject
{
Q_OBJECT
public:
explicit GirlFriend(QObject *parent = nullptr);
signals:
void hungry(); // 不能表达出想要吃什么
void hungry(QString msg); // 可以通过参数表达想要吃什么
};
// class Me
class Me : public QObject
{
Q_OBJECT
public:
explicit Me(QObject *parent = nullptr);
public slots:
// 槽函数
void eatMeal(); // 不能知道信号发出者要吃什么
void eatMeal(QString msg); // 可以知道信号发出者要吃什么
};然后使用connect()函数进行连接:
// 写在int main()中
// 连接两个重载信号/槽。Qt5+ 推荐用函数指针语法
QObject::connect(&girl, QOverload<>::of(&GirlFriend::hungry),
&me, QOverload<>::of(&Me::eatMeal)); // 无参版本
QObject::connect(&girl, QOverload<QString>::of(&GirlFriend::hungry),
&me, QOverload<QString>::of(&Me::eatMeal)); // 有参版本运行结果:

(本篇完)