QT入门

programmer 程式師 程序员
Post Reply
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

四、信号与槽机制

信号:各种事件

槽: 响应信号的动作

当某个事件发生后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal)。

某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)。

但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect)
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.1 系统自带的信号和槽

下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?

其实两行代码就可以搞定了,我们看下面的代码

Code: Select all

    QPushButton * quitBtn = new QPushButton("关闭窗口",this);
      connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式

connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:

Code: Select all

        connect(sender, signal, receiver, slot);
参数解释:

1)sender:信号发送者

2)signal:信号

3)receiver:信号接收者

4)slot:接收对象在接收到信号之后所需要调用的函数(槽函数)

这里要注意的是connect的四个参数都是指针,信号和槽是函数指针。

系统自带的信号和槽如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个

Image

这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.2 自定义信号和槽

Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.2.1自定义信号使用条件

声明在类的signals域下
没有返回值,void类型的函数
只有函数声明,没有定义
可以有参数,可以重载
通过emit关键字来触发信号,形式:emit object->sig(参数);

4.2.2 自定义槽函数使用条件

qt4 必须声明在 private/public/protected slots域下面,qt5之后可以声明public下,同时还可以是静态的成员函数,全局函数,lambda表达式
没有返回值,void类型的函数
不仅有声明,还得要有实现
可以有参数,可以重载
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.2.3 使用自定义信号和槽

定义场景:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)

首先定义一个学生类和老师类:

老师类中声明信号 饿了 hungry

Code: Select all

    signals:
           void hungry();
学生类中声明槽 请客treat

Code: Select all

    public slots:
           void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客

Code: Select all

    	void MyWidget::ClassIsOver()
    {
        //发送信号
        emit teacher->hungry();
    }
学生响应了槽函数,并且打印信息

Code: Select all

        //自定义槽函数 实现

    void Student::treat()
    {
           qDebug() << "Student treat teacher";
    }
在窗口中连接信号槽

Code: Select all

     teacher = new Teacher(this);
        student = new Student(this);
        connect(teacher,&Teacher::hungury,student,&Student::treat);
并且调用下课函数,测试打印出相应log

自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽

Code: Select all

    void hungry(QString name);  自定义信号
    void treat(QString name );    自定义槽
但是由于有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来指向函数地址, 然后在做连接

Code: Select all

    void (Teacher:: * teacherSingal)(QString) = &Teacher:: hangry;
    void (Student:: * studentSlot)(QString) = &Student::treat;
    connect(teacher,teacherSingal,student,studentSlot);
也可以使用static_cast静态转换挑选我们要的函数

Code: Select all

    connect(
    teacher,
    static_cast<void(Teacher:: *)(QString)>(&Teacher:: hangry),
    student,
    static_cast<void(Student:: *)(QString)>(& Student::treat));
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.3 信号和槽的扩展

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

如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。像上面的例子,可以将一个按钮点击信号连接到关闭窗口的槽函数,同时也连接到学生请吃饭的槽函数,点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。

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

只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。

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

当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。

Code: Select all

    connect(btn,&QPushButton::clicked,teacher,&Teacher::hungry);
4.信号和槽可以断开连接

可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。

5.信号和槽函数参数类型和个数必须同时满足两个条件

1)信号函数的参数个数必须大于等于槽函数的参数个数

2)信号函数的参数类型和槽函数的参数类型必须一一对应
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.4 Qt4版本的信号槽写法

Code: Select all

    connect(
        teacher,
        SIGNAL(hungry(QString)),
        student,
        SLOT(treat(QString))
    );
这里使用了SIGNAL和SLOT这两个宏,宏的参数是信号函数和槽函数的函数原型。

因为直接填入了函数原型,所有这里边编译不会出现因为重载导致的函数指针二义性的问题。但问题是如果函数原型填错了,或者不符合信号槽传参个数类型约定,编译期间也不会报错,只有运行期间才会看到错误log输出。

原因就是这两个宏将后边参数(函数原型)转化成了字符串。目前编译器还没有那么智能去判断字符串里边的内容符不符合运行条件。
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.5 Lambda 表达式

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

分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}

Code: Select all

    [capture](parameters) opt ->retType
    {
        ……;
    }
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.5.1 局部变量引入方式

[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体A里边,所以lambda表达式有可能会去访问A函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:

[]

表示lambda表达式不能访问外部函数体的任何局部变量

[a]

在函数体内部使用值传递的方式访问a变量

[&b]

在函数体内部使用引用传递的方式访问b变量

[=]

函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本

[&]

引用的方式使用lambda表达式外部的所有变量

[=, &foo]

foo使用引用方式, 其余是值传递的方式

[&,foo]

foo使用值传递方式, 其余是引用传递的方式

[this]

在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入

由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。

所以在无特殊情况下建议使用[=](){}的形式
擎天殿
test ranks
Posts: 132
Joined: 2025-02-21T13:46:27+00:00

Re: QT入门

Post by 擎天殿 »

4.5.2 函数参数

(params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
Post Reply

Return to “programmer 程式師 程序员”