Qt:信号和槽

目录

一、自定义槽函数

connect手动连接

自动快速绑定

二、自定义信号

三、带参数的信号和槽

参数个数不一致的情况

四、信号和槽的意义

五、断开连接

六、使用lambda表达式定义槽函数


一、自定义槽函数

槽函数,又称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来编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值