【Qt】信号与槽
在Qt中,用户和控件的每次交互过程成为一个事件。比如"用户点击按钮"是一个事件,"用户关闭窗口"也是一个事件。每个事件都会发出一个信号,例如用户点击按钮会发出按钮被"按钮被点击"的信号,用户关闭窗口会发出"窗口被关闭"的信号。
Qt中的所有控件都具有接受信号的能力,一个控件还可以接受多个不同的信号。对于接受的每个信号,控件都会做出相应的响应动作。例如,按钮所在的窗口接受到"按钮被点击"的信号后,会做出"关闭自己"的响应动作;再比如输入框自己接受到"输入框被点击"的信号后,会做出"显示闪烁的光标,等待用户输入数据"的响应动作。在Qt中,对信号做出的响应动作就称之为槽。
信号的本质:
信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时Qt对应的窗口类会发出某个信号,以此对用户的操作做出反应。因此,信号的本质就是事件。
如:按钮单击、双击,窗口刷新,鼠标移动
槽的本质:
槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何位置(public、protected、private),可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与一般的函数的不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
一、connect
connect是QObject提供的静态的成员函数。该函数专门用来关联指定的信号函数和槽函数。
关于QObject这里画个图介绍一下:
QObject就是其他Qt内置类的"祖宗",所有类都是继承QObject类,另外不仅有QWidget控件继承QObject还有其他类........
connect (const QObject * sender , // 描述了当前信号是哪个控件发出来的const char * signal , // 描述了信号的类型,所谓"信号"也是Qt中的对象,内部提供的一些成员函数const QObject * receiver,// 描述信号如何处理,哪个对象负责处理(控件)const char * method , // 描述信号如何处理,这个对象怎么处理(要处理信号的对象提供的成员函数)Qt::ConnectionType type = Qt::AutoConnection )
在这里要注意一下这个内部函数的变量类型,其中char* 是所有指针的一个统称。想了解更多的话,可以翻阅文档和查看一下底层代码声明(文档目前还没有修正,可以看一下底层代码声明)
另外connect要求,这俩参数(sender,signal)是匹配的,sender的类型如果是QPushButton* 此时第二个参数signal信号必须是QPushButton内置的信号或者是从父类继承来的信号。不能是一个其他的类,比如QLineEdit的信号。
下面是一个点击按钮关闭的connect:
在connect中信号是有一个WIFE是信号的标志,槽是一个带有小钜尺的标志
写一个简单的例子:
界面上包含一个按钮,用户点击按钮,则关闭窗口。
代码实现一下:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QPushButton* button = new QPushButton(this);button->setText("关闭");button->move(200,200);connect(button, &QPushButton::clicked,this,&Widget::close);
}
此外想了解更多的槽函数还有信号可以参考一些Qt中自带的文档。直接在Qt自带的文档索引那一栏搜索某一类。
下面我搜索的是QAbstractButton这一类的信号与槽:
当然如果在这个对象没找到想要的槽函数和信号可以找一下父类对象或子类对象,父类对象或子类对象的位置,文档也有体现和说明。
此外还可以自定义信号
二、自定义信号
自定义信号的书写:
1.自定义信号函数必须写到"signals"下;
2.返回值为void,只需要声明,不需要实现;
3.可以有参数,也可以发生重载;
发送信号
使用"emit"关键字发送信号。"emit"是一个空的宏。"emit"其实是可选的,没有什么含义,只是为了提醒开发人员。
下面写一个简单的自定义信号的例子:
widget.h
widget.cpp
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this,&Widget::mySignal,this,&Widget::handleMySignal);
}void Widget::handleMySignal()
{this->setWindowTitle("处理自定义信号");
}void Widget::on_pushButton_clicked()
{// 发送自定义的信号// 发送信号的操作,也可以在任意合适的代码中,不一定非得在构造函数里。// 此时就是点击按钮的时候,发送自定义信号了emit mySignal();
}
示例二:
老师点击"按钮"触发学生上课;
也就是说老师点击按钮这个动作是信号,学生执行上课是槽函数。这里我们分别定义了两个类一个老师类,一个学生类
class Teacher : public QObject
{Q_OBJECT
public:explicit Teacher(QObject *parent = nullptr);signals:// 自定义信号函数说明void MySignal();
};
class student : public QObject
{Q_OBJECT
public:explicit student(QObject *parent = nullptr);signals:
public slots://自定义槽函数声明void StartStudy();
};
在student这个类当中实现槽函数
// 自定义槽函数实现
void student::StartStudy()
{qDebug() << "回到座位,开始学习";
}
在widget声明一下标识这两个类Teacher *tch; student* stu;
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void EmitSignal(); // 信号发送函数
private:Ui::Widget *ui;Teacher *tch; // 发送信号的对象student* stu; // 接受信号的对象
};
这里我介绍一下connect(button,&QPushButton::clicked,tch,&Teacher::MySignal);这句代码,我们不应该绑定槽函数吗?为什么这里绑定了自定义MySignal没有发生报错????
这行代码确实是信号连接到信号,而非传统的信号连接到槽。这种用法是 Qt 框架的高级特性,允许信号形成链式触发。
这里实际发生的是:
1.当用户点击button时,QPushButton::clicked信号被发射。
2.这个信号直接触发tch对象的MySignal信号,无需通过槽函数中转。因为已经连接stu中的StartStudy这个槽函数。
最终效果:按钮点击 → Teacher
发射MySignal
→ student
开始学习。
connect布局可以信号->槽 还可以信号->信号,实现事件转发,状态同步过程!
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);tch = new Teacher(this);stu = new student(this);QPushButton *button = new QPushButton("上课了",this);button->move(100,100);connect(tch,&Teacher::MySignal,stu,&student::StartStudy);connect(button,&QPushButton::clicked,tch,&Teacher::MySignal);EmitSignal();
}Widget::~Widget()
{delete ui;
}void Widget::EmitSignal()
{emit tch->MySignal();
}
三、自定义槽
1.要写到"public slots"下
2.返回值为void,需要声明,也需要实现;
3.可以有参数,可以发生重载;
举个例子:点击按钮,修改窗口名
widget.h声明一下自定义的槽函数handleClicked
widget.cpp中的代码
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);
}
void Widget::handleClicked()
{// 按下按钮,修改一下窗口标题this->setWindowTitle("按钮已经按下");
}
自定义槽的第二种方式图形化操作
点击clicked()后,Qt Creactor会给我们自动生成一个函数。
带参数的信号和槽
当信号带有参数的时候,槽的参数要和信号的参数一致,若不一致的时候,要求信号的参数的个数必须要比槽的参数个数要更多。
举个例子:重载信号槽
widget.h
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void EmitSignal(); // 信号发射函数
signals:void MySignal(); // 信号函数声明void MySignal(QString); // 发生重载的信号
public slots:void MySlot(); // 槽函数声明void MySlot(QString); // 发生重载的槽函数
private:Ui::Widget *ui;
};
widget.cpp
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);// 注意:在指针前面一定要加上类作用域说明符,说明该指针指向那个类的成员函数void (Widget::*Signal_p)(QString) = &Widget::MySignal;void (Widget::*Slot_p)(QString) = &Widget::MySlot;connect(this,Signal_p,this,Slot_p);EmitSignal();
}Widget::~Widget()
{delete ui;
}void Widget::EmitSignal()
{emit MySignal("Hello");
}void Widget::MySlot()
{qDebug() << "MySlot被调用";
}void Widget::MySlot(QString str)
{qDebug() << "MySlot(QString)被调用" << str;
}
示例2:信号槽参数列表匹配规则
widget.h
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void EmitSignal(); // 信号发射函数
signals:void MySignal(QString); // 信号函数参数为QString
public slots:void MySlot(QString); //槽函数参数为QString
private:Ui::Widget *ui;
};
widget.cpp
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this,&Widget::MySignal,this,&Widget::MySlot); // !!!!EmitSignal();
}Widget::~Widget()
{delete ui;
}void Widget::EmitSignal()
{emit MySignal("Hello");
}
void Widget::MySlot(QString str)
{qDebug() << str;
}
示例3:在"widget.h"头文件中声明信号和槽函数;
widget.h
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void EmitSignal(); // 信号发射函数
signals:void MySignal(QString); // 信号函数参数为QString
public slots:void MySlot(); //槽函数无参数
private:Ui::Widget *ui;
};
widget.cpp
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this,&Widget::MySignal,this,&Widget::MySlot);EmitSignal();
}Widget::~Widget()
{delete ui;
}void Widget::EmitSignal()
{emit MySignal("hehehe");
}
void Widget::MySlot()
{qDebug() << "Hello";
}
综上所述,当只定义一个槽函数(不发生重载)还有一个信号(不发生重载)可以直接connect绑定!若发生重载则要进行函数指针进行绑定!
四、信号与槽的连接方式
一对一:
主要有两种形式,分别是:一个信号连接一个槽 和 一个信号连接一个信号
一对多&多对一:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(this,&Widget::mySignal1,this,&Widget::mySlot1);connect(this,&Widget::mySignal1,this,&Widget::mySlot1);connect(this,&Widget::mySignal2,this,&Widget::mySlot1);connect(this,&Widget::mySignal2,this,&Widget::mySlot3);}
五、disconnect
断开信号槽的连接
disconnect使用方式和connect是非常类似的
disconnect用的比较少,大部分的情况下,把信号和槽连接上了之后,就不必管了。主动断开往往是把信号重新绑定到另一个槽函数上
示例:两个按钮,按钮一执行hehe,按钮二解绑按钮一打印hehe连接handleClick2()
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);
}Widget::~Widget()
{delete ui;
}void Widget::handleClick()
{this->setWindowTitle("hehe");qDebug() << "handleClick";
}void Widget::handleClick2()
{this->setWindowTitle("666666");qDebug() << "handleClick2";}void Widget::on_pushButton_2_clicked()
{// 1. 先断开 pushButton 原来的信号槽disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick);// 2. 重新绑定信号槽connect(ui->pushButton,&QPushButton::clicked,this,&Widget::handleClick2);
}
六、lambda表达式
这里就是C++相关的知识点了不过多赘述,[=]捕获外部所有变量[&] 引用外部所有变量
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QPushButton* button = new QPushButton(this);button->setText("按钮");button->move(200,200);connect(button,&QPushButton::clicked,this,[=](){qDebug() << "lamada被执行了";button->move(300,300);this->move(100,100);});
}
下面是Qt4中的connect连接方式,了解一下