标签:
Qt学习(17)——自定义信号和槽
本节首先介绍一下C++编程中常用的传递数据机制,包括类对象的公有成员变量、友元类/函数、公有函数、回调函数等等,这些机制在Qt程序中也是可以使用的。然后重点介绍如何在Qt类里面自定义信号和槽,通过手动触发信号来调用槽函数,完成两个对象之间的消息传递,本节最后示范一个信号接力触发的例子。本节内容较多,分三部分来学习。
1、C++的沟通方式
C++编程中常遇到各个对象之间进行沟通的情景,需要将数据从一个对象传递给另一个对象来处理。大致的方法有如下几种:
这些方式都是基于标准C++的,在Qt中都可以使用,但由于Qt有更好的信号和槽机制,因此一般更推荐使用信号和槽机制实现通信。下面对C++常见的传递数据方式依次举例示范,主要是让读者大致了解一下传递数据的过程。
在本小节的示例中是多个独立的cpp文件,可以通过“Hello Word”一节中使用命令行方式进行编译运行,这几个cpp文件都放到E:\QtProjects\ch04\cppcom文件夹内。
首先是公有成员变量的例子,publicmember.cpp代码:
//publicmember.cpp #include <iostream> using namespace std; //接收端类 class Dst { public: double m_dblValue; int m_nCount; //处理数据函数 double DoSomething() { double dblResult = m_dblValue / m_nCount; return dblResult; } }; //源端类 class Src { public: void SendDataTo( Dst &theDst) { //设置接收端公有变量 theDst.m_dblValue = 2.0; theDst.m_nCount = 3; } }; int main() { //定义两个对象 Dst theDst; Src theSrc; //传递数据 theSrc.SendDataTo(theDst); //接收端处理 cout<<theDst.DoSomething(); // return 0; }
接收端的类对象 theDst 有两个公有成员 m_nCount 和 m_dblValue,源端对象 theSrc 在 SendDataTo 函数里面修改了接收端的成员变量,然后接收端再对数据进行处理。这种传递数据方式最大的问题是谁都可以修改接收端的公有成员变量,如果有其他代码也修改了theDst,那么处理结果是很难预知的,尤其是在多线程程序里,公有变量在另一个线程被修改了,本线程很可能都不知道。如果通过全局变量传递数据的情景,这种负面效果是类似的,而且更严重。
公有成员变量和全局变量方式都会破坏类数据的封装特性,谁都能修改,结果不可控。另外,这种方式不方便做数值有效性鉴定,比如把 m_nCount 设置为 0,除 0 会直接导致程序出错。所以不建议使用这种方式。
接下来是友元类的例子,通过友元声明,源端可以直接设置接收端的私有成员变量,如 friend.cpp 代码:
//friend.cpp #include <iostream> using namespace std; //接收端类 class Dst { private: //私有变量 double m_dblValue; int m_nCount; public: //处理数据函数 double DoSomething() { double dblResult = m_dblValue / m_nCount; return dblResult; } //友元类 friend class Src; }; //源端类 class Src { public: void SendDataTo( Dst &theDst) { //因为是友元类,所以能设置接收端私有变量 theDst.m_dblValue = 2.0; theDst.m_nCount = 3; } }; int main() { //定义两个对象 Dst theDst; Src theSrc; //传递数据 theSrc.SendDataTo(theDst); //接收端处理 cout<<theDst.DoSomething(); // return 0; }
接收端的成员变量是私有的,因此不用担心成员变量被其他代码段胡乱修改,只有友元授权的 Src 类对象才能修改接收端对象私有变量。这种方式的缺陷是 Src 类和 Dst 类是紧耦合的,它们息息相关,修改了一个类的代码很可能影响另一个。除非程序员确定需要这种紧耦合设计,否则一般也不建议使用友元。另外,对于传递的数值有效性鉴定,需要友元类对象确保不把 m_nCount 设置成 0。
再来看看第三种,使用 get 和 set 函数对的方式,在 Qt 类库里面,对于设置数值的函数以 set 字样打头,而获取数值的函数默认省略 get 字样,比如 QLabel 对象,设置文本函数为 setText(),获取文本函数为 text() 。下面的示例也按照 Qt 命名风格,getset.cpp 代码:
//getset.cpp #include <iostream> using namespace std; //接收端类 class Dst { private: //私有变量 double m_dblValue; int m_nCount; public: //get函数 double value() { return m_dblValue; } int count() { return m_nCount; } //set函数 void setValue(double v) { m_dblValue = v; } void setCount(int n) { if( n < 1 ) //防止除 0 ,并且计数限定为正整数 { m_nCount = 1; } else { m_nCount = n; } } //处理数据函数 double DoSomething() { double dblResult = m_dblValue / m_nCount; return dblResult; } }; //源端类 class Src { public: void SendDataTo( Dst &theDst) { //通过set函数传递数据 theDst.setValue(2.0); theDst.setCount(3); } }; int main() { //定义两个对象 Dst theDst; Src theSrc; //传递数据 theSrc.SendDataTo(theDst); //接收端处理 cout<<theDst.DoSomething(); // return 0; }
代码里通过 value()和 setValue() 函数封装私有变量 m_dblValue,通过 count()和 setCount() 函数封装 m_nCount 私有变量,在 set函数里面可以对输入的数据做判断,确认是否合法,数据的可控性大大增强。get/set方式是推荐的做法,下一节有规范的 Qt 属性封装介绍,与这种方式是类似的。
接下来示范一个回调函数的例子,多线程编程和 Windows 编程会经常遇到类似的回调函数,通常源端会给出通用的回调函数类型声明,接收端按照该格式定义自己的回调函数。因为回调函数一般是通用的,所以参数里常用的是void * 指针,而不会针对某一个固定的类对象传参。因为类的普通成员函数需要隐藏的 this指针,会导致不符合回调函数类型声明,所以回调函数只能用静态成员函数或全局函数定义。回调函数示范 callback.cpp:
//callback.cpp #include <iostream> using namespace std; //源端约定回调函数类型 typedef void (*PFUNC)(double v, int n, void *pObject); //接收端类 class Dst { private: double m_dblValue; int m_nCount; public: //处理数据函数 double DoSomething() { double dblResult = m_dblValue / m_nCount; return dblResult; } //回调函数 static void FuncCallBack(double v, int n, void *pObject) { //转换成 Dst 指针 Dst *pDst = (Dst *)pObject; //静态成员函数也是可以设置私有变量的,但需要手动传对象指针 //设置 value pDst->m_dblValue = v; //设置count if( n < 1) { pDst->m_nCount = 1; } else { pDst->m_nCount = n; } } }; //源端类 class Src { public: void SendDataTo( Dst *pDst, PFUNC pFunc) { //通过回调函数传数据 pFunc(2.0, 3, pDst); } }; int main() { //定义两个对象 Dst theDst; Src theSrc; //传递数据 theSrc.SendDataTo(&theDst, Dst::FuncCallBack); //接收端处理 cout<<theDst.DoSomething(); // return 0; }
在上面示例代码中,源端先给出了回调函数类型 PFUNC 的声明,参数为 double 和 int,返回值为空。
接收端的类就按照这个声明定义参数、返回值都一样的静态成员函数 FuncCallBack,作为实际执行的回调函数。回调函数只能是全局函数或静态成员函数,因为类的普通成员函数需要隐藏的 this 指针参数。
在 main 函数里,程序执行流程为:
回调函数机制是很常见的,Windows 消息机制本身也是回调函数的应用,多线程编程也使用回调函数作为新线程里的任务函数。我们上一节示范的三个例子,信号与槽函数可以一对一关联,一对多关联,多对一关联,如果用回调函数实现这些复杂的映射,那会是非常头疼的事。比如希望 theSrc 同时把数据传递给 A、B、C 三个目标对象,那 SendDataTo 函数必须手动执行三次。回调函数难以实现同时一发多收、多发一收,而信号和槽机制是完全可以的,并且代码非常简洁明了。另外信号与槽函数可以在运行时解除关联关系,这也是回调函数不好实现的特性。
介绍完常规的传递数据方法之后,下面来看看 Qt 自定义信号和槽的通信过程。
2、通过自定义信号和槽沟通
通过信号和槽机制通信,通信的源头和接收端之间的松耦合的:
源头和接收端是非常自由的,connect函数决定源头和接收端的关联关系,并会自动根据信号里的参数传递给接收端的槽函数。
因为源头是不关心谁接收信号的,所以 connect 函数一般放在接收端类的代码中,或者放在能同时访问源端和接收端对象的代码位置。
下面开始自定义信号和槽的例子,打开 Qt Creator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 qobjcom,创建路径 E:\QtProjects\ch04,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,向其中拖入一个按钮控件,按钮 objectName 默认为pushButton,将其显示文本的 text 属性设置为“发送自定义信号”,并调整按钮宽度将文本都显示出来,如下图所示:
编辑好之后保存界面,回到代码编辑模式,打开 widget.h,添加处理按钮 clicked 信号的槽函数,和新的自定义的信号 SendMsg:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); signals: //添加自定义的信号 void SendMsg(QString str); //信号只需要声明,不要给信号写实体代码 public slots: //接收按钮信号的槽函数 void ButtonClicked(); private: Ui::Widget *ui; }; #endif // WIDGET_H
signals: 就是信号代码段的标志,这个标志不带 public 、protected、private 等前缀,那是因为信号默认强制规定为公有类型,这样才能保证其他对象能接收到信号。我们定义了 SendMsg 信号,带一个 QString 参数,这个声明与普通函数声明类似。注意信号只是一个空壳,只需要声明它,而不要给它写实体代码。自定义信号的全部代码就是头文件这里的两行(包括 signals: 行),不需要其他的。signals: 标识的代码段只能放置信号声明,不能放其他任何东西,普通的函数或变量、槽函数都不要放在这里。
public slots: 是公有槽函数代码段的标志,定义了 ButtonClicked 槽函数,接收按钮被点击的信号,这个槽函数以后会触发我们自定义的信号。槽函数代码段也只能放槽函数声明的代码,不要把其他的东西放在这个代码段里。
下面来编写 widget.cpp 里面的代码,实现发送我们自定义信号的槽函数,并和按钮的信号关联起来:
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); //关联 connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(ButtonClicked())); } Widget::~Widget() { delete ui; } //槽函数 void Widget::ButtonClicked() { //用 emit 发信号 emit SendMsg( tr("This is the message!") ); }
标签:
原文地址:http://www.cnblogs.com/wyxsq/p/5121882.html