Qt 信号与槽 讲解与案例
信号与槽
所谓信号槽,实际就是观察者模式(发布-订阅模式)。
当某个事件
发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。
也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
信号
是由对象发射出去的消息,信号实际上是一个特殊的函数,不需要由程序员实现,而是由Qt的Qt Meta Object System
实现。
槽
实际上就是普通的函数,成员函数、全局函数、静态函数、lambda函数都可以!
当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号的参数传递给槽函数!
绑定信号与槽
connect 函数
[static] QMetaObject::Connection connect( const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, , Qt::ConnectionType type = Qt::AutoConnection) [static] QMetaObject::Connection connect( const QObject *sender, PointerToMemberFunction signal, Functor functor)
解析:
- sender: 信号的发送者
- signal: 发送的信号
- receiver: 信号接收者
- method:接受到信号之后,自动调用的方法(槽函数)
- type: 表示信号与槽的连接类型,有默认值
简单案例:按钮的点击
关于QT的简单窗口实现代码,请看这篇:
编写第一个Qt窗口
我们首先包含头文件:
#include < QPushButton >
我们就可以使用QPushButton作为我们的按钮了。
Widget::Widget(QWidget* parent) :QWidget(parent) { //设置窗口的大小 resize(200, 200); QPushButton* btn = new QPushButton("我是个按钮", this); connect(btn, &QPushButton::pressed, this, &Widget::close); }
connect:
- btn:作为信号的发送者
- &QPushButton::pressed:信号函数,表示你pressed(点击)
- this:信号的接收者,表示的就是Widget这个窗口
- &Widget::close:槽函数,表示接收信号之后准备干什么,即close掉这个窗口。
效果如下:
我们使用按钮控制窗口的退出就实现了!
我们也可以控制点击按钮使得按钮本身close:
connect(btn, &QPushButton::pressed, btn, &QPushButton::close);
即信号可以被自身接收,然后调用自身的槽函数,使得自身关闭。
自定义槽函数
定义槽函数必须遵循以下规则
-
槽函数的返回类型必须是void类型,不能是其他类型;
-
槽函数的参数必须等于或少于信号的参数
类中的槽成员函数可以使用:
public slots: //槽函数声明
槽函数的类型:
- 成员函数
- 普通成员函数
- 静态成员函数
- 全局函数
- lambda表达式(匿名函数)
void global_func(); Widget::Widget(QWidget *parent) : QWidget(parent) { QPushButton *btn = new QPushButton(this); //连接标准槽函数 connect(btn,&QPushButton::clicked,self,&Widget::close); //连接普通成员函数 connect(btn,&QPushButton::clicked,this,&Widget::member_func); //连接静态成员函数 connect(btn,&QPushButton::clicked,this,&Widget::static_func); //连接全局函数 connect(btn,&QPushButton::clicked,this,&Widget::global_func); //连接lambda表达式 connect(btn,&QPushButton::clicked,this,[=]() { qInfo()<<"lambda"; this->close(); }); } //普通成员函数 void Widget::member_func() { this->close(); } //静态成员函数 void Widget::static_func(bool checked) { qInfo()<<"static_func"<<checked; } //全局函数 void global_func() { qInfo()<<"global_func"; }
如果你想在槽中知道是哪个对象触发的信号,那么你可以使用 QObject *sender() const
函数获取,信号的发送者。
自定义信号
如果想要使用自定义的信号和槽, 首先要编写新的类并且让其继承Qt的某些标准类,我们自己编写的类想要在Qt中使用使用信号槽机制, 那么必须要满足的如下条件:
- 这个类必须从QObject类或者是其子类进行派生
- 在定义类的第一行头文件中加入 Q_OBJECT 宏
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏: class MyMainWindow : public QWidget { Q_OBJECT public: ...... }
如果是单文件编写的,还需要在代码的最下面加上#include "name.moc"
,name是指原文件的名称。
自定义信号需要遵循以下规则:
-
信号是类的成员函数,并且返回类型必须是 void 类型
-
信号函数只需要声明, 不需要定义(没有函数体实现)
-
参数可以随意指定, 信号也支持重载
-
信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
-
在程序中发送自定义信号: 发送信号的本质就是调用信号函数
emit mysignals(); //发送信号 emit是一个空宏,没有特殊含义,仅用来表示这个语句是发射一个信号。
示例:
当我们点击第一个按钮的时候,第二个按钮会消失,但是我们使用自定义信号的方式
class Widget :public QWidget { ... signals: void MySignalFunc(); };
btnTemp->setGeometry(100, 100, 100, 20); connect(btn, &QPushButton::pressed,this,&Widget::MySignalFunc); connect(this, &Widget::MySignalFunc, btnTemp, &QPushButton::close);
解析:我们又创建了一个用于展示效果的临时按钮
- 第一个connect: btn发送pressed的消息,我们的主窗口会接收这个信号,并且这是我们自定义的 MySignalFunc信号。
- 第二个connect:主窗口接收到MySignalFunc信号之后,会再次发出这个信号,使得btnTemp按钮调用close的函数,使得Temp按钮关闭。
信号参数的作用是数据传递, 谁调用信号函数谁就指定实参,实参最终会被传递给槽函数。
信号和槽重载二义性问题
信号和槽是支持重载的,那么,我们就会想到,当我们在connect的时候,会不会出现二义性的问题? 到底会调用哪个呢?
信号的重载二义性:
class SubWindow :public QWidget { ... signals: void OnButtonCutMainWindow(); void OnButtonCutMainWindow(const QString& str) };
槽的重载二义性:
class Widget :public QWidget { ... public slots: //响应按钮信号的槽函数 void OnButtonCutWindow(); void OnButtonCutWindow(const QString&); };
connect(btn,&SubWidget::OnButtonCutMainWindow,this,&Widget::OnButtonCutWindow);
我们应该调用哪个呢??
解决方案
- 一,通过函数指针(类的成员函数当作函数指针)解决
//信号 void (SubWindow:: * GetNone1)() = &SubWindow::OnButtonCutMainWindow; void (SubWindow:: * GetOne1)(const QString&) = &SubWindow::OnButtonCutMainWindow; //槽 void (Widget:: * GetNone2)() = &Widget::OnButtonCutWindow; void (Widget:: * GetOne2)(const QString&) = &Widget::OnButtonCutWindow; //无参连接 connect(SubWidget, GetNone1, this,GetNone2); //有参连接 connect(SubWidget, GetOne1, this, GetOne2);
- 二,通过Qt提供的重载类(QOverload)解决
//2. QOverload解决 //无参连接 connect(SubWidget, QOverload<>::of(&SubWindow::OnButtonCutMainWindow), this, QOverload<>::of(&Widget::OnButtonCutWindow)); //有参连接 connect(SubWidget, QOverload<const QString&>::of(&SubWindow::OnButtonCutMainWindow), this, QOverload<const QString&>::of(&Widget::OnButtonCutWindow));
- 三,Qt4的连接方式
使用Qt4的SIGNAL 和 SLOT 宏的形式:
//3. Qt4的宏解决 connect(SubWidget, SIGNAL(OnButtonCutMainWindow()), this, SLOT(OnButtonCutWindow())); connect(SubWidget, SIGNAL(OnButtonCutMainWindow(const QString&)), this, SLOT(OnButtonCutWindow(const QString&)));
-
总结
- Qt4的信号槽连接方式因为使用了宏函数, 宏函数对用户传递的信号槽不会做错误检测, 容易出bug
- Qt5的信号槽连接方式, 传递的是信号槽函数的地址, 编译器会做错误检测, 减少了bug的产生
- 当信号槽函数被重载之后, Qt4的信号槽连接方式不受影响
- 当信号槽函数被重载之后, Qt6中需要给被重载的信号或者槽定义函数指针
推荐使用方法二。
案例: 实现两个窗口间的切换
父窗口头文件
#ifndef _WIDGET_H_ #define _WIDGET_H_ #include <QWidget> #include <QPushButton> #include <QDebug> #include "SubWindow.h" class SubWindow; class Widget :public QWidget { Q_OBJECT public: Widget(QWidget* parent = nullptr); ~Widget(); public slots: //响应按钮信号的槽函数 void OnButtonCutWindow(); void OnButtonCutWindow(const QString&); private: SubWindow* SubWidget; }; #endif // !_WIDGET_H_
父窗口的实现:
#include "Widget.h" #include <windows.h> Widget::Widget(QWidget* parent) :QWidget(parent),SubWidget(new SubWindow) { resize(250, 250); QPushButton* btn = new QPushButton("点击切换子窗口", this); //点击按钮 切换子窗口 connect(btn, &QPushButton::pressed,SubWidget, &SubWindow::show); //当按钮released(释放时),隐藏hide父窗口 connect(btn, &QPushButton::released, this, &Widget::hide); //子窗口会发送信号,父窗口接收此信号,提示父窗口将要show connect(SubWidget, QOverload<>::of(&SubWindow::OnButtonCutMainWindow), this, QOverload<>::of(&Widget::OnButtonCutWindow)); connect(SubWidget, QOverload<const QString&>::of(&SubWindow::OnButtonCutMainWindow), this, QOverload<const QString&>::of(&Widget::OnButtonCutWindow)); } Widget::~Widget() { } void Widget::OnButtonCutWindow(const QString& Info) { //随便打印一个信息 qInfo() << Info; } void Widget::OnButtonCutWindow() { //子窗口隐藏 SubWidget->hide(); //主窗口显示 this->show(); }
子窗口头文件:
#ifndef _SUBWINDOW_H_ #define _SUBWINDOW_H_ #include "Widget.h" class SubWindow :public QWidget { Q_OBJECT public: SubWindow(QWidget* parent = nullptr); signals: void OnButtonCutMainWindow(); void OnButtonCutMainWindow(const QString& str); }; #endif
子窗口的实现文件:
#include "SubWindow.h" SubWindow::SubWindow(QWidget* parent) :QWidget(parent) { resize(400, 400); QPushButton* SubBtn = new QPushButton("点击切换主窗口", this); //点击按钮,切换回主窗口,会发送切换的信号,等待主窗口的接收。 connect(SubBtn, &QPushButton::clicked, this, [=]() { emit this->OnButtonCutMainWindow(); emit this->OnButtonCutMainWindow("我不想换!!"); }); }
main函数:
#include <QApplication> #include "Widget.h" #include <QDebug> int main(int argc, char* argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }
运行如下:
两个窗口可以相互切换。。。
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209679.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)