信号和槽用于两个对象之间的通信。信号和槽机制是Qt的核心特征,也是Qt不同于其他开发框架的最突出特征。在GUI编程中,当改变了一个部件时,总希望其他部件也能了解到该变化。更一般来说,我们希望任何对象都可以和其他对象进行通信。例如,用户单击了关闭按钮,则希望可以执行窗口的close()函数来关闭窗口。为了实现对象间的通信,一些工具包中使用了回调(callback)机制,而在Qt中使用了信号和槽来进行对象间的通信。当一个特殊的事情发生时便可以发射一个信号,比如按钮被单击就发射clicked()信号;而槽就是一个函数,它在信号发射后被调用来响应这个信号。Qt的部件类中已经定义了一些信号和槽,但是更常用的做法是子类化部件,然后添加自定义的信号和槽来实现想要的功能。
信号槽类似于软件设计模式中的观察者模式,(观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。)被观察者发出的信号(signal),观察者收到自己注册监听signal,就通过槽(slot)关联的槽函数function实现动作操作。(这里提一句,Qt 的信号槽使用了额外的处理来实现,并不是 GoF 经典的观察者模式的实现方式)
信号槽优点:
(1)类型安全
信号的参数类型、参数个数需要和槽函数的参数类型和参数个数一致。槽函数的个数也可以少于信号的参数个数,但缺少的参数必须是信号参数的最后一个或最后几个。
(2)松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject。
(3)效率
与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调用速度对性能要求不是非常高的场景是可以忽略的,是可以满足绝大部分场景。
下面通过一个简单的例子来进一步讲解信号和槽的相关知识。这个例子实现的效
果是:在主界面中创建一个对话框,在这个对话框中可以输入数值,当单击“OK”按钮
时关闭对话框并且将输入的数值通过信号发射出去,最后在主界面中接收该信号并且
显示数值。程序的运行效果如图所示。
新建Qt Widgets应用,
项目名称为mysignalslot,基类选择QWidget,类名保持Widget不变。项目建立完成
后,向项目中添加新文件,模板选择Qt分类中的“Qt设计师界面类”,界面模板选择Di-
alog without Buttons,类名设置为MyDialog。
完成后,在ui文件中添加一个pushButton,一个spinBox
在mydialog.h文件中添加代码
- #ifndef MYDIALOG_H
- #define MYDIALOG_H
-
- #include
-
- namespace Ui {
- class MyDialog;
- }
-
- class MyDialog : public QDialog
- {
- Q_OBJECT // 重要
-
- public:
- explicit MyDialog(QWidget *parent = nullptr);
- ~MyDialog();
-
- private:
- Ui::MyDialog *ui;
- signals:
- void DialogReturn(int); // 信号函数
- private slots:
- void on_pushButton_clicked();
- };
-
- #endif // MYDIALOG_H
cpp文件
- #include "mydialog.h"
- #include "ui_mydialog.h"
-
- MyDialog::MyDialog(QWidget *parent) :
- QDialog(parent),
- ui(new Ui::MyDialog)
- {
- ui->setupUi(this);
- }
-
- MyDialog::~MyDialog()
- {
- delete ui;
- }
-
- void MyDialog::on_pushButton_clicked()
- {
- int value = ui->spinBox->value();
- emit DialogReturn(value);
- close();
- }
声明一个信号要使用signals关键字,在signals前面不能用public、private和protected等限定符,因为信号默认是public函数,可以从任何地方进行发射,但是建议只在定义该信号的类及其子类中发射该信号。信号只用声明,不需要也不能对它进行定义实现。
还要注意,信号没有返回值,只能是void类型的。因为只有QObject类及其子类派生的类才能使用信号和槽机制,这里的MyDialog类继承自QDialog类,QDialog类又继承自QWidget类,QWidget类是QObject类的子类,所以这里可以使用信号和槽。不过,使用信号和槽还必须在类声明的最开始处添加Q_OBJECT宏,在这个程序中,类的声明是自动生成的,已经添加了这个宏。
槽就是普通的C++函数,可以像一般的函数一样使用。声明槽要使用slots关键
字,一个槽可以是private、.public或者protected类型的,槽也可以被声明为虚函数,这
与普通的成员函数是一样的。槽的最大特点就是可以和信号关联。
下面打开widget..ui文件,向界面上拖入一个Label部件,更改其文本为“get value is:”。然后进入widget..cpp文件添加头文件#include“mydialog.h”,
Widget.h文件
- #ifndef WIDGET_H
- #define WIDGET_H
-
- #include
-
- QT_BEGIN_NAMESPACE
- namespace Ui { class Widget; }
- QT_END_NAMESPACE
-
- class Widget : public QWidget
- {
- Q_OBJECT
-
- public:
- Widget(QWidget *parent = nullptr);
- ~Widget();
-
- private:
- Ui::Widget *ui;
- private slots:
- void ShowValue(int value);
- };
- #endif // WIDGET_H
cpp文件
- #include "widget.h"
- #include "ui_widget.h"
- #include "mydialog.h"
-
- Widget::Widget(QWidget *parent)
- : QWidget(parent)
- , ui(new Ui::Widget)
- {
- ui->setupUi(this);
- MyDialog* myDialog = new MyDialog(this);
- connect(myDialog,SIGNAL(DialogReturn(int)),this,SLOT(ShowValue(int)));
- myDialog->show();
-
- };
-
- Widget::~Widget()
- {
- delete ui;
- }
-
- void Widget::ShowValue(int value)
- {
- ui->label->setText(tr("get value is : %1").arg(value));
- }
-
现在运行程序查看效果。
这个程序自定义了信号和槽,可以看到它们使用起来很简单,只需要进行关联,然后在适当的时候发射信号即可。这里列举一下使用信号和槽应该注意的几点:
参考: