自定义信号
信号和槽也是可以带参数的,当信号带有参数的时候,槽的参数必须和信号的参数一致,此时发射信号的时候,就可以给信号函数传实参,与之对应的这个参数就会被传递到对应的槽函数中
自定义信号代码的实现(参数版本)
// widget.h
signals:
// 自定义信号的声明
void mySingal(const QString& );
public slots: // 可加可不加
// 自定义槽函数
void handleMysingal(const QString& );
private slots:
void on_pushButton_clicked();
// widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 连接自定义信号和自定义槽函数
connect(this, &Widget::mySingal, this, &Widget::handleMysingal);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handleMysingal(const QString& text)
{
// 自定义槽函数
// 当发出信号后,需要将程序运行的窗口改名为:信号已经发出
this->setWindowTitle(text);
}
void Widget::on_pushButton_clicked()
{
// 发射信号
// 这里是自定义槽函数--在点击按钮后,它会将我们自定义的信号mySingal直接发出
emit mySingal("信号已经发出");
}
C++中声明函数的时候,形参的名字可以不必写。其次就是这里的形参必须要一致,一致主要是要求类型,个数如果不一致也可以,不过要求信号的参数个数必须要比槽的参数个数要更多
signals:
// 自定义信号的声明
void mySingal(const QString& );
public slots: // 可加可不加
// 自定义槽函数
void handleMysingal(const QString& );
带参数版本的作用
传参可以起到复用代码的效果,如果存在多个逻辑,且逻辑思路整体上一致,但是涉及到的数据不同。就可以通过函数-参数来复用代码,并且在不同的场景中传入不同的参数即可
void Widget::on_pushButton_clicked()
{
// 发射信号
// 这里是自定义槽函数--在点击按钮后,它会将我们自定义的信号mySingal直接发出
emit mySingal("点击按钮一,更改标题一");
}
void Widget::on_pushButton_2_clicked()
{
emit mySingal("点击按钮二,更改标题二");
}
- 此时我们再去创建一个按钮,
转到槽函数
,如果点到按钮二,就更改标题为:点击按钮二,更改标题二。很明显,它发出的信号是同一个自定义信号,传的参数不同罢了,这也是代码复用的一种场景
。 - Qt中有很多内置的信号也是带有参数的(这些参数不是咱们自己去传递的),比如
clicked(bool)
信号就带有一个参数,这个bool类型的参数表示当前按钮是否处于“选中”状态,这个选中状态对于QPushButton没啥意义,但对于QCheckBox复选框就很有用了
,这个复选框就是平时待办打勾这种。 - 信号函数的参数个数得超过槽函数的参数个数,此时槽函数就会按照参数顺序拿到信号的前N个参数。若信号函数的参数个数少于槽函数的参数个数,此时代码就无法编译通过。
一定得保证槽函数的每个参数都是有直的
- 直观的思考,应该是要求信号的参数个数和槽的参数个数严格一致,那此处为啥允许信号的参数比槽的参数多呢?因为如果我们严格要求参数个数一致,就意味着信号绑定到槽的要求就变高了。换而言之,当下这样的规则,就允许信号和槽之间的绑定更灵活了,那更多的信号可以绑定到这个槽函数上了。
Q_OBJECT宏的了解
// widget.h
class Widget : public QWidget
{
Q_OBJECT
Qt中的硬性规定,Qt如果要让某个类能够使用信号和槽(或者可以在类中定义信号和槽)则
必须在类的最开始的地方,写下Q_OBJECT宏
,此外这个宏展开的话会有很多额外的代码,里面又包括很多宏。如果不加这个宏,这个类在编译的时候就会报错
信号和槽存在的意义
所谓的信号槽,终究要解决的问题,就是响应用户的操作。信号槽,其实在GUI开发的各自框架中是一个比较有特色的存在
Qt与其它GUI开发框架的区别
- 其它的GUI开发框架,信号槽的实现方式要简洁一点。
不需要搞一个单独的 connect完成上述的信号槽连接
。比如网页开发(js + dom api
)中相应用户的操作主要就是挂回调函数
,function handle(){},button.onclick = handle;
处理函数就像控件的一个属性/成员一样,该回调函数与一个事件是一对一的关系,即一个事件只能对应一个处理函数,一个处理函数也只能对应到一个事件上,现在基本上大部分的GUI开发框架都是这么设计信号槽 - 而Qt信号槽设计了一个connect这个机制,最初的设想有这么两点。第一点:
解耦合,即把触发用户操作的控件和处理用户的操作逻辑解耦合,分别单独处理
。第二点:实现多对多的效果,即一个信号可以connect连接到多个槽函数上,一个槽函数也可以被多个信号connect
从数据库(MySQL)的角度来理解
要去设计数据库的表结构,就需要理解实体和实体(实体 == 对象,就是对现实问题的关键名词进行的抽象
)之间的关系。总共有一对一,一对多,多对多
这三种关系,所以当你去设计表的时候就会有不同的写法(定式),虽然数据库主要是给后端程序员使用的,咱们写Qt其实范围是属于客户端内,很少会直接涉及到数据库,但得学习一些MySOL
的基础用法,毕竟数据库属于是程序员的常识
Qt中谈到的信号和槽的多对多与数据库中的多对多是非常类似的,比如下面的表格中,学生与课程之间就存在多对多的选择关系,一个学生可以选择多门课程学习,一个课程也可以被多个同学选择。
学生学号 | 学生姓名 | 课程编号 | 课程名字 |
---|---|---|---|
1 | 张三 | 100 | 语文 |
2 | 李四 | 101 | 数学 |
3 | 王五 | 102 | 英语 |
因此根据这种多对多的关系,便引入了第三张表作为关联表
(学生-课程表)。一个同学可以选择多门课程,比如这里张三选择了语文和数学。一门课程也可以被多个同学选择,比如语文被张三与李四同学所选择。Qt中一个信号可以connect到多个槽函数上,一个槽函数也可以被connect到多个信号上,这里connect的作用就相当于下面的这个关联表
学号 | 课程编号 |
---|---|
1 | 100 |
1 | 101 |
2 | 100 |
2 | 102 |
connect(this, &widget::mySingal1, this, &widget::mySlot1);
connect(this, &widget::mySingal2, this, &widget::mySlot2);
总结
综上,Qt引入信号槽机制最本质的目的就是为了能够让信号和槽之间按照多对多的方式来进行关联,其他的GUI框架往往也不具备这样的特性。不过实际上在GUI开发的过程中,多对多这件事其实是个伪需求
,实际开发很少会用到,绝大部分情况一对一就够用了,而且新出现的一些图形化开发框架,很少有再继续支持这种多对多的了,但信号槽在Qt当年也是一个很大的卖点