【QT】 信号与槽初始

前言:这篇就来认识Qt中的信号与槽。


1.从控件到信号与槽

显示一段Hello world,并不只有标签这一种方式。Qt提供了不少和文字有关的控件,先简单区分一下:

控件 主要用途
QLabel 显示文本、图片等内容
QLineEdit 单行文本输入
QTextEdit 多行文本编辑
QPushButton 普通按钮,文字通常作为按钮的说明

比如在窗口构造函数中,可以通过下面两行代码创建一个单行输入框:

cpp 复制代码
QLineEdit* edit = new QLineEdit(this);
edit->setText("Hello world!");

使用时包含<QLineEdit>。new QLineEdit(this)创建输入框,并把当前窗口设置为父对象;setText()设置其中的文字。这和前面创建标签的思路基本一致。

这些控件都直接或间接继承自QWidget,而QWidget又继承自QObject。可以先看下面这张简化关系图:

其实还有一些中间基类,比如QPushButton和QRadioButton还经过QAbstractButton,这里主要是认识它们都在这个继承体系中,能够使用Qt提供的对象通信机制。

控件有了,接下来就需要考虑交互。比如用户点了一下按钮,程序应该修改文字,还是关闭窗口?按钮本身并不知道我们的业务要求,这时就需要把用户操作与处理代码联系起来。

信号可以理解成对象发出的一条通知,槽则是用来响应这条通知的函数。

对于按钮,clicked表示发生了点击;我们再准备一个处理函数,让Qt在收到这个信号时调用它。建立这层关系的函数,就是connect()。

这里先关注四件事:哪个对象发出信号、发出什么信号、由哪个对象接收、接收后执行什么函数。发送者和接收者不一定都是界面控件,本篇先用按钮与窗口来理解。

Qt中的信号与槽属于对象之间的通信机制,和Linux中的进程信号不是同一套东西。

要让某次点击触发我们的处理函数,需要在点击发生前建立连接。

2.connect的基本使用

2.1点击按钮关闭窗口

先看一个直接的例子:窗口上放一个按钮,点击它就关闭当前窗口。

使用QPushButton需要包含<QPushButton>。下面是构造函数中负责这一功能的部分,放在ui->setupUi(this);之后:

cpp 复制代码
// 设置位置
QPushButton* button = new QPushButton(this);
button->setText("我是按钮");
button->move(200, 300);
// close 是 QWidget 内置的槽函数,Widget 继承 QWidget 所以可以使用
connect(button, &QPushButton::clicked, this, &Widget::close);

前三行完成按钮的创建、文字设置和位置设置。move(200, 300)把按钮放到相对于父控件的这个位置

真正把点击和关闭联系起来的是最后一行。把四个参数拆开看:

参数 在这里表示什么
button 信号发送者,也就是这个按钮对象
&QPushButton::clicked 要连接的点击信号
this 信号接收者,也就是当前Widget窗口对象
&Widget::close 接收后调用的成员函数,这里用于关闭窗口

连起来就是:当button发出clicked信号时,调用当前窗口的close()。

这里使用的是QObject提供的静态connect()重载。我们的Widget继承了QWidget,而QWidget属于QObject的继承体系,所以可以在成员函数中直接这样调用。

connect()执行时是在建立联系。后面按钮真正发出信号,才会触发相应处理。

再看第二、第四个参数:它们是成员函数指针,用来告诉Qt要连接哪一个信号、哪一个处理函数。

Widget是我们自己定义的窗口类,QWidget才是Qt提供的类。close()虽然不是我们在Widget里重新实现的,但它从基类继承而来,所以可以写&Widget::close。

还有一点,关闭窗口与立即删除窗口对象不是一回事。对于这里没有额外修改关闭行为的窗口,close()会关闭并隐藏它;对象什么时候析构,还要看它的生命周期和相关设置。可以查考QWidget的close说明。

2.2clicked

刚开始写这行代码时,还有一个很容易混淆的地方:补全列表里既有click,又有clicked,它们有什么区别?

click()是一个槽函数,可以通过代码调用它,模拟一次按钮点击;clicked则是按钮被点击时发出的信号。我们希望响应点击,所以connect()的第二个参数使用的是clicked。

如果在QPushButton自己的接口列表中没有直接找到clicked,可以继续看它的父类QAbstractButton。Qt把不同按钮共有的功能放到了这个基类中,clicked就是继承来的信号。QAbstractButton的clicked说明

查文档时,除了看名字,还要看这个信号什么时候发出 、参数表达什么含义,以及相关功能是不是从父类继承的。把这些弄清楚,比只记住一行connect()更有用,但现在有了AI确实也更方便,但想要更准确权威的答案还是得要参考官方提供的(可能再过几年就不一定了)。

3.通过槽函数切换按钮文字

关闭窗口使用的是Qt已经提供好的函数。如果想实现自己的处理逻辑实现一些定制化的需求,例如让按钮文字在Hello world与Hello Qt之间切换,就可以自己写一个函数。

下面分成纯代码和Designer两种方式。它们是同一个效果的不同写法,分别使用即可,不要把关闭窗口和切换文字的几组示例直接叠在一起。

3.1通过代码创建按钮

先看窗口类的声明,也就是widget.h:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPushButton>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void HandleClick();

private:
    Ui::Widget *ui;
    QPushButton* MyButton;
};
#endif // WIDGET_H

这里主要关注新增的两项:HandleClick()是我们自己的点击处理函数,MyButton用来保存按钮的地址,让另一个成员函数也能够访问这个按钮。

前面关闭窗口的示例中,按钮指针只是构造函数里的局部变量,因为建立连接后,不需要再通过另一个成员函数读取它。现在HandleClick()要修改按钮文字,所以这里把指针保存为窗口类的成员。

保留构造函数已有的ui(new Ui::Widget)初始化和ui->setupUi(this);,把前面创建关闭按钮的那几行替换为下面这组代码:

cpp 复制代码
MyButton = new QPushButton(this);
MyButton->setText("Hello world");
connect(MyButton, &QPushButton::clicked, this, &Widget::HandleClick);

这次信号发送者变成了MyButton,接收者仍然是当前窗口,只是最后一个参数换成了&Widget::HandleClick。

接着在widget.cpp中实现处理函数:

cpp 复制代码
void Widget::HandleClick()
{
    if (MyButton->text() == QString("Hello world")){
        MyButton->setText("Hello Qt");
    }else{
        MyButton->setText("Hello world");
    }
}

text()用来取得按钮当前显示的文字,setText()则用来修改文字。第一次点击时,原来的文字是Hello world,于是进入if分支,把它改成Hello Qt;再点击一次,就进入else分支,改回Hello world。

整个过程就是:按钮被点击,发出clicked信号,Qt调用当前窗口的HandleClick(),这个函数再修改按钮文字。

3.2通过Designer创建按钮

也可以在Designer中直接拖入一个QPushButton。这样按钮的创建代码由界面生成流程负责,我们只需要找到这个按钮,再建立连接。

先在widget.ui中放入一个按钮,把它的objectName设为pushButton,初始text设为Hello world。

这两个属性很容易混在一起,实际上用途不同:

属性 用途
objectName 对象名称,界面生成代码会用相应名称提供访问成员
text 按钮在界面上显示的文字

按钮上显示Hello world,不代表代码中要写ui->Hello world。如果它的objectName是pushButton,对应的访问方式就是ui->pushButton。

这里再把界面生成过程接起来看:构建时,uic读取widget.ui,生成ui_widget.h;其中的界面辅助类保存控件指针,并提供setupUi()来创建和设置控件。qmake负责组织这一步构建工作,真正把.ui转换成C++代码的是uic。UI文件的使用方式

new Ui::Widget创建的是辅助对象,随后调用ui->setupUi(this),才会完成界面控件的创建。因此,下面这行连接要放在ui->setupUi(this);之后:

cpp 复制代码
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::HandleClick);

这一版不需要再执行MyButton = new QPushButton(this);那组三行,也不用再次手动创建同一个按钮。widget.cpp仍然需要包含"ui_widget.h",才能访问界面辅助类中的成员。

cpp 复制代码
void Widget::HandleClick()
{
    if (ui->pushButton->text() == QString("Hello world")){
        ui->pushButton->setText("Hello Qt");
    }else{
        ui->pushButton->setText("Hello world");
    }
}

这里仍然是在同一个HandleClick()中做文字切换,变化的只是访问按钮的方式。前一版通过我们自己保存的MyButton访问,这一版通过界面辅助对象中的ui->pushButton访问。

对于比较固定的界面,用Designer安排控件通常比较方便;需要动态创建控件时,直接写代码也很自然。这两种方式可以结合使用,最后创建出来的仍然是Qt控件,信号与槽的连接思路没有变化, 哪个方便就用哪个。

4.connect的两种写法

4.1SIGNAL与SLOT写法

查看Qt文档或者一些已有代码时,还会遇到另一种connect()形式。它的第二、第四个参数是const char*,看起来和前面传入的成员函数指针完全不同:

这张图展示的是字符串形式的连接重载。发送者、信号、接收者、处理函数这四个要素仍然存在,但信号与槽的表达方式不同。

仍然以点击按钮关闭窗口为例,对照写法如下:

cpp 复制代码
connect(button, SIGNAL(clicked(bool)), this, SLOT(close()));

这里的SIGNAL()和SLOT()是宏。宏里面写的是函数名和参数类型,比如clicked(bool)、close(),不写类名、返回类型,也不写&QPushButton::clicked这样的成员函数指针表达式。

它们会把相应的函数签名文字转换成Qt需要的字符串形式,再由Qt查找对应的信号与槽。这不是把一个函数指针强行转换成char*。 字符指针与成员函数指针是不同类型,两种写法能够工作,是因为调用了不同的connect()重载。

4.2成员函数指针写法

从Qt 5开始,可以使用前面一直在写的这种形式:

cpp 复制代码
connect(button, &QPushButton::clicked, this, &Widget::close);

旧的宏形式仍然可以使用,只是这里更适合通过成员函数指针,把对象与接口直接对应起来。

如果继续看QObject::connect的相关实现,就会发现这种重载使用了模板:

Func1、Func2分别对应传入的信号和处理函数类型,QtPrivate::FunctionPointer用于提取相关类型信息。这里先看懂它为什么能够接收不同类的成员函数即可,因为无我也没去研究。

后面继续补充信号和槽


完

相关推荐
旖旎夜光1 小时前
【LangGraph实战】LangGraph 学习笔记(三):Overwrite、输入输出模式与四大工作流模式
人工智能·笔记·学习·ai·langgraph
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】C++ ChatSDK使用手册
开发语言·网络·c++·人工智能·学习·大模型
DP DPharness9 小时前
ClearAI、记忆插件与普通笔记,三种让 Agent 记住的路数对比
笔记
爱倒腾的老唐10 小时前
0、元器件——电阻
学习
“AI国潮设计-小江”10 小时前
《Python+SDXL实战:用ControlNet精准控制“英歌舞翻糖吐司”构图,附批量生成脚本》
开发语言·人工智能·python·prompt·aigc
程序喵大人10 小时前
【C++入门】编译链接模型 - 01 一个 C++ 程序是怎样变成可执行文件的
开发语言·c++·编译链接模型
sunshine22 girl10 小时前
Java学习七 Java 项目实战2-注册业务
java·学习
piikee12 小时前
粤语初学入门:从粤拼、九声六调到开口,一个普通话母语者的练习笔记
笔记·粤语入门·怎么学习粤语
sunshine22 girl12 小时前
Java学习七 Java 项目实战1-项目创建和主页面搭建
java·开发语言·学习