• QT_2-信号和槽


    信号和槽

    信号的槽机制:

    信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现,并不是 GoF 经典的观察者模式的实现方式。)

    connect()函数最常用的一般形式:

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

    注意

    信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。

    Qt5 的书写方式

    #include <QApplication>
    #include <QPushButton>
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
       	QPushButton button("Quit");
    QObject::connect(&button,&QPushButton::clicked,
    			&app,&QApplication::quit);
    			
        button.show();
        return app.exec();
    }
    

    Qt4的书写方式

    int main(int argc, char *argv[]) { 
            QApplication a(argc, argv); 
            QPushButton *button = new QPushButton("Quit"); 
            connect(button, SIGNAL(clicked()), &a, SLOT(quit())); 
            button->show(); 
            return a.exec(); 
    }
    

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

    自定义槽函数:

    例: 一个切换窗口的程序的代码(QT4 语法):

    包含文件:
    main.cpp
    widget.h //主窗口类
    widget.cpp 
    subwindow.h //子窗口类
    subwindow.cpp
    

    main.cpp

    int main(int argc, char *argv[]) {
        QApplication a(argc, argv);
        Widget w; //创建一个Widget窗口对象
        w.show(); //显示主窗口
        return a.exec();
    }
    

    widget.cpp

    #ifndef WIDGET_H
    #define WIDGET_H
    #include <QWidget>
    #include <QPushButton>
    #include <QString>
    #include <QDebug>
    #include "subwindow.h"
    class Widget : public QWidget {
        Q_OBJECT
    public:
        //explicit Widget(QWidget *parent = 0); //不能隐式调用其构造函数
        Widget(QWidget *parent = 0);
        ~Widget();
    signals:
    public slots:
        void encrase(); //增大窗口大小的槽函数
        void changeWindow(); //改变窗口的槽函数
        void dealSub(); //处理子窗口发来信号的槽函数
    private:
        subWindow sbw; //子窗口
    };
    #endif // WIDGET_H
    
    
    subwindow.h
    #ifndef SUBWINDOW_H
    #define SUBWINDOW_H
    #include <QWidget>
    #include <QPushButton>
    #include <QDebug>
    class subWindow : public QWidget {
        Q_OBJECT
    public:
        explicit subWindow(QWidget *parent = 0);
    
    signals:
        void S_changeWindow(); //发给主窗口的信号
    public slots:
        void BChangMain(); //发信号的槽函数
    private:
        QPushButton b;
    };
    #endif // SUBWINDOW_H
    
    
    subwindow.cpp
    subWindow::subWindow(QWidget *parent) : QWidget(parent) {
        this->setWindowTitle("SubWindow");
        b.setParent(this);
        b.setText("Change to main window");
        this->resize(400, 200);
        connect(&b, SIGNAL(clicked()), this, SLOT(BChangMain()));
    	
    }
    
    void subWindow::BChangMain() {
        emit S_changeWindow(); //发信号
    	//emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)
    }
    
    

    注意:

    1. 只有继承了QObject类的类,才具有信号槽的能力
    2. 凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT (不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力)
    3. 信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现。
    4. Qt 5 中,任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
    5. 操函数与信号函数不同,槽函数必须自己完成实现代码。槽函数就是普通的成员函数,因此作为成员函数,也会受到 public、private 等访问控制符的影响。(如果信号是 private 的,这个信号就不能在类的外面连接,也就没有任何意义。)

    信号或槽重载问题:

    在qt4语法上就可以轻松解决,在使用connect的时候就可以确定要使用哪个信号或者槽,参数已经定死.而qt5语法的connect里却没有.
    解决方案: 使用函数指针

    例:

    #include "widget.h"
    Widget::Widget(QWidget *parent)
        : QWidget(parent) {
        this->t_1 = new QTextEdit(this);
        t_1->setText("Hello");
        t_1->move(50,50);
        w2.show();
        this->resize(400, 200);
        this->setWindowTitle("MainWindow");
    	//使用函数指针来选着哪个重载的函数
    	 //信号指定为win2::MySingal无参数的 
        void (win2::*S_1)() = &win2::MySingal;
    	 //槽函数指定为Widget::dealWith 无参数的 
        void (Widget::*D_1)() = &Widget::dealWith;
        connect(&w2, S_1, this, D_1); //连接
    	 //信号指定为win2::MySingal参数为QString类和int类型的 
        void (win2::*S_2)(QString, int) = &win2::MySingal;
    	 //槽函数指定为Widget::dealWith参数为QString类和int类型的 
        void (Widget::*D_2)(QString, int) = &Widget::dealWith;
        connect(&w2, S_2, this, D_2);
    }
    Widget::~Widget(){
    }
    void Widget::dealWith() {
        qDebug() << "Send a msg";
    }
    void Widget::dealWith(QString a, int b) {
        this->t_1->setText(a);
        qDebug() << a.toUtf8().data(); //From QString to C String
    }
    
    

    Lambda表达式

    C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
    enter description here

    函数对象参数mutable或exception ->返回值{函数体}

    ①函数对象参数:

    [],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:

    1. 空 : 没有使用任何函数对象参数
    2. = :函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)
    3. & : 函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)
    4. this。函数体内可以使用Lambda所在类中的成员变量
    5. a :将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符
    6. &a : 将a按引用进行传递
    7. a, &b : 将a按值进行传递,b按引用进行传递
    8. =,&a, &b : 除a和b按引用进行传递外,其他参数都按值进行传递
    9. &, a, b : 除a和b按值进行传递外,其他参数都按引用进行传递

    ② 操作符重载函数参数;

    标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。

    ③ 可修改标示符;

    mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。

    ④ 错误抛出标示符;

    exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)

    ⑤ 函数返回值;

    ->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。

    ⑥ 是函数体;

    {},标识函数的实现,这部分不能省略,但函数体可以为空

    例: 使用Lambda 实现1 + 2 + .... + 100 的程序:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    int main(void) {
            std::vector<int> num_list;
            int total = 0;
            for(int i = 1; i <= 100; ++i) num_list.push_back(i);
            std::for_each(begin(num_list), end(num_list), [&total](int x) {
                    total +=x;
            });
            std::cout << "total = " << total;
    }
    

    例: 使用Lambda表达式代替槽函数:

    功能: 点击按钮是打印hello
    包含文件:
    Lambda.pro //需要添加c++11特性
    widget.cpp
    widget.h
    

    Lambda.pro文件:

    QT       += core gui
    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    TARGET = 5_Lambda
    TEMPLATE = app
    SOURCES += main.cpp
            widget.cpp
    HEADERS  += widget.h
    CONFIG += C++11 //添加c++ 11语法格式
    

    widget.h文件:

    #ifndef WIDGET_H
    #define WIDGET_H
    #include <QWidget>
    #include <QPushButton>
    #include <QDebug>
    class Widget : public QWidget {
        Q_OBJECT
    
    public:
        Widget(QWidget *parent = 0);
        ~Widget();
    };
    #endif // WIDGET_H
    

    widget.cpp文件:

    #include "widget.h"
    Widget::Widget(QWidget *parent)
        : QWidget(parent) {
        //Lambda formular
        //c++11 new feature,it is very convenient to use
        //Add 'CONFIG += C++11' to the project file
        QPushButton *b = new QPushButton(this);
        this->resize(400,200);
        b->setText("Lambda");
        b->move(50,50);
        //'[]' : Square brackets indicate passing in external variables
        // You can use it:
        //[a, b, c....] Passes the specified variable  (read only)
        //mutable  : Allows incoming variables to be modified
        //[=]  Pass all variables
        //[&]  Pass in all local variables that are external, but it's easy to make mistakes
        //[this] Pass the this pointer from the class
        //() To take the parameters of the signal function
        int a = 4;
        connect(b, &QPushButton::clicked,
                [a](bool isCheck) mutable  { 
                a = 5;
                qDebug() << "HELLO " << a << isCheck;
                });
    }
    Widget::~Widget() {
    }
    

    总结

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

    信号槽的更多用法:

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

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

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

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

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

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

    槽可以被取消连接

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

    使用Lambda 表达式

    能够支持 Qt 5 的编译器都是支持 Lambda 表达式的, 这样的话就可以不用定义槽函数,十分方便。

  • 相关阅读:
  • 原文地址:https://www.cnblogs.com/lyxf/p/12263766.html
  • 最新文章
  • 热门文章
一二三 - 开发者的网上家园