目录
一、自定义槽函数
槽函数,又称slot函数,它本身就是一个普通的类成员函数
connect手动连接
比如,在处理按钮控件的点击信号上,可以自定义槽函数。
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("这是一个按钮");
button->move(100,100);
connect(button,&QPushButton::clicked,this,&Widget::handleClicked);
}
其中handleClicked是我们自定义的槽函数,在Qt中,函数的实现严格遵守声明和实现分离,因此在Widget.h头文件中写好声明后,点击 Alt+回车 快速生成在Widget.cpp中的定义。
自定义槽函数实现
void Widget::handleClicked()
{
//槽函数对点击的处理:按下按钮,修改窗口标题
this->setWindowTitle("修改后的窗口标题");
}
文章开头提起,slot函数本身是一个普通成员函数,是基于Qt 5来讲,在早期Qt版本中,槽函数的声明必须由public slots限定符修饰,
public slots:
void handleClicked();
值得说明的是,slots关键字是由Qt扩展的,而非C++标准中的
- Qt广泛的使用元编程技术,即基于现有的代码,生成更多的代码。在qmake构建Qt项目的时候,qmake调用特定的扫描器,扫描代码中像slots这样的关键字,基于关键字生成更多的代码
自动快速绑定
选中控件后,右键
弹出的窗口显示了当前可选的信号,不仅包含了QPushButton自己的信号,还显示了继承于父类的信号。
选中信号后,页面跳转到自动生成的槽函数。
实现槽函数
void WidgetTest::on_pushButton_clicked()
{
this->setWindowTitle("切换标题窗口");
//error:
//ui->pushButton->setWindowTitle();
//ui->setWindowsTitle();
}
在实现槽函数时,我的目标是设置窗口标题,调用setWindowTitle函数,作为一个Qt初学者,我犯了两个错误
写法一,error,ui->pushButton->setWindowTitle,编译通过,点击切换标题窗口没有反应,原因是pushButton的类型为QPushButton,继承自QWidget类,因此有setWindowTitle函数,但是这句代码的执行结果是修改了按钮作为独立窗口时的标题
写法二,编译不通过,原因是ui的类型为Ui::Widget*,没有setWindowTitle函数,ui是Ui::Widget*类型,this指针也是Widget*类型,这是两种不同的类型
因为在widget.h中,有这样一行代码,
namespace Ui { class Widget; }注意Widget类型是在命名空间Ui中,而this指针的类型是全局作用域中的Widget*类型,这是不一样的类型,Qt初学者容易混淆。
在实现槽函数以后,点击信号能正确被槽函数处理,但是整个源文件中,并没有connect函数的调用,信号和槽是如何绑定到一起的。
这是因为自动生成的槽函数,命名格式十分规范,编译器能识别到这就是clicked信号对应的槽函数
private slots:
void on_pushButton_clicked();
二、自定义信号
自定义槽函数非常关键,在实践开发中,信号触发槽函数的业务逻辑往往都是需要自定义的,相比之下,自定义信号就比较少见了,一个控件可触发的大部分信号Qt已经内置了,即使如此,自定义信号还是有必要学习的。
Widget虽然没有定义任何信号,但其中已经有继承自QObject和QWidget的信号了。
所谓的信号,本质上也是函数,在Qt5及更高版本中,槽函数和普通函数几乎没啥区别,但是信号还是有自己特殊的点:
1.信号是一类特殊的函数,定义信号,只需要写出函数声明,并“告诉”编译器这是一个信号,函数的定义实现,是在编译过程中,由Qt自动生成,程序员无法直接干预,这是因为信号函数在Qt中是特殊的机制,由Qt框架来生成函数的实现可以配合完成很多操作。
2.信号函数的返回值必须是void类型,而函数参数没有限制,甚至可以重载。
signals:
void mySignal();
Qt扩展了一个signals的关键字,qmake会调用代码分析工具,当扫描到signals时,Qt会把接下来的函数声明当作是信号函数,并且自动生成函数定义。
connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
void Widget::handleMySignal()
{
this->setWindowTitle("修改了标题");
}
Qt扩展了emit关键字用来发射信号
emit mySignal();
但在Qt5及更高版本中,emit不做任何操作了,发射操作都在Qt自动定义的函数中实现,写成下面这样也是可以发射信号的。
mySignal();
三、带参数的信号和槽
信号和槽也可以带参数,当信号带有参数的时候,槽函数的参数必须和信号的参数一致。
一致:指要求参数类型要匹配,个数可以不一定完全相等,但是如果不一致的时候,要求信号的参数个数要多于槽函数的个数。
带参数的信号和槽有什么用呢?
最常用的就是多个控件要间接的调用同一个槽函数完成操作。
先用快速绑定的方法定义绑定两个按钮的槽函数。
在此基础上自定义出一个带参数的信号和槽函数。
void hanlerSignal(const QString&);
signals:
void mySignal(const QString &);
在控件的槽函数中都发射自定义的信号,来执行同一个操作。
void Widget::hanlerSignal(const QString& text)
{
this->setWindowTitle(text);
}
void Widget::on_pushButton_clicked()
{
emit mySignal("修改标题为参数一");
}
void Widget::on_pushButton_2_clicked()
{
emit mySignal("修改标题为参数二");
}
参数个数不一致的情况
如果信号的参数个数比槽函数的个数多,则编译没问题。
void hanlerSignal(const QString&);
signals:
void mySignal(const QString &,const QString&);
void Widget::hanlerSignal(const QString& text)
{
this->setWindowTitle(text);
}
void Widget::on_pushButton_clicked()
{
emit mySignal("修改标题为参数一","");
}
void Widget::on_pushButton_2_clicked()
{
emit mySignal("修改标题为参数二","");
}
这样的实现也是可以编译成功的。
而如果是信号的参数少,槽函数的参数个数多,这样则编译失败,槽函数调用的时候没有传足够多的参数,无法成功调用它。
为什么Qt中不要求信号和槽函数强制一一对应,而是要求只需信号的参数个数不少于槽函数的个数,这样的要求就留出来一种情况,就是一个槽函数可以对应多个信号,这样一来,一个槽函数可以被更多的信号绑定,至少在实现上肯定是槽函数比较复杂,所以让一个槽函数去绑定多个信号,而不是让一个信号去绑定多个槽函数。
四、信号和槽的意义
不同于其他GUI开发框架,在控件的操作(信号)和处理方法(槽函数)上,其他语言如js,大致是clicked = handler,这种直接声明绑定的方式,而Qt却做了中间一层connect函数。
原因是Qt在设计时,考虑到两个问题
1.解耦合,信号和槽函数的强制解耦合
2.多对多的设计,js这种语言下的绑定是一对一实现,而Qt却很容易实现一个槽函数绑定多个信号,或者一个信号绑定多个槽函数。
- Qt实现多对多
实现两个信号、三个槽函数。信号1可以绑定槽函数2、3,信号2可以绑定槽函数1、3。
槽函数3可以被信号1、2同时绑定。
signals:
void mySignal1();
void mySignal2();
private slots:
void mySlot1();
void mySlot2();
void mySlot3();
//信号1绑定槽函数2、3
connect(this,&Widget::mySignal1,this,&Widget::mySlot2);
connect(this,&Widget::mySignal1,this,&Widget::mySlot3);
//信号2绑定槽函数1、3
connect(this,&Widget::mySignal2,this,&Widget::mySlot1);
connect(this,&Widget::mySignal2,this,&Widget::mySlot3);
Qt引入信号槽机制的初心,就是让为了让信号和槽函数之间可以多对多的关联,其他GUI框架几乎不支持这个机制。
然而,实际开发经验中,多对多是一个伪需求,一对一的机制已经完全够用了。
五、断开连接
使用disconnect函数断开信号槽的连接。
实际中,很少用disconnect来断开连接。
如果使用disconnect,往往是要把信号绑定的槽函数更换,因为如果不断开连接,就会变成多对多的机制,并不符合实际要求。
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handlerClicked);
}
Widget::~Widget()
{
delete ui;
}
void Widget::handlerClicked()
{
this->setWindowTitle("修改窗口标题");
}
现在想让这个按钮的槽函数绑定到一个新的槽函数上面。
定义一个新的槽函数。
void Widget::handlerClicked2()
{
this->setWindowTitle("新的修改标题函数");
}
在按钮2的槽函数中重新绑定。
void Widget::on_pushButton_2_clicked()
{
//1.点击按钮2,更换按钮1的槽函数
//先断开连接
disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handlerClicked);
//2.把信号绑定到新的槽函数上。
connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handlerClicked2);
}
最终实现效果是,点击按钮1,修改窗口标题,点击按钮2,更换槽函数,再点击按钮1,则是新的标题。
加上日志打印,来观察,如是不断开连接,再次绑定,则实现一个信号绑定了多个槽函数,他们都会被执行。
void Widget::on_pushButton_2_clicked()
{
//1.点击按钮2,更换按钮1的槽函数
//先断开连接
//disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handlerClicked);
//2.把信号绑定到新的槽函数上。
connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handlerClicked2);
}
六、使用lambda表达式定义槽函数
lambda表达式,很多语言都支持,形象的称为一个语法糖,本质上是一个匿名函数,类比之下,可以使用在回调函数的场景。
lambda表达式,最大的特点就是一次性使用,不用声明和定义分离。
QPushButton* button = new QPushButton(this);
button->setText("这是一个按钮");
connect(button,&QPushButton::clicked,this,[button](){
button->move(300,300);
});
如果想使用上下文中所有参数,可以[=]按照值的方式来捕获变量,如果想按照引用的方式来捕获变量,则使用[&]这种写法,Qt中一般都是按照值的方式捕获。
lambda表达式是C++11才有的语法,所以对于Qt5及其更高版本,默认按照C++11来编译
而如果使用Qt4及其以下版本,则要在.pro文件中指定按照C++11来编译。