码迷,mamicode.com
首页 > 其他好文 > 详细

Qt学习(17)

时间:2016-01-11 17:58:05      阅读:322      评论:0      收藏:0      [点我收藏+]

标签:

Qt学习(17)——自定义信号和槽

        本节首先介绍一下C++编程中常用的传递数据机制,包括类对象的公有成员变量、友元类/函数、公有函数、回调函数等等,这些机制在Qt程序中也是可以使用的。然后重点介绍如何在Qt类里面自定义信号和槽,通过手动触发信号来调用槽函数,完成两个对象之间的消息传递,本节最后示范一个信号接力触发的例子。本节内容较多,分三部分来学习。

1、C++的沟通方式

         C++编程中常遇到各个对象之间进行沟通的情景,需要将数据从一个对象传递给另一个对象来处理。大致的方法有如下几种:

  • 接收端定义公有成员变量以提供源端修改,然后接收端处理数据;(不建议用!)
  • 接收端将私有成员变量通过友元方式共享给源端,源端可以修改接收端变量;(除了特殊情况,一般不建议使用!)
  • 接收端定义公开的get和set函数,提供给源端调用;(推荐使用,可以与信号和槽机制协同工作
  • 源端给出回调函数约定,接收端定义相同参数和返回值类型的静态成员函数,将静态成员函数作为回调函数交给源端,源端再调用该函数。(可以使用

这些方式都是基于标准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 函数里,程序执行流程为:

  •  ①theSrc 调用 SendDataTo 函数,参数有目标对象 theDst 指针和目标类里定义好的 回调函数 Dst::FuncCallBack。
  •  ②在 SendDataTo 函数内部,回调函数 Dst::FuncCallBack 作为 pFunc ,被调用执行,三个参数值为 2.0、3 和目标对象    指针。
  •  ③pFunc 就是 Dst::FuncCallBack 回调函数,这个回调函数会根据三个参数里数值,首先将 void * 指针转为目标对象指针     pDst,然后设置目标对象里的两个私有成员变量,并且可以直接做数据有效性检查。
  •  ④theDst 对象里的成员变量被设置好之后,就可以调用 DoSomething 函数做处理了。

        回调函数机制是很常见的,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!") );
}

 

Qt学习(17)

标签:

原文地址:http://www.cnblogs.com/wyxsq/p/5121882.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!