您当前的位置:首页 > 计算机 > 编程开发 > Qt

Qt自定义信号和槽函数

时间:01-22来源:作者:点击数:

实际开发中,如果仅使用 Qt 提供的信号函数和槽函数,会经常遇到信号函数的参数类型和个数无法满足实际需求、信号函数和槽函数的参数类型不匹配等问题。解决此类问题,最简单有效的方式就是:自定义场景需要的信号函数和槽函数。

自定义信号函数

信号函数指的是符合以下条件的函数:

  • 定义在某个类中,该类直接或间接继承自 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 修饰。

很多读者会问,既然 public 修饰的成员函数可以当做槽函数,为什么还要提供 slots 关键字呢?笔者认为,“兼容旧的 Qt 版本”是其中的一个原因。Qt4 中的槽函数只能是 slots 修饰的类成员函数,Qt5 中取消了这一限制,但考虑到要兼容旧的 Qt 版本,Qt5 保留了旧版本中 connect() 函数的语法格式,也保留了 slots 关键字。

调用 connect() 函数,将 MySignal() 信号函数分别连接 recSlot1()、recSlot2()、recSlot3() 三个槽函数,实现代码为:

//类的成员函数作为槽函数
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 = "城东书院";
    mywidget.message2 = "https://www.cdsy.xyz";
    //类的成员函数作为槽函数
    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() 成员函数,输出 "城东书院"
执行 recSlot2() 槽函数,输出 "城东书院"   "https://www.cdsy.xyz"
执行 recSlot3() 全局函数
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门