信号和槽(slot)
类似于OS中的信号回调机制,一旦发射了信号就会触发(执行)槽函数**。在Qt中,信号看起来是函数的样子(之所以说看起来像,是因为实际上Qt暴露给我们的信号或者我们在Qt上自己创建的信号,都会被Qt的MOC处理成另外一种样子),而槽本质上就是一个函数。**
大部分信号都是Qt提供给我们的,但是我们也可以自定义信号。Qt也提供一些槽,但我们经常自己定义槽。对于Qt给我我们的,我们要多去查阅文档,从子类到父类向上寻找。
在Qt中,信号和槽的关系是多对多关系,即一个槽可以被多个信号关联,一个信号可以关联多个槽。作为Qt的使用者,我们主要做的就是将信号源,信号,槽,接受对象关联起来,目的是使得用户操作后得到反馈。当信号源发射后信号后就会查找信号和槽的关联关系,执行与之关联的所有槽。
关联信号和槽
关联信号和槽有多种方式,我们下面逐一介绍
Connect函数方式
connect是QObject类(也就是Qt中所有类的祖宗)中的一个静态方法,因此所有类中都可以直接调用它。它常使用(connect有多个重载)的重载形式如下:
cpp
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
- sender:发送信号的控件对象的地址,即设置信号源。
- signal:要关联的信号,以函数地址的方式传入。
- receiver:要对信号做出反应的控件对象,以对象指针的方式传入。
- slot:槽函数,以函数指针的方式传入。
- type:基本不会用到,暂时不做解释
接下来我们来使用connect:
cpp
/*只要点击按钮,按钮上就会出现"哈哈"*/
void Widget::handler()
{
button->setText("哈哈");
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
button = new QPushButton(this);//创建按钮对象,button是widget的成员属性
button->move(100,100);//将button安置到某位置
//建立关联关系,&QPushButton::clicked是QAbstractButton的给我们提供的信号,
//继承这个类的其他类型的按钮也有这个信号
connect(button,&QPushButton::clicked,this,&Widget::handler);//建立关联关系
}
cpp
/*只要点击按钮,窗口就会关闭*/
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
button = new QPushButton(this);//创建按钮对象,button是widget的成员属性
button->move(100,100);//将button安置到某位置
//建立关联关系,&QPushButton::clicked是QAbstractButton的给我们提供的信号,
//继承这个类的其他类型的按钮也有这个信号
//&Widget::close是widget提供给我们的槽
connect(button,&QPushButton::clicked,this,&Widget::close);//建立关联关系
}
顺便提一下,以前connect函数的第二和第四个参数都是char*类型的,因此每次传参都要把实参用宏强转成char*,但是现在重载了更好用的版本。
自动关联方式
右击选中想要设置信号和槽的控件,然后选择'转到槽':

然后我们选择一个按钮发射的信号:

然后我们发现widget的成员函数多了一个(头文件和源文件都写好了):

接下来我么只要在该函数编写信号的处理过程即可,该函数就是pushbutton按钮发出发射信号后所执行的槽函数。并且我们不需要手动关联信号源,信号,接收对象,槽,而是Qt通过命名方式自动关联的:

接下来我们使用一下这种方式:
cpp
/*点击按钮,按钮上出现"哈哈"*/
void Widget::handler()
{
button->setText("哈哈");
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
ui->pushButton->setText("哈哈");
}
当我们查看ui文件生成的类,可以发现:

这个函数就是开启名字匹配的方式。
disconnect解除关联
diconnect函数和connect用法一样,只不过他是把关联关系解除。
自定义槽
上面已经使用过自定义槽和Qt提供的槽(如close)。自定义槽其实就是编写一个成员函数然后传参给connect即可,但是在早期,Qt中自定义槽需要在声明时加**"slot"关键字****来帮助Qt进行元编程(现在不需要了,但是加上也可以),比如下面这样:**

槽还可以是一个lambda表达式,调用connect的时候传入即可**:**
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
button = new QPushButton(this);
connect(button,&QPushButton::clicked,this,[this]()->void{this->setWindowTitle("haha");});
}
Widget::~Widget()
{
delete ui;
}
自定义信号
自定义信号只需要写出函数声明,并告诉Qt这是一个信号(使用signals关键字)即可,但是要保证信号的返回值必须是void。例如这样:

定义信号过后,就可以像正常信号一样关联使用了:

上面程序的意思就是打开窗口后Widget发送信号,然后将Widget内的按钮文本替换成"哈哈"。emit也是Qt添加的关键字,表示发射一个信号,但实际上现在不加emit也可以。
信号和槽的参数
如果要使用参数,信号和槽的对应参数的类型必须一致,但是在个数上信号的参数可以比槽更多(不可以更少),这样可以让槽被更多信号复用,关联。直接举一个使用参数的例子:


在这个例子中,我们实现了按不同按钮就设置不同标题的功能,但是设置标题的槽函数只有一个。
注意
一个类想要定义信号和槽,必须在类声明最开始的地方,写下Q_OBJECT宏:

如果不写这个宏,那么不能定义信号槽,但是使用Qt库提供给我们的信号槽是可以的