Qt自定义信号和槽函数
实际开发中,如果仅使用 Qt 提供的信号函数和槽函数,会经常遇到信号函数的参数类型和个数无法满足实际需求、信号函数和槽函数的参数类型不匹配等问题。解决此类问题,最简单有效的方式就是:自定义场景需要的信号函数和槽函数。
举个简单的例子:
对于 MySignal() 信号函数,程序中不会直接调用它,而是借助 connect() 连接某个槽函数,实现的语法格式是:
对于 Qt 提供给我们的信号函数,其底层已经设置好了信号发出的时机,例如按下鼠标时、点击 Enter 回车键时等等。对于自定义的信号,我们需要自己指定信号发出的时机,这就需要用到 emit 关键字。emit 中文意思为“发出、射出”,是 Qt 在 C++ 基础上扩展的一个关键字,专门用来发射信号。
以定义好的 MySignal 信号为例,修改 MyWidget 类为:
和信号函数不同,槽函数必须手动定义(实现)。槽函数可以在程序中直接调用,但主要用来响应某个信号。自定义一个槽函数时,需要注意以下几点:
举个例子,自定义响应 MySignal 信号的槽函数:
slots 关键字可以和 public、protected、private 搭配使用,它们的区别是:
通常情况下,槽函数使用 public slots 修饰。
自定义信号函数
信号函数指的是符合以下条件的函数:- 定义在某个类中,该类直接或间接继承自 QObject 类;
- 用 signals 关键字修饰;
- 函数只需要声明,不需要定义(实现);
- 函数的返回值类型为 void,参数的类型和个数不限。
举个简单的例子:
class MyWidget:public QWidget{ //Q_OBJECT 是一个宏,添加它才能正常使用 Qt 的信号和槽机制 Q_OBJECT //修饰信号函数的关键字 signals: //自定义的信号函数 void MySignal(QString message); };我们自定义了一个继承自 QWidget 的 MyWidget 类,QWidget 是 QObject 的子类,所以 MyWidget 间接继承自 QObject 类。MyWidget 类中自定义了名为 MySignal 的信号函数(可以简称 MySignal 信号),它用 signals 关键字修饰,没有返回值,也没有定义(实现),仅有 1 个参数。
对于 MySignal() 信号函数,程序中不会直接调用它,而是借助 connect() 连接某个槽函数,实现的语法格式是:
MyWidget myWidget; QObject::connect(&myWidget,&MyWidget::MySignal,信号接收者,槽函数);一旦确定了信号接收者和槽函数,当 MySignal 信号发出后,与之相连的槽函数就会执行。那么,程序中如何发出 MySignal 信号呢?
对于 Qt 提供给我们的信号函数,其底层已经设置好了信号发出的时机,例如按下鼠标时、点击 Enter 回车键时等等。对于自定义的信号,我们需要自己指定信号发出的时机,这就需要用到 emit 关键字。emit 中文意思为“发出、射出”,是 Qt 在 C++ 基础上扩展的一个关键字,专门用来发射信号。
以定义好的 MySignal 信号为例,修改 MyWidget 类为:
class MyWidget:public QWidget{ //Q_OBJECT 是一个宏,添加它才能正常使用 Qt 的信号和槽机制 Q_OBJECT //自定义信号函数 signals: void MySignal(QString mess); public: void emitSignal(){ emit MySignal(message); } private: QString message; };我们为 MyWidget 类新增了一个 emitSignal() 方法和一个 message 属性,emitSignal() 方法中的
emit MySignal(message);
语句就表示发射 MySignal 信号。当程序中执行 emitSingal() 函数时,就会发出 MySignal 信号,message 属性的值也会随信号一同发出,对应的槽函数可以接收到 message 的值。对于每一个自定义的信号函数,程序中都应该提供发射该信号的方法(函数),而且这样的方法(函数)可以有多个。
自定义槽函数
Qt5 中,槽函数既可以是普通的全局函数、也可以是类的成员函数、静态成员函数、友元函数、虚函数,还可以用 lambda 表达式表示。和信号函数不同,槽函数必须手动定义(实现)。槽函数可以在程序中直接调用,但主要用来响应某个信号。自定义一个槽函数时,需要注意以下几点:
- 槽函数的返回值必须和信号函数相同,由于信号函数的返回值一定是 void,所以槽函数的返回值也必须为 void;
- 对于带参的信号函数,槽函数可以选择接收所有参数,则参数的类型、顺序、个数都必须与信号函数相同;也可以选择接收前几个参数,这些参数的类型、顺序都必须与信号函数相同;还可以选择不接受任何参数。
- 槽函数的参数个数只能比信号函数少,不能比信号函数多;
- 槽函数的参数不能有默认值。
举个例子,自定义响应 MySignal 信号的槽函数:
class MyWidget:public QWidget{ //Q_OBJECT 是一个宏,添加它才能正常使用 Qt 的信号和槽机制 Q_OBJECT signals: void MySignal(QString mess1,QString mess2); public: void emitSignal(){ emit MySignal(message1,message2); } //类的成员函数 void recSlot1(QString mess){ qDebug() << "执行 recSlot1() 成员函数,输出" << mess; } //指明定义的是槽函数 public slots: void recSlot2(QString mess1,QString mess2){ qDebug() << "执行 recSlot2() 槽函数,输出"<< mess1 << " " << mess2; } public: QString message1; QString message2; }; //全局函数 void recSlot3(){ qDebug() << "执行 recSlot3() 全局函数"; }程序中,重点关注 recSlot1()、recSlot2()、recSlot3() 这 3 个函数:
- recSlot1() 是 MyWidget 类内部的 public 成员函数,可以当做槽函数使用;
-
recSlot2() 位于 MyWidget 类的内部,修饰它的关键字是
public slots
。slots 和 emit 一样,是 Qt 扩展的一个关键字,专门用来修饰槽函数。也就是说,recSlot2() 是 MyWidget 类中的槽函数。 - recSlot3() 是全局函数,可以当做槽函数使用。
slots 关键字可以和 public、protected、private 搭配使用,它们的区别是:
- public slots:该关键字修饰的槽函数,既可以在当前类及其子类的成员函数中调用,也可以在类外部的其它函数(比如 main() 函数)中调用;
- protected slots:该关键字修饰的槽函数,仅允许在当前类及其子类的成员函数内调用,不能在类外部的其它函数内调用;
- private slots:该关键字修饰的槽函数,只允许在当前类的成员函数内调用,不能在子类中调用,也不能在类外部的其它函数内调用。
通常情况下,槽函数使用 public slots 修饰。
调用 connect() 函数,将 MySignal() 信号函数分别连接 recSlot1()、recSlot2()、recSlot3() 三个槽函数,实现代码为:很多读者会问,既然 public 修饰的成员函数可以当做槽函数,为什么还要提供 slots 关键字呢?笔者认为,“兼容旧的 Qt 版本”是其中的一个原因。Qt4 中的槽函数只能是 slots 修饰的类成员函数,Qt5 中取消了这一限制,但考虑到要兼容旧的 Qt 版本,Qt5 保留了旧版本中 connect() 函数的语法格式,也保留了 slots 关键字。
//类的成员函数作为槽函数 QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot1); //信号函数和槽函数相连接 QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot2); //全局函数作为槽函数 QObject::connect(&mywidget,&MyWidget::MySignal,&recSlot3);
自定义信号和槽的完整实例
//main.cpp #include <QApplication> #include <QWidget> #include <QDebug> class MyWidget:public QWidget{ //Q_OBJECT 是一个宏,添加它才能正常使用 Qt 的信号和槽机制 Q_OBJECT //信号函数 signals: void MySignal(QString mess1,QString mess2); public: //发射信号的函数 void emitSignal(){ emit MySignal(message1,message2); } //普通类成员函数 void recSlot1(QString mess){ qDebug() << "执行 recSlot1() 成员函数,输出" << mess; } //槽函数 public slots: void recSlot2(QString mess1,QString mess2){ qDebug() << "执行 recSlot2() 槽函数,输出"<< mess1 << " " << mess2; } public: QString message1; QString message2; }; //全局函数 void recSlot3(){ qDebug() << "执行 recSlot3() 全局函数"; } int main(int argc, char *argv[]) { QApplication a(argc, argv); //创建主窗口 MyWidget mywidget; mywidget.message1 = "C语言中文网"; mywidget.message2 = "http://c.biancheng.net"; //类的成员函数作为槽函数 QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot1); //信号函数和槽函数相连接 QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot2); //全局函数作为槽函数 QObject::connect(&mywidget,&MyWidget::MySignal,&recSlot3); mywidget.show(); //发射 Signal 信号 mywidget.emitSignal(); return a.exec(); } //MyWidget类的定义应该放到 .h 文件中,本例中将其写到 main.cpp 中,程序最后需要添加 #include "当前源文件名.moc" 语句,否则无法通过编译。 #include "main.moc"执行程序,会弹出一个 myWidget 空白窗口,同时输出以下信息:
执行 recSlot1() 成员函数,输出 "C语言中文网"
执行 recSlot2() 槽函数,输出 "C语言中文网" "http://c.biancheng.net"
执行 recSlot3() 全局函数