• 【Qt基础篇】信号和槽


    一些常见的bug:

    字符集不对产生的错误

    image-20231003112340386

    该bug的产生原因:字符集不对的原因

    解决办法:记事本打开该文件 => 另存为 => 编码改为带有BOM 的 UTF-8 (更改该文件的编码格式)

    image-20231003112404027


    VS平台中文乱码

    乱码原因:QT使用的utf-8编码进行字符串处理,VS使用的是GB2312 ,二者编码格式不匹配,所以中文就会乱码

    解决办法1:在字符串加上u8

    QWidget w;
    w.setWindowTitle(u8"windows 芒果");
    w.show();
    
    • 1
    • 2
    • 3

    解决办法2:按照上面字符集不对产生的错误解决之后 => 项目=>命令行增加一个选项 /utf-8

    image-20231003112817430

    解决办法3:安装拓展

    image-20231003112904558

    作用:自动将文件编码格式保存为utf-8。而如果改为编写C语言文件的时候,需要禁用该拓展

    image-20231003112931656


    方法4:在头文件当中增加

    #pragma execution_character_set("utf-8")
    
    • 1

    QT的优点

    • 跨平台,几乎支持所有的平台
    • 接口简单,容易上手,学习QT框架对学习其他框架有参考意义。
    • 一定程度上简化了内存回收机制
    • 开发效率高,能够快速的构建应用程序
    • 有很好的社区氛围,市场份额在缓慢上升
    • 可以进行嵌入式开发

    关于.pro文件

    .pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件

    QT       += core gui   Qt包含的模块
    
    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets  //大于4版本以上 包含 widget模块
    
    TARGET = 01_FirstProject  //目标   生成的.exe程序的名称
    TEMPLATE = app       	  //模板   应用程序模板  Application  
    
    
    SOURCES += main.cpp\      //源文件
            mywidget.cpp
    
    HEADERS  += mywidget.h    //头文件   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    解释:

    【1】.注释:从“#”开始,到这一行结束

    【2】.模板变量:告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:TEMPLATE = app

    • app -建立一个应用程序的makefile,这是默认值,所以如果模板没有被指定,这个将被使用
    • lib - 建立一个库的makefile
    • vcapp - 建立一个应用程序的VisualStudio项目文件
    • vclib - 建立一个库的VisualStudio项目文件
    • subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile

    【3】.指定生成的应用程序名:TARGET = QtDemo

    【4】.工程中包含的头文件:HEADERS += include/painter.h

    【5】.工程中包含的.ui设计文件:FORMS += forms/painter.ui

    【6】.工程中包含的源文件:SOURCES += sources/main.cpp sources

    【7】.工程中包含的资源文件:RESOURCES += qrc/painter.qrc

    【8】.greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

    这条语句的含义是,如果QT_MAJOR_VERSION大于4(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写


    QtCreator快捷键

    注释  ctrl + /
    运行  ctrl + r
    编译  ctrl + b
    字体缩放 ctrl + 鼠标滚轮
    查找  ctrl + f
    整行移动 ctrl + shift + ↑ 或者↓
    帮助文档 F1
    自动对齐  ctrl + i;
    同名之间的.h 和 .cpp切换  F4
    帮助文档 第一种方式 F1  第二种 左侧按钮  第三种:QT安装路径下的bin目录,例如: C:\Qt\Qt5.6.0\5.6\mingw49_32\bin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    最简单的qt程序

    main函数入口

    //main程序入口  argc命令行变量的数量  argv命令行变量的数组
    int main(int argc, char *argv[])
    {
        //a应用程序对象,在Qt中,应用程序对象 有且仅有一个
        QApplication a(argc, argv);
        Widget w; //窗口对象
        w.show();//窗口对象 默认不会显示,必须要调用show方法显示窗口
    
        //让应用程序对象进入消息循环  让代码阻塞到这行
        return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    QApplication应用程序类:是Qt的整个后台管理的命脉它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理

    • 对于任何一个使用Qt的图形用户界面应用程序,都只能存在一个QApplication 对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口

    a . e x e c ( ) a.exec() a.exec()程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。


    按钮的创建

    在Qt程序中,最常用的控件之一就是按钮,一个按钮其实就是一个QPushButton类对象,其中有两种创建方式:

    • 如果只是创建出对象,是无法显示到窗口中的,所以我们需要依赖一个父窗口,也就是指定一个父亲利用setParent函数即可
    • 如果想设置按钮上显示的文字利用setText,移动按钮位置用move
    #include 
    //创建方式1:
    QPushButton * btn = new QPushButton; 
    btn->setParent(this);//设置父亲  让btn对象依赖在当前Widget窗口中
    btn->setText("德玛西亚");//设置文字
    btn->move(100,100);//移动位置
    
    //创建方式2:
    QPushButton * btn2 = new QPushButton("孙悟空",this);
    //下述的this都可以省略
    this->resize(600,400);//重新指定窗口大小
    this->setWindowTitle("第一个项目");//设置窗口标题
    this->setFixedSize(600,400);//限制窗口大小 => 不可以拖拽去扩大/缩小大小
    //重置窗口大小
    resize(600,400); //this->resize(...)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    对于窗口而言,我们可以修改左上角窗口的标题setWindowTitle,重新指定窗口大小:resize,或者设置固定的窗口大小setFixedSize;

    image-20231003100004313

    注意:如果是在Widget的构造函数当中:

    QPushButton btnStack;
    btnStack.setParent(this);
    
    • 1
    • 2

    此时该按钮并不会显示在窗口当中,因为该对象是在栈上创建的,一旦Widget的构造函数执行完成之后,该对象就会被销毁,因此它不会显示在窗口中。要使 btnStack 对象显示在窗口中,你应该将它的生命周期延长,使其在窗口的整个生命周期内都存在 =>可以选择 将btnStack声明为类的成员变量


    对象模型

    image-20231003155443912

    在Qt中创建对象的时候需要提供一个Parent对象指针,因为QObject是以对象树的形式组织起来的,当你创建一个对象时,会看到该对象的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。

    image-20231003100343298

    【1】.在创建对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类

    【2】.QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件

    • 例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件

    【3】.我们也可以自己删除子对象,它们会自动从其父对象列表中删除

    • 当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示

    Qt 引入对象树的概念,在一定程度上解决了内存问题

    • 当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的
      • 任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除
      • 如果有孩子,则自动 delete 每一个孩子
      • Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的
    • 如果QObject在栈上创建,Qt 保持同样的行为
    {
        QWidget window;
        QPushButton quit("Quit", &window);
    }
    
    • 1
    • 2
    • 3
    • 4

    作为父组件的 window 和作为子组件的 quit 都是QObject的子类,此时这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数

    错误代码

    {
        QPushButton quit("Quit");
        QWidget window;
        quit.setParent(&window);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时:1.父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了

    2.在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了

    结论:尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。当创建的对象在堆区时候,如果指定的父亲是QObject类或者父类是QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入到对象树中,一定程度上简化了内存回收机制


    Qt窗口坐标体系

    以左上角为原点(0,0),x以右为正方向,y以下为正方向

    image-20231003101103992

    如果是对于嵌套窗口,其坐标是相对于父窗口来说的


    信号和槽机制

    信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal),但是这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调


    connect函数

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

    信号槽的优点:松散耦合,信号的发送端和接收端本身没有任何关联,二者通过connect函数进行关联,将两端耦合在一起


    系统自带的信号和槽

    在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字

    image-20231003102143720

    点击signals索引到系统自带的信号有如下几个:

    image-20231003102205487

    槽函数的寻找方式和信号一样,只不过他的关键字是slot


    案例:实现点击按钮-关闭窗口的案例

    testQt::testQt(QWidget* parent): QWidget(parent)
    {
        ui.setupUi(this);
        QPushButton* btn = new QPushButton(u8"关闭窗口", this);
        btn->move(100, 200);
        resize(500, 500);
        //参数1  信号的发送者 参数2  发送的信号(函数的地址) 参数3  信号的接受者 参数4  处理的槽函数
        connect(btn, &QPushButton::clicked, this, &QWidget::close);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    自定义信号和槽

    案例:下课->老师触发饿了信号->学生响应信号请吃饭

    image-20231003153906028

    testQt.cpp

    testQt::testQt(QWidget* parent): QWidget(parent)
    {
        ui.setupUi(this);
        //1.创建老师和学生对象
        this->teacher = new Teacher(this);
        this->stu = new Student(this);
         
        //点击按钮触发下课
        QPushButton* btn = new QPushButton(u8"下课", this);
        btn->move(200, 200);
        btn->resize(100, 100);
    
    
        //2.0:无参 信号和槽函数连接 =>假设没有重载版本时的调用情况=>只有treat()和hungry()
        /*
            connect(teacher, SIGNAL(hungry()), stu, SLOT(treat())); //QT4写法
            connect(teacher, &Teacher::hungry, stu, &Student::treat);   
        */
    
        //2.1:无参 信号和槽函数连接 =>有重载版本时的调用情况
        /*
            void(Teacher:: * teacherSignal)() = &Teacher::hungry;
            void(Student:: * studentSlot)() = &Student::treat;
            connect(teacher, teacherSignal, stu, studentSlot);
        
            connect(btn, &QPushButton::clicked, teacher, teacherSignal); //信号可以连接信号 => 当点击事件发生=>触发teacherSignal信号=>触发studentSlot槽函数
        */
    
    
        //2.2:有参 信号和槽函数连接 =>有重载版本时的调用情况
        /*
            void(Teacher:: * teacherSignal)(QString) = &Teacher::hungry;
            void(Student:: * studentSlot)(QString) = &Student::treat;
            connect(teacher, teacherSignal, stu, studentSlot);
            
            //connect(btn, &QPushButton::clicked, teacher, teacherSignal); //err,原因:信号和槽的参数类型不匹配
                //clicked的第一个参数为bool teacherSignal函数的参数为QString,信号和槽函数的参数 必须类型一一对应
        */
    
    
        //3.调用下课函数
        ClassOver();
    }
    
    void testQt::ClassOver()
    {
        //emit teacher->hungry();//触发老师饿了信号 ->无参版本
        emit teacher->hungry(u8"宫保鸡丁"); //触发老师饿了信号 ->带参版本
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    案例:使用lambda表达式实现点击按钮关闭窗口功能

     QPushButton* btn = new QPushButton(u8"close Widget", this);
        btn->move(200, 200);
        btn->resize(100, 100);
    
        connect(btn, &QPushButton::clicked,[=]() {
            qDebug() << u8"关闭窗口";
            close(); //this->close(); 关闭窗口
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:如果第三个参数是this,第四个参数是lambda表达式,那么第三个参数可以省略


    注意:

    【1】.如果有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来明确指向函数地址, 然后再做连接

    connect(teacher,&Teacher::hungury,student,&Student::treat); //void hungury()和void treat()连接
    
    //自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽
    //void hungury(QString name);  自定义信号
    //void treat(QString name );    自定义槽
    void (Teacher:: *teacherSingal)(QString) = &Teacher::hungury;
    void (Student:: * studentSlot)(QString) = &Student::treat;
    connect(teacher,teacherSingal,student,studentSlot);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    补充:关于函数指针指向的是类当中的成员函数:

    class A
    {
    public:
    	void func()
    	{
    		cout << "A::func()" << endl;
    	}
    };
    void (A::*PFunc)() = &A::func;
    int main()
    {
    	A obj;
    	(obj.*PFunc)();//成员函数需要一个对象实例来调用
        /*
            obj:这是一个已经创建的类A的对象实例。成员函数需要一个对象实例来操作类的数据成员和执行操作。
            .*:这是成员函数指针运算符。它用于将成员函数指针与对象实例关联在一起,以便调用成员函数。
            PFunc:这是一个指向成员函数的指针,它保存了要调用的成员函数的地址。在这种情况下,它指向类A的func()成员函数。
            ():这是函数调用运算符,用于调用函数。在这里,我们使用它来调用与对象实例obj关联的成员函数PFunc指向的成员函数。
         */
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    【2】发送者和接收者都需要是QObject的子类

    • ​ 例外:槽函数是全局函数、Lambda 表达式等无需接收者

    【3】信号和槽函数返回值是 void信号只需要声明,不需要实现槽函数需要声明也需要实现。信号和槽函数都可以进行重载

    • 自定义信号写到$signals$当中,自定义槽函数写到public slot当中或者public函数或者全局函数

    【4】槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响

    【5】使用 emit 在恰当的位置发送信号使用connect()函数连接信号和槽

    【6】任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数

    【7】信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果信号和槽的参数不一致,允许的情况是:槽函数的参数可以比信号的少,但是此时槽函数存在的那些参数的顺序也必须和信号的前面几个一致,因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)


    信号槽的扩展

    【1】一个信号可以和多个槽相连

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

    【2】多个信号可以连接到一个槽,只要任意一个信号发出,这个槽就会被调用

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

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

    【4】信号槽可以被取消链接,可以disconnect断开

    • 这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽
    disconnect(teacher,teacherSignal2,stu,studentSlot2); //断开信号  和 信号连接的格式一致
    
    • 1

    【5】l 使用Lambda 表达式

    在使用QT5 的时候,能够支持QT5的编译器都是支持 Lambda 表达式的。在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理


    QT4版本的信号槽写法

    connect(teacher,teacherSingal,student,studentSlot); //QT5  teacherSingal和studentSlot是函数指针
    connect(teacher,SIGNAL(hungry(QString)),student,SLOT(treat(QString))); //QT4版本的写法
    
    //connect( 信号的发送者,发送的信号SIGNAL(信号),信号接受者,槽函数SLOT(槽函数))
    
    • 1
    • 2
    • 3
    • 4

    优点:参数直观

    缺点:编译器不会检测参数类型

    使用了SIGNAL和SLOT这两个宏,作用是:将两个函数名转换成了字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的,而是在运行时给出错误。这无疑会增加程序的不稳定性


    总结

    image-20231003155855925

  • 相关阅读:
    使用k8s创建一个支持ssh的pod,docker
    推荐一款es轻量级的压测工具
    题解0014:信奥一本通1472——The XOR Largest Pair(字典树)
    打造智能物品租赁平台:Java与SpringBoot的实践
    python下celery的基本使用
    1.5、Python基础-模块和包
    微服务平滑迁移上云最佳实践
    一文让你搞懂MYSQL底层原理。-内部结构、索引、锁、集群
    C++ 如何根据地理坐标范围获取瓦片地图并使用CImage库实现多张图片(瓦片地图)的快速合并
    【vue基础】v-if和v-show的区别;v-for更新检测($set强制更新);计算属性的get与set方法;侦听器与计算属性区别;
  • 原文地址:https://blog.csdn.net/chuxinchangcun/article/details/133607323