Qt 之 信号和槽

信号和槽

解释

当某个控件或某个事件发生时,系统中“关心”该信号的模块,执行一个Action,来响应该Signal。

机制

类似于设计模式:观察者模式。 信号发出类似于广播,通过广播的方式传递出去。 遍历“链”上的内容,如果对此信号感兴趣,就响应该消息。

signals 关键字

  • 信号必须有signals关键字来声明
  • 信号没有返回值,但可以有参数
  • 信号就是函数的声明,只需声明,无需定义
  • 使用:emit mySignal();
  • 信号可以重载
signals:
    void mySignal();
    void mySignal(int, QString);

简单示例(Qt5 语法)

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);	
    
    QPushButton button("Quit");  //定义一个Button 控件
    
    QObject::connect(&button, &QPushButton::clicked, &app, &QApplication::quit); //信号和槽绑定
    
    button.show(); //显示button控件
    
    return app.exec();
}

信号和槽的第5个参数

Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

Signal和Slot一般形式

connect(sender, signal, receiver, slot);
  • sender:发出信号的对象
  • signal:发送对象发出的信号
  • receiver:接收信号的对象
  • slot:接收对象在接收到信号之后所需要调用的函数

Demo

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
#include "subwidget.h" //子窗口头文件

class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();

public slots:								//槽函数定义
    void mySlot();
    void changeWin();
    void dealSub();
    void dealSlot(int, QString);

private:
    QPushButton b1;
    QPushButton *b2;
    QPushButton b3;

    SubWidget subWin;
};

#endif // MAINWIDGET_H


#ifndef SUBWIDGET_H
#define SUBWIDGET_H

#include <QWidget>
#include <QPushButton>

class SubWidget : public QWidget
{
    Q_OBJECT
public:
    explicit SubWidget(QWidget *parent = 0);

    void sendSlot();

signals:
     /* 信号必须有signals关键字来声明
      * 信号没有返回值,但可以有参数
      * 信号就是函数的声明,只需声明,无需定义
      * 使用:emit mySignal();
      * 信号可以重载
     */

    void mySignal();
    void mySignal(int, QString);

public slots:

private:
    QPushButton b;
};

#endif // SUBWIDGET_H
#include "mainwidget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWidget w;//执行MainWidget的构造函数
    w.show();

    return a.exec();
}

#include "mainwidget.h"
#include <QPushButton>
#include <QDebug> //打印

MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
    b1.setParent(this);
    b1.setText("close");
    b1.move(100, 100);

    b2 = new QPushButton(this);
    b2->setText("abc");

    connect(&b1, &QPushButton::pressed, this, &MainWidget::close);
    /* &b1: 信号发出者,指针类型
     * &QPushButton::pressed:处理的信号,  &发送者的类名::信号名字
     * this: 信号接收者
     * &MainWidget::close: 槽函数,信号处理函数  &接收的类名::槽函数名字
    */

    /* 自定义槽,普通函数的用法
     * Qt5:任意的成员函数,普通全局函数,静态函数
     * 槽函数需要和信号一致(参数,返回值)
     * 由于信号都是没有返回值,所以,槽函数一定没有返回值
     */
    connect(b2, &QPushButton::released, this, &MainWidget::mySlot);

    connect(b2, &QPushButton::released, &b1, &QPushButton::hide);

    /* 信号:短信
     * 槽函数:接收短信的手机
     */

    setWindowTitle("老大");
    //this->setWindowTitle("老大");

    b3.setParent(this);
    b3.setText("切换到子窗口");
    b3.move(50, 50);

    //显示子窗口
    //subWin.show();

    connect(&b3, &QPushButton::released, this, &MainWidget::changeWin);


    //处理子窗口的信号
//    void (SubWidget::*funSignal)() = &SubWidget::mySignal;
//    connect(&subWin, funSignal, this, &MainWidget::dealSub);

//     void (SubWidget::*testSignal)(int, QString) = &SubWidget::mySignal;
//    connect(&subWin, testSignal, this, &MainWidget::dealSlot);

    //Qt4信号连接
    //Qt4槽函数必须有slots关键字来修饰
    connect(&subWin, SIGNAL(mySignal()), this, SLOT(dealSub()) );

    connect(&subWin, SIGNAL(mySignal(int,QString)),
            this, SLOT(dealSlot(int,QString)) );
    // SIGNAL SLOT 将函数名字 -> 字符串  不进行错误检查

    //Lambda表达式, 匿名函数对象
    //C++11增加的新特性, 项目文件: CONFIG += C++11
    //Qt配合信号一起使用,非常方便

    QPushButton *b4 = new QPushButton(this);
    b4->setText("Lambda表达式");
    b4->move(150, 150);
    //int a = 10, b = 100;
    connect(b4, &QPushButton::clicked,
            // = :把外部所有局部变量、类中所有成员以值传递方式
            // this: 类中所有成员以值传递方式
            // & : 把外部所有局部变量, 引用符号
            [=](bool isCheck)
            {
                qDebug() << isCheck;
            }


            );


    resize(400, 300);
}

void MainWidget::dealSlot(int a, QString str)
{
    // str.toUtf8() -> 字节数组QByteArray
    // ……data()  -> QByteArray -> char *
    qDebug() << a << str.toUtf8().data();
}

void MainWidget::mySlot()
{
    b2->setText("123");
}

void MainWidget::changeWin()
{
    //子窗口显示
    subWin.show();
    //本窗口隐藏
    this->hide();
}


void MainWidget::dealSub()
{
    //子窗口隐藏
    subWin.hide();
    //本窗口显示
    show();
}

MainWidget::~MainWidget()
{

}

#include "subwidget.h"

SubWidget::SubWidget(QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle("小弟");
    b.setParent(this);
    b.setText("切换到主窗口");

    connect(&b, &QPushButton::clicked, this, &SubWidget::sendSlot);

    resize(400, 300);
}

void SubWidget::sendSlot()
{
    emit mySignal();						//释放信号量
    emit mySignal(250, "我是子窗口");		//信号量函数重载
}


注意

  1. 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
  2. 允许槽函数的参数可以比信号的少,槽函数存在的参数的顺序也必须和信号的前面几个一致。

Qt4 语法的不稳定性

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);	
    
    QPushButton *button = new QPushButton("Quit"); 
    
    connect(button, SIGNAL(clicked()), &a, SLOT(quit())); //SINGAL 和 SLOT宏, connect函数接受字符串
    
    button.show(); //显示button控件
    
    return app.exec();
}

  • SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。
  • 注意到connect()函数的 signal 和 slot 都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。

自定义信号和槽

有一个报纸类Newspaper,有一个订阅者类Subscriber。Subscriber可以订阅Newspaper。这样,当Newspaper有了新的内容的时候,Subscriber可以立即得到通知。

  • NewPaper类
#include <QObject>
 // newspaper.h //
class Newspaper : public QObject   //QObject类才具有信号和槽功能
{
    Q_OBJECT		//凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT
public:
    Newspaper(const QString & name) :m_name(name)			//构造及初始化函数
    {
    }
 
    void send()												//发送信号函数
    {
        emit newPaper(m_name);
    }
 
signals:													//信号关键字
    void newPaper(const QString &name);					   
 
private:
    QString m_name;
};
  • reader 类

// reader.h //
#include <QObject>
#include <QDebug>
 
class Reader : public QObject
{
    Q_OBJECT
public:
    Reader() {}
 
    void receiveNewspaper(const QString & name)
    {
        qDebug() << "Receives Newspaper: " << name;
    }
};

  • test
// main.cpp //
#include <QCoreApplication>
 
#include "newspaper.h"
#include "reader.h"
 
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
 
    Newspaper newspaper("Newspaper A");		//报纸对象 
    Reader reader;							//读者
    
    QObject::connect(&newspaper, &Newspaper::newPaper,
                     &reader,    &Reader::receiveNewspaper);
    newspaper.send();
 
    return app.exec();
}

Signal函数定义

  • 信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值)。
  • 参数是该类需要让外界知道的数据(即槽接收到的数据)。
  • 信号作为函数名,不需要在 cpp 函数中添加任何实现。

emit 关键字

emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出,也就是发出newPaper()信号。

数据从发送者到接收者的转移

emit 发出newPaper()信号。感兴趣的接收者会关注这个信号,可能还需要知道是哪份报纸发出的信号?所以,我们将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的一个转移。

Slot函数

哪些函数可以作为槽函数

以下 函数都可以作为槽函数。

  • 成员函数
  • static 函数
  • 全局函数
  • Lambda 表达式
  1. 与信号函数不同,槽函数必须自己完成实现代码。
  2. 槽函数就是普通的成员函数,因此作为成员函数,也会受到 public、private 等访问控制符的影响。(如果信号是 private 的,这个信号就不能在类的外面连接,也就没有任何意义。)

自定义信号和槽函数注意事项

1.发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
2.使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
3.槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
4.使用 emit 在恰当的位置发送信号;
5.使用QObject::connect()函数连接信号和槽。
6.任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数

信号和槽的更多用法

一个信号可以和多个槽相连

如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的

多个信号可以连接到一个槽

只要任意一个信号发出,这个槽就会被调用。

一个信号可以连接到另外的一个信号

当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。

槽可以被取消链接

这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。

信号和槽中使用Lambda 表达式

在使用 Qt 5 的时候,能够支持 Qt 5 的编译器都是支持 Lambda 表达式的。
我们的代码可以写成下面这样:

QObject::connect(&newspaper, static_cast<void (Newspaper:: *)
               (const QString &)>(&Newspaper::newPaper),
               [=](const QString &name) 
               { 
                   /* Your code here. */ 
               }
);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值