目录
信号和槽的概述
Qt中的信号与Linux中的信号,虽然不是一样的概念,但是却有相似之处。
在Qt中,谈到信号,涉及到三个要素:
- 信号源:由哪一个控件发出的信号。
- 信号的类型:用户进行不同的操作可能触发不同的信号。
- 信号的处理方式:槽函数。
在Qt中,用户和控件的每一次交互过程称为一个事件。就比如,"用户点击按钮"是一个事件,"用户关闭窗口"也是一个事件。每个事件都会发出一个信号,就比如,"按钮被点击"的信号,"窗口被关闭"的信号。这种GUI程序,目的就是为了让用户进行操作,就是为了和用户进行交互,这个过程中就需要关注用户当前的具体操作。
Qt中的所有控件都具有接受信号的能力,一个控件还可以接受多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作,就比如,按钮所在的窗口接收到"按钮被点击"的信号之后,会做出"关闭窗口"的操作。在Qt中,对信号做出的响应动作就称之为槽。
信号和槽是Qt特有的消息传输机制,它能将相互独立的控件关联起来,就比如,"窗口"和"按钮"俩个独立的控件,点击按钮并不会对窗口造成影响,但是通过信号与槽机制,可以将按钮与窗口连接起来,实现关闭窗口的操作。
信号
信号是由于用户对窗口或者控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时Qt对应的窗口类会发出某个信号,以此对用户的操作做出反应。因此,信号的本质就是事件。
- 按钮单机、双击
- 窗口刷新
- 鼠标移动、鼠标按下、鼠标释放
- 键盘输入
Qt中的信号通过什么形式呈现给使用者?
- 对某个窗口进行操作,窗口会捕捉这些被触发的事件。
- 触发事件可以得到Qt框架发出的信号。
- 信号的呈现形式就是函数,就是说某个事件产生了,Qt框架会调用某个对应的信号函数。
【注意】Qt中信号的发出者是某个实例化的类对象。
槽
槽(SLot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数一样,可以定义在类的任何位置(public、protected或private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能由默认参数)。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
槽函数的本质也是一种回调函数(callback)。
【说明】:
(1)信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。
(2)信号函数和槽函数通常位于某个类中,和普通的成员函数相比,其特别之处在于:
- 信号函数用signals关键字修饰,槽函数用public slots、protected slots或者private slots修饰。signals和slots是Qt在C++的基础上扩展的关键字,专门用来指明信号函数和槽函数。
- 信号函数只需要声明,不需要定义(实现),槽函数需要定义(实现)。
【注意】信号函数的定义是Qt在编译之前生成的,这种自动生成代码的机制称为元编程。
信号与槽的使用
连接信号与槽
在Qt中可以使用connect这样的函数,把一个信号和一个槽关联起来,后续只要信号触发了,Qt就会自动的执行槽函数。一定需要将信号的处理方式准备好,在进行触发信号。
在Qt中,一定先关联信号和槽,然后再触发信号,顺序是不能颠倒的。
connect是QObject提供的金泰的成员函数,该函数专门用来关联指定的信号函数和槽函数。
QObject是Qt内置的父类,Qt中提供的很多类都是直接或者间接继承自QObject。
像QPushButton、QLineEdit、QTextEdit...这些类都有一个父类QWidget,这个QWidget是一个控件,QWidget的父类是QObject.
connect()函数原型
cpp
connect (const QObject *sender,
const char * signal ,
const QObject * receiver ,
const char * method ,
Qt::ConnectionType type = Qt::AutoConnection )
参数说明:
- sender:信号的发送者
- signal:发送的信号(信号函数)
- receiver:信号的接收者
- method:接收信号的槽函数
- type:用于指定关联方式,默认的关联方式为Qt::AutoConnection,通常不需要手动设定。
参数1:sender描述了当前信号是哪个控件发出来的。
参数2:signal信号的类型
参数3:哪个对象(控件)复杂处理
参数4:这个对象(控件)该如何处理(要处理信号的对象提供的成员函数)
案例:点击"按钮"时关闭窗口
cpp
#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(350,250);
connect(button,&QPushButton::clicked, this, &QWidget::close);
}
Widget::~Widget()
{
delete ui;
}
所谓的"信号"也是Qt中的对象,内部提供的成员函数。
- click:动词,是一个slot槽函数,作用就是在调用的时候相当于点击了一些按钮
- clicked:过去分词,已经点击完成,之后会触发信号。
- 图标上带有锯齿的是slot函数(槽函数)。
- 图标上带有WiFi信号图标的是信号函数。
connect要求参数1和参数2是匹配的,如果参数1是button,button的类型如果是QPushButton*,此时第二个参数的信号必须是QPushButton内置的信号(父类的信号),不能是其他的类。
close是QWidget内置的槽函数,Widget继承自QWidget,也就继承里面的槽函数。close槽函数功能已经被系统实现,作用是关闭当前的窗口或者控件。
cpp
#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(350,250);
//connect(button,&QPushButton::clicked, this, &QWidget::close);
connect(button,&QPushButton::clicked, button, &QPushButton::close);
}
Widget::~Widget()
{
delete ui;
}
这个代码就可以关闭QPushButton这个控件。
俩个问题
问题1
如何知道QPushButton由clicked信号?如何知道widget中有close槽的,那么Qt里面到底提供了哪些内置信号和槽可以让我们直接使用呢?
查看内置信号和槽:
系统自带的信号和槽通常是通过"Qt 帮助文档"来查询。
如上述所述,要查询"按钮"的信号,在帮助文档中输入:QPushButton
在翻译文档的时候,如果在当前文档中没有找对对应的线索,不妨试试这个类的父类:
在Qt中会提供多种按钮,这些按钮之间存在一些"共性内容",把这些共性内容提取出来,放到了QAbstractButton类里面。
查询文档中的信息的时候,最重点的就是关注信号的发送时机。
问题2
connect函数的第二个和第四个参数的类型是char*,但是在使用的却是函数指针,char* 和函数指针是同一个东西吗?
所谓指针,其实是一个统称,char*、int*和函数指针都是不同的类型,在C++中,不允许使用俩个不同类型的指针相互赋值(函数传参本质上就是赋值)。
在Qt文档中的旧版本中的connect()函数声明中可以看出,旧版本传参的写法和新的传参也是有区别的。
旧版本给信号传参,需要搭配一个SIGNAL宏,给槽函数传操,需要搭配一个SLOT宏,将传入的函数指针转成char*。
从Qt5中,对旧版本进行了简化,不需要写SIGNAL和SLOT宏了,给connet提供了重载版本,重载版本中,第二个参数和第四个参数成了泛型参数,允许传入任意类型的函数指针。
新版本的connect函数带有一定的参数检查功能,如果传入的第一个参数和第二个参数不匹配或者传入的第三个参数和第四个参数不匹配(即第二个参数和第四个参数的函数指针不是第一个参数和第三个参数的成员函数),此时代码会编译出错。
自定义信号与槽的基本语法
在Qt中,允许自定义信号的发送方已经接收方,即可以自定义信号函数和槽函数。但是对于自定义的信号函数和槽函数有一定的书写规范。
自定义信号函数的书写规范
- 自定义信号函数必须写到"signals"下;
- 返回值为void,只需要声明,不需要实现;
- 可以有参数,也可以发生重载;
自定义槽函数的书写规范
- 早期的Qt版本要求槽函数必须写到"public slots"下,但是现在高级版本的Qt允许写到类的"public"作用域或者全局下。
- 返回值为void,需要声明,也需要实现;
- 可以有参数,可以发射重载
发送信号
使用"emit"关键字发送信号。"emit"是一个空的宏。"emit"其实是可选的,没有什么含义,只是为了提醒。
自定义槽
所谓的槽,就是一个普通的成员函数,换句话来说:自定一个槽函数的操作过程与自定义一个普通的成员函数没有区别。
第一种自定义槽函数的方式
使用纯代码的方式,下面将举一个例子:按下按钮,修改窗口的标题。
cpp
#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(350,250);
connect(button, &QPushButton::clicked, this, &Widget::buttonClick);
}
Widget::~Widget()
{
delete ui;
}
void Widget::buttonClick()
{
this->setWindowTitle("按钮已被更改");
}
在以前版本的Qt中,槽函数必须放在public/private/protected slots中。
类似这种,此处的slots是Qt自己扩展的关键字(不是C++标准中的语法)。
Qt里广泛使用了元编程技术(基于代码,生成代码),qmake构建Qt项目的时候,就会调用专门的扫描器,扫描代码中特定的关键字(slots这种),基于关键字自动生成一大堆相关的代码。
第二种自定义槽函数的方式
在这里直接编辑槽函数即可。
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
this->setWindowTitle("按钮已被更改");
}
自动生成槽函数原型框架:
(1)在"widget.h"头文件中自动添加槽函数的声明。
【说明】自动生成槽函数的名称有一定的规则。槽函数的命名规则为:on_xxx_sss,其中:
- 以"on"开头,中间使用下划线连接起来;
- "xxx"表示的是对象名(控件的objectName属性);
- "sss"表示的是对应的信号。
(2)在"widget.cpp"中自动生成槽函数定义
所以,在Qt中除了通过connect来连接信号槽之外,还可以通过函数名字的方式自动连接。
在Qt中调用这个函数的时候,就会触发上述自动连接信号槽的规则。