Qt界面开发总结--信号槽使用自定义的数据结构

1 项目背景

在完成最后一个项目的过程中,因为之前的定制版本是基于C#和WPF的,由于Qt的跨平台特性,而且相较于WPF简单易学,资料丰富,界面漂亮,尤其是能够独立安装和跨平台的特性,使得在项目开发初期就选择了C++与Qt的版本。由于之前并没有进行过界面类的开发,因此在这里简要的对项目开发中所遇到的问题进行总结,作为资料留存。

2 Qt简介

Qt是一个跨平台的C++应用程序框架,支持Windows、Linux、Mac OS X、 Android、IOS、嵌入式系统。即Qt可以同时支持桌面应用程序开发、嵌入式开发和移动开发,可以覆盖现有的主流平台,编写一次代码即可发布到不同的平台重新编译即可。
Qt不仅仅是一个GUI库(图形用户界面),除了可以创建漂亮的界面,还有很多其他的组件,在Qt中,不用再研究C++的STL、std::string,解析XML、连接数据库、Socket的各种第三方库,这些都已经在Qt中得到继承。如同Qt中的QString类、Qvector类。所以Qt是应用程序的一站式解决方案,而且Qt的程序最终要编译成本地代码,而不是依托虚拟机。
Qt的历史也比较丰富,1991年由Qt Company公司开发出来,作为跨平台的C++图形用户界面应用程序开发框架。2008年被诺基亚收购,之后诺基亚被微软坑了一把,2012年被芬兰公司Digia收购。

···
http://download.qt.io/archive/qt/
···

网站看到的Qt的当前版本如下图所示:
这里写图片描述
本次项目代码开发基于的Qt的版本是4.8.6.

3 Qt控件

与WPF一致,作为图形用户界面的应用程序开发框架,Qt内含丰富的控件类型,支持拖拽,同时Qt使用面向对象的策略组织Qt的控件,这使得在自定义控件时非常方便。与常用的页面元素一致,常用的Qt控件如下:
这里写图片描述
Qt常用的布局如下:
这里写图片描述

4 开发遇到的问题

在使用Qt进行界面开发的过程中,遇到了许多的问题,然后通过查阅博客,官方文档各种手段也解决了一部分,在此对遇到的问题进行总结,加深理解,若以后需要重新使用,以期望能够快速捡起来的目的对问题进行简要总结。

4.1 QWidget背景图片填充

如何用一个图片的URL作为参数,传入某个QWidget,进而填充该Qwidget?这是开始遇到的问题。代码片段如下:

4.1.1 代码实现

void FillWidgetWithBg(QWidget *widget, QString& fileName)
{
    widget->setAutoFillBackground(true);
    QPixmap bgMap(fileName);
    /*qDebug()<<fileName;*/
    QPalette bgPalette;
    bgPalette.setBrush(QPalette::Background,QBrush(bgMap.scaled(widget->size(),
        Qt::IgnoreAspectRatio,
        Qt::SmoothTransformation)));
    widget->setPalette(bgPalette);
}

在Qt中涉及到字符串的几乎所有操作,都是QString类,因此如果习惯了C++开发者可能不习惯,需要进行std::string到Qstring的转换。

QString fileName = QDir::currentPath() + QString::fromLocal8Bit("/image/来访人员和来访车辆-bg.png");
FillWidgetWithBg(ui.widget1, fileName);

或者使用资源文件定义:

backgroundUrl = QString::fromLocal8Bit(":/DisplayImage/image/相似度-对比通过.png");  
FillWidgetWithBg(ui.widget2_3, backgroundUrl);

注意:在Qt的程序中有两种路径,一种是“./”,一种是“:/”,这两种的区别如下:
“./”:相对路径,表示的是相对于当前目录下的位置。项目的当前位置一般是.vcproj文件所在的位置。如果在当前目录下有image目录,image目录下有来访人员和来访车辆-bg.png文件,我们引用该目录的另外一种与方式1等价的方式如下:

QString fileName = QString::fromLocal8Bit(“./image/来访人员和来访车辆-bg.png”);

实际上,QDir::currentPath()即取当前目录的意思。
“:/”: 表示对资源的引用,引用资源文件路径 如”:/DisplayImage/image/相似度-对比通过.png” 表示资源文件里面定义的相应文件。而资源文件的配置方式参见4.3

4.1.2 样式表设置背景

可以使用Qt提供的灵活多变的样式便来完成背景填充,下述代码片段是一些样式表的简单使用:

widgetBrowse->setStyleSheet(QString::fromUtf8("background-color: #140F37"));
labelSearchIcon->setStyleSheet(QString::fromUtf8("background-image: url(:/DisplayImage/image/icon-search.png)"));
listWidgetCarNameDisplay->setStyleSheet(QString::fromUtf8("background:transparent"));
setStyleSheet("QFrame#CardPersonFrame{border: 1px solid #0197FF; border-radius: 4px;}");
m_pLabelName->setStyleSheet("color: white");
ui.listWidget_car->setStyleSheet("background-color:transparent");
m_pLabelLeaveTime->setStyleSheet("color: white; "
       "font-family: Microsoft YaHei;"
       "font-size: 16px;");

更多的关于样式表的设置的例子可以参见
http://doc.qt.io/archives/qt-4.8/stylesheet-examples.html

4.2 QWidget

QWidget是所有用户界面对象的基类,如下图所示,常见的Qlable,QlineEdit,QtextEdit等控件均为该类的子类,在GUI开发中,常见的操作是继承该类,实现某种模式的窗口部件类型。在下图中出现了两种窗口部件,parent和child。
这里写图片描述
- QWidget *parent = 0 ,为新创建的widget指定其父widget。如果parent为0(默认值),那么这个新widget就会变成一个独立的window。如果parent不为0,那么新创建的widget是parent的一个子窗口,但是此时我们新创建的widget的形状会受其父窗口形状的约束。(除非你指定这个新创建的widget的window flag为Qt::Window)
- Qt::WindowFlags f = 0,这个参数用来设置新创建的widget的window flags(例如是否有最大化按钮等)。默认的参数几乎对所有的widget都是适用的。但是如果你需要一个没有边框的widget,你必须使用特定的flag。

4.2.1 顶级Widget和子Widget

一个没有父widget的widget是一个独立的window,即是一个顶级widget。对于这些widget,setWindowTitle()函数和setWindowIcon()分别设置窗口的标题和图标。

非window类型的widget是子widget,在父widget中显示。Qt中的大多数widget主要是作为子widget。例如,我们可以把一个按钮作为一个顶级窗口,但是大多数人倾向于把他们的按钮放在widget里面,例如把按钮放在QDialog(对话框类)中。

4.2.2 继承

这里写图片描述
可以看到大部分的界面元素对象均为Qwidget的子类,因此在实现时,我们经常需要继承该Qwidget类。项目中一个这样的示例如下:
这里写图片描述
从示意图可以看到,该模块是一个列表,可以对应Qt中的QlistWidget,而其中的每一项则是一个含有两个成员的标签,一个标签显示访客姓名,另外一个显示目的地。在点击某一项时,则弹出该访客此次访问的具体信息,诸如进入时间和无效时间。该项目的实现如下:
visitor.h

#ifndef VISITOR_H
#define VISITOR_H
#include <QWidget>
#include <QLabel>
#include <QFont>
#include <QPalette>
#include <QDebug>


class Visitor : public QWidget
{
    Q_OBJECT

public:
    Visitor(QWidget *parent, QString& visitorName, QString& visitorDestination, QString& sArriveTime, QString& sLeaveTime);
    ~Visitor();

private:
public:
    QLabel* m_pLabelName;
    QLabel* m_pLabelDestination;
    QString m_strArriveTime;
    QString m_strLeaveTime;

};

#endif // VISITOR_H

visitor.cpp如下:

#include "visitor.h"

Visitor::Visitor(QWidget *parent, QString& visiortName, QString& visitorDestination, QString& sArriveTime, QString& sLeaveTime)
    : QWidget(parent)
{
   setMinimumSize(260, 70);
   setMaximumSize(260, 70);

   m_pLabelName = new QLabel(this);
   m_pLabelDestination = new QLabel(this);
   m_pLabelName->setGeometry(28, 22, 96, 28);
   m_pLabelDestination->setGeometry(136, 22, 96, 28);

   m_pLabelName->setStyleSheet("color: white; "
       "font-family: Microsoft YaHei;"
       "font-size: 20px;");
   m_pLabelName->setText(visiortName);
   m_pLabelDestination->setStyleSheet("color: white; "
       "font-family: Microsoft YaHei;"
       "font-size: 20px");
   m_pLabelDestination->setText(visitorDestination);
   this->m_strArriveTime = sArriveTime;
   this->m_strLeaveTime = sLeaveTime;
   setVisible(true);

}



Visitor::~Visitor()
{

}

按照部门的代码规范,类中定义的成员以“m_”开头,其后的字符串应该能指示出该成员的数据类型,若为字符串可以使用m_strArriveTime,,而对应的指针型成员可以使用m_pLabelName,如果是bool类型成员,则可以以m_b开头。
上述两部分的代码主要是演示继承QWidget类时的具体使用方式。在该类中并没有使用布局,这是当前代码的不足之处。控件之间的位置是通过精确的几何布局管理实现的。
The widget is the atom of the user interface: it receives mouse, keyboard and other events from the window system, and paints a representation of itself on the screen. Every widget is rectangular, and they are sorted in a Z-order. A widget is clipped by its parent and by the widgets in front of it.

A widget that is not embedded in a parent widget is called a window. Usually, windows have a frame and a title bar, although it is also possible to create windows without such decoration using suitable window flags). In Qt, QMainWindow and the various subclasses of QDialog are the most common window types.
上述两行则是官网对QWidget的简要描述。在界面上,需要使用Qwidget的场合使用自定义的QWidget都是可用的。这便是继承体系的强大之处。

4.3 资源的使用

在Qt中通过为项目添加资源文件,可以进行统一的资源管理,方便开发时使用。配置步骤如下:
双击项目Resource下的.qrc文件可以看到如下界面:
这里写图片描述
在资源文件这样设置过之后,就可以按照下面的方式进行使用。

4.3.1 Qt Designer

这里写图片描述
在开发中有一个这样的界面,该小部分可以直接在Qt设计师中进行设计。该QWidget一共有两个部分搜索图标签,一个QLineEdit。设置一个QWidget的背景图片填充,可以直接在Qt设计师中对切图资源进行设置,因为这些图片时框架的一部分,在程序运行时,是不会变化的。首先右击该标签,
这里写图片描述
单机改变样式表,选择添加资源。
这里写图片描述
此时就已经使用资源文件对该标签部件的背景图片进行设置了。

4.3.2 代码使用

代码使用同样方便,与上述操作等价的代码实现如下:

labelSearchIcon = new QLabel(widgetBrowse);
labelSearchIcon->setObjectName(QString::fromUtf8("labelSearchIcon"));
labelSearchIcon->setGeometry(QRect(16, 22, 28, 28));
labelSearchIcon->setStyleSheet(QString::fromUtf8("background-image: url(:/DisplayImage/image/icon-search.png)"));

因此在使用资源文件时,可以方便的使用“:/DisplayImage/”进行文件的访问。此前缀也是可以修改的。不再赘述。

4.4 QString类与std::string和const char*

Qt中最常使用的类大概就是QString类,因为该类是肉眼可识别的字符串类。为了跨平台和跨语言,QString在实现时存储的是16-位unicode字符(QChar)序列。与C++标准库std::string字符序列[8位0-255]不同,unicoe字符序列适用于中文、日文、韩文因此对语系的支持非常好。Unicode是一种国际标准,支持今天正在使用的大多数的书写系统。是USA-ASCII码和ISO8859-1的超集,并且ASCII/Latin-1在相同的位置是可用的。

4.4.1 相互转换

QString类可以通过下述的函数返回成QByteArray的const char*。toAscii(), toLatin1(), toUtf8(), and toLocal8Bit().如果要从这些编码转换成QString,则可以通过成员fromAscii(), fromLatin1(), fromUtf8(), and fromLocal8Bit()。
使用中文命名的文件的一个常见形式就是Qt的中文支持,可以通过下述的方式把中文字符串转换成QString

QString str = QString::fromLocal8Bit("访客人员");
QFont font("Microsoft YaHei", 24, QFont::Normal);
ui.labelVisitor->setFont(font);
QPalette pa;
pa.setColor(QPalette::WindowText, Qt::white);
ui.labelVisitor->setText(str);
ui.labelVisitor->setPalette(pa);

上述代码片段通过fromLocal8Bit把中文转换成QString,并设置成一个标签的内容,同时设置了内容的字体和颜色。

4.4.2 QString的格式化

QString类提供了大量的arg函数来完成格式化输出的功能:

QString QString::arg(const QString & a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ' ' )) const

返回字符串的拷贝,其中最下编号的位置标记由字符串替换,即%1,%2, %3, ……,%99

QString i;           // current file's number
QString total;       // number of files to process
QString fileName;    // current file's name

QString status = QString("Processing file %1 of %2: %3")
                .arg(i).arg(total).arg(fileName);
QString QString::arg(const QString & a1, const QString & a2) conststr.

arg(a1).arg(a2)等价,参见实例:

QString str;
str = "%1 %2";

str.arg("%1f", "Hello");        // returns "%1f Hello"
str.arg("%1f").arg("Hello");    // returns "Hellof %2"
QString QString::arg(int a, int fieldWidth = 0, int base = 10, const QChar & fillChar = QLatin1Char( ' ' )) const

该函数完成整数的格式化

QString str;
str = QString("Decimal 63 is %1 in hexadecimal")
        .arg(63, 0, 16);
// str == "Decimal 63 is 3f in hexadecimal"

QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
str = QString("%1 %L2 %L3")
        .arg(12345)
        .arg(12345)
        .arg(12345, 0, 16);
// str == "12345 12,345 3039"
QString QString::arg(double a, int fieldWidth = 0, char format = 'g', int precision = -1, const QChar & fillChar = QLatin1Char( ' ' )) const
double d = 12.34;
QString str = QString("delta: %1").arg(d, 0, 'E', 3);
// str == "delta: 1.234E+01"

,如果实在不习惯使用QString类的格式化,则可以先使用C/C++格式化成功之后转换成QString也是可以的。

4.5 dynamic_cast

dynamic_cast支持运行时识别指针或引用所指向的对象。该关键字的主要作用是通过使用dynamic_cast操作符将基类对象的引用或指针转换为同一层次中其他类型的引用或真知。与dynamic_cast一起使用的指针必须是有效的—-它必须为0或者指向一个对象。
与其他强制类型转换不同,dynamic_cast涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型的对象,则dynamic_cast失败。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果为0;如果转换到引用类型的dynamic_cast失败,则跑出一个bad_cast类型的异常。
一般使用的模式代码如下:

if (Derived *derivedPtr = dynamic_cast<Derived*>(basePtr))
{
        //use the Derived object to which derivedPtr points
}
    else //BasePtr points
{
    //use the Base Object to which basePtr points
}

在运行时,如果 basePtr 实际指向 Derived 对象,则转换将成功,并且derivedPtr 将被初始化为指向 basePtr 所指的 Derived 对象;否则,转换的结果是 0,意味着将 derivedPtr 置为 0,并且 if 中的条件失败。可以对值为 0 的指针应用 dynamic_cast,这样做的结果是 0。
在实现访客人员列表展示的时候,使用了这种技术,因为在未点击列表中某一项的时候,所有项仅展示姓名和目的地,而展示了某一项则展示完整信息。由于两个窗体均为QWidget的子类,因此非常自然的符合这种dynamic_cast用法。

QObject::connect(ui.listWidgetVisitor, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(shapeChanged(QListWidgetItem *, QListWidgetItem *)));
QObject::connect(ui.listWidgetVisitor, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(visitorItemClicked(QListWidgetItem *)));

两个函数的实现如下:

void BuildingDisplay::shapeChanged(QListWidgetItem * current, QListWidgetItem * previous)
{
    DISPLAYMAIN_TRACE("Enter BuildingDisplay::shapeChanged");
    QString sName;
    QString sDestination;
    QString sArriveTime;
    QString sLeaveTime;
    VisitorFull *previousVisitorFull = NULL;
    if (NULL != dynamic_cast<VisitorFull*>(ui.listWidgetVisitor->itemWidget(previous)))
    {
        //强制转换为VisitorFull类型指针
        previousVisitorFull = static_cast<VisitorFull*>(ui.listWidgetVisitor->itemWidget(previous));
        Visitor *visitorSmall = new Visitor(ui.listWidgetVisitor, previousVisitorFull->m_pLabelName->text(), previousVisitorFull->m_pLabelDestination->text(), previousVisitorFull->m_pLabelArriveTime->text(), previousVisitorFull->m_pLabelLeaveTime->text());
        if (NULL == visitorSmall)
        {
            DISPLAYMAIN_ERROR("Memory Allocation Fails: visitorSmall");
            return;
        }
        previous->setSizeHint(QSize(260, 70));
        QBrush brush;
        brush.setColor(Qt::transparent);
        previous->setBackground(brush);
        ui.listWidgetVisitor->setItemWidget(previous, visitorSmall);
    } 

    //使用dynamic_cast进行动态运行转换
    Visitor *currentItem = dynamic_cast<Visitor*>(ui.listWidgetVisitor->itemWidget(current));
    if(NULL == currentItem)
    {
        DISPLAYMAIN_ERROR("Memory Allocation Fails:dynamic_cast currentItem");
        return ;
    }
    sName = currentItem->m_pLabelName->text();
    sDestination = currentItem->m_pLabelDestination->text();
    sArriveTime = currentItem->m_strArriveTime;
    sLeaveTime = currentItem->m_strLeaveTime;
    VisitorFull *visitorBig = new VisitorFull(ui.listWidgetVisitor, sName, sDestination, sArriveTime, sLeaveTime);
    if (NULL == visitorBig)
    {
        qDebug()<<"Memory Allocation fails";
        return;
    }
    current->setSizeHint(QSize(260, 210));
    current->setFlags(Qt::ItemIsEnabled);
    current->setBackgroundColor(QColor(7, 37, 73));
    ui.listWidgetVisitor->setItemWidget(current, visitorBig);

    //delete previousVisitorFull;

    //delete currentItem;
    //previousVisitorFull = NULL;
    //currentItem = NULL;
   /* SAFEDELETE(previousVisitorFull);
    SAFEDELETE(currentItem);*/
    DISPLAYMAIN_TRACE("Leave BuildingDisplay::shapeChanged");
}

void BuildingDisplay::visitorItemClicked(QListWidgetItem *item)
{
    qDebug()<<"Enter BuildingDisplay::visitorItemClicked";
    DISPLAYMAIN_TRACE("Enter BuildingDisplay::visitorItemClicked");
    QWidget *currentItem = ui.listWidgetVisitor->itemWidget(item);
    VisitorFull *vf = NULL;
    Visitor *vis = NULL;
    QString sName;
    QString sDestination;
    QString sArriveTime;
    QString sLeaveTime;
    //使用dynamic_cast进行两种类型的切换
    if (NULL != (vf = dynamic_cast<VisitorFull*>(currentItem)))
    {
        //currentItem为VisitorFull类型的指针
        sName = vf->m_pLabelName->text();
        sDestination = vf->m_pLabelDestination->text();
        sArriveTime = vf->m_pLabelArriveTime->text();
        sLeaveTime = vf->m_pLabelLeaveTime->text();
        vis = new Visitor(ui.listWidgetVisitor, sName, sDestination, sArriveTime, sLeaveTime);
        if (NULL == vis)
        {
            DISPLAYMAIN_ERROR("NULL == vis");
            return;
        }
        //变成小的,窗口要设置成透明
        QBrush brush;
        brush.setColor(Qt::transparent);
        item->setBackground(brush);
        item->setSizeHint(QSize(260, 70));
        ui.listWidgetVisitor->setItemWidget(item, vis);
        //注意:在此处发现如果鼠标右击每一项,发现程序崩溃,同时使用任务管理器查看,
        //如果删除如下的delete,内存并没有增长。因此暂时注释内存释放
        //因为Qt的资源管理与常规理解不一样。之后的代码与此注释一致,不再赘述
        //delete vf;
        //vf = NULL;
    }
    else
    {
        //currentItem为Visitor类型指针
        vis = dynamic_cast<Visitor*>(currentItem);
        sName = vis->m_pLabelName->text();
        sDestination = vis->m_pLabelDestination->text();
        sArriveTime = vis->m_strArriveTime;
        sLeaveTime = vis->m_strLeaveTime;
        vf = new VisitorFull(ui.listWidgetVisitor, sName, sDestination, sArriveTime, sLeaveTime);
        if (NULL == vf)
        {
            qDebug()<<"vf == NULL";
            return;
        }
        item->setBackgroundColor(QColor(7, 37, 73));
        item->setSizeHint(QSize(260, 210));
        ui.listWidgetVisitor->setItemWidget(item, vf);


        //delete vis;
        //vis = NULL;

    }
    DISPLAYMAIN_TRACE("Leave BuildingDisplay::visitorItemClicked");

}

4.6 new与空指针

内存分配在这一次的代码开发中也有存在,C++的内存管理策略就是自己分配自己释放,因此在分配内存过程中,一定要在分配内存之后立刻进行判断,然后若分配失败直接返回即可。主要要用do-while(0)包含内存分配的代码。简要的代码结构如下:

bool MemoryAllocate()
{
    MyClass * pM1 = NULL;
    MyClass * pM2 = NULL;
bool ret = true;
do
{
        pM1 = new MyClass();
        pM2 = new MyClass();
        if ((NULL == pM1) || (NULL == pM2))
        {
        ret = false;
        break;
}
//use pM1 and pM2
}while (0);
if(!ret)
{
        SAFEDELETE(pM1);
        SAFEDELETE(pM2);
}
return true;
}

4.7 QListWidgetItem项

QListWidgetItem类提供QListWidget类中的项。一个QListWidgetItem表示QLIstWidget中一个单一的项,每一项可以有几个信息块,会适当的展示这些信息。List items通常用来展示text()和icon().文字的外观可以通过setFont,setBackground,setForeground进行定制。在列表中的项的对齐方式可以通过setTextAlignment()进行设置。每一项的标志可以通过调用setFlags()进行设置。可选择的项能够通过setCheckState()被选择,取消选择,部分选择。对应的checkState()能够指示当前项的选择状态。

Constant    Value   Description|
Qt::NoItemFlags:    0   It does not have any properties set.|
Qt::ItemIsSelectable    1   It can be selected.
Qt::ItemIsEditable  2   It can be edited.
Qt::ItemIsDragEnabled   4   It can be dragged.
Qt::ItemIsDropEnabled   8   It can be used as a drop target.
Qt::ItemIsUserCheckable 16  It can be checked or unchecked by the user.
Qt::ItemIsEnabled   32  The user can interact with the item.
Qt::ItemIsTristate  64  The item is checkable with three separate states.

4.7.1 属性设置

QListWidgetItem *item = new QListWidgetItem(ui.listWidgetPersonPhoto);
item->setSizeHint(QSize(140, 163));
item->setBackgroundColor(QColor(19, 49, 101));
ui.listWidgetPersonPhoto->setItemWidget(item, 
    new InternalPersonnel(ui.listWidgetPersonPhoto, 
    photoUrl, QString::fromLocal8Bit("内部人员")));

4.8 QListWidget显示窗体过程

下图为QListWidget运行时的界面显示。QListWidget是一个提供列表视图的方便类,与QListView类似。但是是基于项的界面来增加或者删除项目。QListWidget使用内部模型来管理列表中的QListWidgetItem。
这里写图片描述
主要的过程是先自定义好QWidget的子类实现,通过下面的方式把生成的QWidget子类对象设置成item对应的Widget。

void QListWidget::setItemWidget(QListWidgetItem * item, QWidget * widget)
Sets the widget to be displayed in the give item.

大致过程与上述属性设置一致。

4.9 控件边缘设置QFrame

在项目中要实现下图的界面:
这里写图片描述
可以看出该界面是有边缘的,而且边缘为蓝色,具有一定的宽度,但在实际开发中,之前的想法是继承自QWidget,但在之后的程序运行中,始终无法显示边缘。之后听取同事的建议,继承自QFrame,通过样式表正确的设置了边缘的属性。
CardPerson类

#ifndef CARDPERSON_H
#define CARDPERSON_H

#include <QWidget>
#include <QLabel>
#include <QFont>
#include <QVBoxLayout>
#include <QDebug>

class CardPerson : public QFrame
{
    Q_OBJECT

public:
    CardPerson(QWidget *parent, QString& PersonName, QString& PersonDestionation, bool InternalPerson);
    ~CardPerson();

private:
    QLabel *m_pLabelName;
    QLabel *m_pLabelDestination;

};

#endif // CARDPERSON_H

该类对应的实现如下:

#include "cardperson.h"

CardPerson::CardPerson(QWidget *parent, QString& PersonName, QString& PersonDestionation, bool InternalPerson)
    : QFrame(parent)
{

   /*qDebug()<<"personName: "<<PersonName<<"personDestination"<<PersonDestionation;*/
   this->setAutoFillBackground(true);
   QPalette pa;
   pa.setColor(QPalette::Background, QColor(13, 48, 103));
   this->setPalette(pa);
   //setStyleSheet(
   //     "border: 1px solid #0197FF;"
   //     "border-radius: 4px;");
   setObjectName("CardPersonFrame");
   /*
    If we want the property to apply only to one specific QLineEdit, we can give it a name using QObject::setObjectName() and use an ID Selector to refer to it:
myDialog->setStyleSheet("QLineEdit#nameEdit { background-color: yellow }");
*/
   setStyleSheet("QFrame#CardPersonFrame{border: 1px solid #0197FF; border-radius: 4px;}");
   setMaximumSize(184, 100);
   setMinimumSize(184, 100);
    if (InternalPerson)
    {
        PersonDestionation = QString::fromLocal8Bit("内部人员");
    }
    //this->setStyleSheet("background-color: rgb(13, 48, 103)");
    m_pLabelName = new QLabel(this);
    m_pLabelName->setGeometry(50, 14, 96, 36);
    m_pLabelDestination = new QLabel(this);
    m_pLabelDestination->setGeometry(50, 50, 96, 36);
    QFont font;
    font.setFamily("Microsoft YaHei");
    font.setPixelSize(20);
    font.setLetterSpacing(QFont::AbsoluteSpacing, 0);
    m_pLabelName->setFont(font);
    m_pLabelName->setStyleSheet("color: white");
    m_pLabelName->setText(PersonName);

    m_pLabelDestination->setFont(font);
    m_pLabelDestination->setStyleSheet("color: white");
    m_pLabelDestination->setText(PersonDestionation); 
    setVisible(true);

}

CardPerson::~CardPerson()
{

}

4.10 信号槽

信号和槽用于对象之间的通信。也是Qt的区别于其他框架的中心特征。
这里写图片描述
信号槽机制是类型安全,实际上槽函数可以具有比信号更少的参数,因为槽函数可以忽略多余的参数。所有继承自QObject类或者它的子类的类型都可以包含信号槽。槽函数用于接收信号,但是槽函数也是普通的成员函数,正如一个对象不知道是否有对象接收自己的信号,槽函数也不知道是否有信号与槽关联。

4.10.1 理解

1、signals前面不可加public、private和protected进行修饰;而且信号没有函数体。slots前面可以加,因为Qt说槽函数可以当普通函数使用。
2、signals区域的函数必须是void类型,而且这些信号函数没有函数体,也就是说不可以自己定义这些信号函数,你只要声明它就够了,其它不用管,Qt内部自己弄。
3、宏定义和函数指针不能用于信号和槽的参数,信号和槽也不能有缺省参数

4.10.2 QListWidgetItem 当前项变化和项被点击

在项目开发过程中,主要围绕的是QListWidget类型。

void    currentItemChanged ( QListWidgetItem * current, QListWidgetItem * previous )
void QListWidget::itemClicked ( QListWidgetItem * item ) [signal]

该项主要是在项目运行时点击访客或车辆列表中的一项时,当前项自动扩展,而前一个当前项折叠起来的界面效果。

QObject::connect(ui.listWidgetVisitor, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(shapeChanged(QListWidgetItem *, QListWidgetItem *)));
QObject::connect(ui.listWidgetVisitor, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(visitorItemClicked(QListWidgetItem *)));

因此在该类中添加两个槽函数即可完成对应动作的界面操作。

4.11 C++代码开发路径问题

4.11.1 属性配置相对路径

在使用VS进行开发的过程中,一个经常的操作是要识别第三方提供的dll和头文件到自己的程序中,因此我们需要包含他人的dll和头文件。惯常的行为是在项目文件.vcproj所在目录建立两个文件夹,一个是include,另外一个是lib文件夹,分别存放的是头文件和.lib文件,至于.dll文件则要手动放置到可执行文件所在的目录。另外对于公共库一般会放置在.vcproj文件的上一层目录中。
另外工程的输出目录一般要和中间目录区分开来,这样可以使成果物和中间文件放置在不同的目录中。

4.11.1.1 输出目录配置

VS右击工程,打开属性,如下图进行配置。
这里写图片描述

4.11.1.2 C/C++配置

为了找到引入的头文件,需要修改项目属性的附加包含目录指向包含头文件的目录。而且注意在指定附加包含目录时不能使用绝对路径,而只能使用相对路径。如图所示:
这里写图片描述

4.11.1.3 链接器配置

链接器配置则需要配置相应的.lib文件的附加包含目录,并且在输入中指定.lib文件的文件名。
在链接器–>常规中添加附加包含目录
这里写图片描述
在链接器–>输入中添加附加依赖项:如下图所示:
这里写图片描述

4.11.2 C++代码中路径问题

C/C++编程中的相对路径和绝对路径使用
C语言中,反斜杠’\‘表示转义字符,所以绝对路径需要如下表示

  FILE * fp;
  fp = fopen("E:\\test\\file\\data\\d.txt","r");

也可以用相对路径表示,不受转义字符限制:

FILE * fp;
  fp = fopen("E:/test/file/data/d.txt","r");

或者,当前路径E:\test\file下,相对路径表示为

  FILE * fp;
  fp = fopen("./data/d.txt","r");

弄清楚,”E:\test\file\data\d.txt”的这种表示是因为const char*的转义字符的存在,而在实际内存中,真实的文件url为”E:\test\file\data\d.txt”。
电脑资源管理器显示目录

  E:\test\file\data

则当前路径

E:\test\file

相对路径和绝对路径也可以如下表示:

4.11.2.1 相对路径

./    表示当前路径,相当于E:\test\file
../  表示当前路径的上一级路径,相当于E:\test
../../  表示当前路径上上一级路径,相当于E:

更多指向上级路径的表示以此类推。

./data  表示当前路径下一级路径,相当于E:\test\file\data
./data/xxx   表示当前路径的下下一级路径,相当于 E:\test\file\data\xxx

4.12 界面绘制

重新绘制Qt界面必须在主线程中进行,这是结论,在项目开发过程中使用的在子线程中绘制要调用了界面的公共函数重新绘制了界面,但界面长期不响应,并且观察内存占用为0,程序卡死。询问同事才明白是Qt界面的绘制必须使用信号槽机制丢给主线程,进行绘制。实现的过程如下:

class BuildingDisplay : public QMainWindow
{
    Q_OBJECT

public:
    BuildingDisplay(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~BuildingDisplay();
    friend HPR_VOIDPTR CALLBACK GetAlarmEventPro(void* param);
    signals:
        void SignalDepictVisitorsAndCars(std::vector<VisitorInfo>& vVisitors);
    public slots:
        void DepictVisitorsAndCars(std::vector<VisitorInfo>& vVisitors);

private:
    Ui::BuildingDisplayClass ui;
    HPR_HANDLE m_hRecvThread;
    volatile bool m_bStop4Pro;
    bool m_bQuery;
    std::string m_strStartTime;
    std::string m_strEndTime;
    std::vector<VisitorInfo> m_Visitors;
    double m_SimilarityCriteria;

};

在接入平台数据时必须使用多线程的机制,可以看到,在实现中把线程体函数实现为了BuildingDisplay的友元了,这是为了读取的方便。
线程体函数的定义如下:

HPR_VOIDPTR CALLBACK GetAlarmEventPro(void* param)
{

    BuildingDisplay * pBuildDisplay = static_cast<BuildingDisplay*>(param);
    //预先分配M大小的数组保存访客列表信息
    //char szResponse[1024*1024] = {0};//保存所有的访客信息1M
    std::string sStartTime;
    std::string sEndTime;
    std::pair<std::string, std::string> Time = pBuildDisplay->GetTime();
    //不断获取数据
    while (!pBuildDisplay->m_bStop4Pro)
    {
        if (pBuildDisplay->m_bQuery)
        {
            NAME_CMSWSUTILS::WS_QueryVistorInfo(Time.first.c_str(), Time.second.c_str(), CGlobalConfig::GetInstance()->GetDestination().c_str(), szResponse, sizeof(szResponse)/sizeof(char));

            std::vector<VisitorInfo> vVisitors;
            CommonTools::JsonToVisitorInfo(szResponse, vVisitors);
            //TODO DealWithData
            pBuildDisplay->DealWithDoorVisitorUpdateSignals(vVisitors);
            pBuildDisplay->m_bQuery = false;
        }
        Sleep(100);
    }
    return NULL;
}

在使用时,一定要留意对线程变量,m_bStop4Pro,m_bQuery成员进行初始化。因为之前未对m_hRecvThread进行初始化,发现分配的变量中竟然有残留值。
在初始化中进行线程的创建:

m_bStop4Pro = false;
if (!m_hRecvThread)
{
    m_hRecvThread = HPR_Thread_Create(GetAlarmEventPro, (void*)this, 0);
}
if (!m_hRecvThread)
{
    DISPLAYMAIN_ERROR("Create Thread Fails");
    return ;
}

由于std::vector不是Qt的类型,因此需要对该类进行注册注册过程和关联信号槽的代码如下:

qRegisterMetaType<DoorCard>("DoorCard");
qRegisterMetaType<DoorCard>("DoorCard&");
qRegisterMetaType<std::vector<VisitorInfo> >("std::vector<VisitorInfo>");
qRegisterMetaType<std::vector<VisitorInfo> >("std::vector<VisitorInfo>&");
qRegisterMetaType<FaceInfo>("FaceInfo");
/*qRegisterMetaType<VecVis>("VecVis");*/
QObject::connect(this, SIGNAL(signalDepictDoorCard(DoorCard&)), this, SLOT(DepictDoorCard(DoorCard&)), Qt::QueuedConnection);
QObject::connect(this, SIGNAL(SignalDepictVisitorsAndCars(std::vector<VisitorInfo>&)), 
    this, SLOT(DepictVisitorsAndCars(std::vector<VisitorInfo>&)));
QObject::connect(this, SIGNAL(SignalDepictFaceGrab(FaceInfo)), this, SLOT(DepictFaceGrab(FaceInfo)));

注册之后,Qt才可以通过元数据对象相关知识为信号槽机制参数的传递进行准备。
然后为该类撰写槽函数DepictVisitorsAndCars即可工作。

4.13 自定义类型或引用作为信号槽的参数

QObject::connect: Cannot queue arguments of type 'DoorCard' (Make sure 'DoorCard' is registed using qRegisterMetaType().) 

Qt信号槽机制在使用了自定义的数据结构时会出现上述的问题,表示的含义是该类型数据无法作为信号槽的参数,线程中信号和槽函数的传递,参数是默认放到队列中去的,但是这个自定义的参数结构不是Qt自带的参数结构,不能识别。一般的解决方式如下:
将不能识别的参数结构进行注册,让Qt能够识别
1, 包含头文件QMetaType
2, 在构造的类的构造函数或者其他函数中完成其注册:qRegisterMetaType< std::vector >(“std::vector”);

4.13.1 自定义类型作为信号槽的参数

在项目中,使用了自定义类DoorCard&作为信号槽的参数,该结构体定义如下:

#pragma once
#include <QMetaType>  
#include <string>
using std::string;


struct DoorCard
{
    string sCardNum;//卡号
    string sDoorId;//门禁点标识
    string sPersonName;//持卡人姓名
    bool bInnterCardType;//内部人员卡片类型
    string sSnapPic;//刷卡时抓拍图片路径
    string sSex;
    string sIdentityCardNum;//可能为空

};
Q_DECLARE_METATYPE(DoorCard);

注意在头文件中包含QMetaType头文件, Q_DECLARE_METATYPE(DoorCard);

4.13.2 标准容器类作为信号槽的参数

相关的注册类型的代码如下:

qRegisterMetaType<DoorCard>("DoorCard");
qRegisterMetaType<DoorCard>("DoorCard&");
qRegisterMetaType<std::vector<VisitorInfo> >("std::vector<VisitorInfo>");
qRegisterMetaType<std::vector<VisitorInfo> >("std::vector<VisitorInfo>&");
qRegisterMetaType<FaceInfo>("FaceInfo");
/*qRegisterMetaType<VecVis>("VecVis");*/
QObject::connect(this, SIGNAL(signalDepictDoorCard(DoorCard&)), this, SLOT(DepictDoorCard(DoorCard&)), Qt::QueuedConnection);
 QObject::connect(this, SIGNAL(SignalDepictVisitorsAndCars(std::vector<VisitorInfo>&)), 
     this, SLOT(DepictVisitorsAndCars(std::vector<VisitorInfo>&)));
 QObject::connect(this, SIGNAL(SignalDepictFaceGrab(FaceInfo)), this, SLOT(DepictFaceGrab(FaceInfo)));

4.13.3 通过typedef进行信号槽函数的注册

在上述的代码片段中使用注册std::vector挺长的,因此在注册时可以在文件头进行别名的定义,如下:

typedef std::vector<VisitorInfo> VecVis;

这样在注册时,使用下述代码:

qRegisterMetaType<VecVis>("VecVis");

即可

4.13.4 Qt类型做信号槽参数

另外的策略就是尽量尽量使用Qt自带的类型做信号槽参数,在开发过程中,同事使用了std::string作为信号槽的参数,这便是对Qt不友好的行为,因为Qt自定义了QString类型,可以非常自然的做信号槽参数,而且不需要注册。
Qt同时也定义了许多丰富的类型,例如QMap,QVector,QList等方便开发者使用。

https://blog.csdn.net/wadfji/article/details/54406767
https://blog.csdn.net/Q1302182594/article/details/50527543

4.14 信号的主动发送

void BuildingDisplay::DealWithSignals(DoorCard doorCard)
{
    emit signalDepictDoorCard(doorCard);
}

4.15 内存释放

#define SAFENEW new(std::nothrow)
#define SAFECALL(fun) { try { fun; } catch (...) {} }
#define SAFEDELETE(p) { if ((p) != NULL) { SAFECALL(delete (p)); (p) = NULL; } }
#define SAFEARRAYDELETE(p) { if ((p) != NULL) { SAFECALL(delete [](p)); (p) = NULL; } }

4.16 公共库撰写

#ifndef _COMMONTOOLS_H_
#define _COMMONTOOLS_H_

#pragma once

#include <list>
#include <vector>
#include <wtypes.h>

#define SAFENEW new(std::nothrow)
#define SAFECALL(fun) { try { fun; } catch (...) {} }
#define SAFEDELETE(p) { if ((p) != NULL) { SAFECALL(delete (p)); (p) = NULL; } }
#define SAFEARRAYDELETE(p) { if ((p) != NULL) { SAFECALL(delete [](p)); (p) = NULL; } }

//! 通用基础工具集
/*!
@defgroup CommonTools
*/
class CommonTools
{
public:
       //! 转换time_t 类型为SYSTEMTIME 类型
    /*!
    @param[in] t 待转换的时间
    @param[out] fTime 转换后的时间
    @return 成功时返回,失败时返回错误码,可参见Errno.h
    @remarks
    转换只会对wYear、wMonth、wDay、wHour、wMinute、wSecond 的内容进行处理,wDayOfWeek 与wMilliseconds 将置为
     */
    static int Timet2SystemTime(time_t t, SYSTEMTIME& fTime);

    //! 将时间字符串转换成time_t 类型
    /*!
    @param[in] strTime 时间字符串
    @return 成功返回转换后的数据,否则返回-1
    @remarks
    时间字符串格式必须为YYYYMMDDHHNNSS 例如:
    > 20140201152300
     */
    static time_t DateTimeStr2Time(const char *strTime);

    //! 将时间字符串转换成time_t 类型
    /*!
    @param[in] strTime 时间字符串
    @return 成功返回转换后的数据,否则返回-1
    @remarks
    时间字符串格式须为YYYY-MM-DD HH:NN:SS 类似的形式,
    其中'-'、' '、':'不是必须如此使用,可以用其它任意单个字符代替,但必须存在且位置数量不能改变
    例如:
    > 2014-02-01 15:23:00 //OK\n
    > 2014 02 01 15 23 23 //OK\n
    > 2014y02m01d15h23n32 //OK
     */
    static time_t DateTimeStr2Time2(const std::string& strTime);

    //! 将time_t 类型转换成时间字符串
    /*!
    @param[in] t 待转换的时间数据
    @return 返回转换后的时间字符串
    @remarks
    结果时间字符串格式为YYYYMMDDHHNNSS,例如
    > 20140201232001
    @see CommonTools::DateTimeStr2Time
     */
    static std::string Time2DateTimeStr(time_t t);

    //! 将time_t 类型转换成时间字符串
    /*!
    @param[in] t 待转换的时间数据
    @return 返回转换后的时间字符串
    @remarks
    结果时间字符串格式为YYYY-MM-DD HH:NN:SS,例如
    > 2014-02-01 23:20:01
    @see CommonTools::DateTimeStr2Time2
     */
    static std::string Time2DateTimeStr2(time_t t);

    //! 将time_t 类型转换成时间字符串
    /*!
    @param[in] t 待转换的时间数据
    @return 返回转换后的时间字符串
    @remarks
    结果时间字符串格式为YYYY-MM-DD_HH-NN-SS,例如
    > 2014-02-01_23-20-01
     */
    static std::string Time2DateTimeStr3(time_t t);

    //! 获取文件大小
    /*!
    @param[in] file 文件名
    @return 成功返回文件的大小,否则返回-1
     */
    static unsigned long long GetFileSize(const std::string& file);

    //! 创建一个新的GUID
    /*!
    @return 新的GUID字符串
    @remarks
    字符串格式为:
    > A2D905FE-804E-40ec-B892-43D437F4ECC7\n
    > 字符串不包括头尾的{},同时第三段中的进制字符串以小写表示
     */
    static std::string NewGuid();
};

#endif /*_COMMONTOOLS_H_*/

4.17 开发崩溃

4.17.1 Stack Overflow

在项目进行中,需要分配空间保存访客信息列表,分配了数组

char szResponse[1024*1024] = {0};//保存所有的访客信息1M

//程序会崩溃,弹出stack overflow崩溃弹窗

test dword ptr [eax],eax ; probe page.

解决方式:
编译时不报错,但是运行时总是报出现中断异常。然后我就调试,发现在申明int left[100000];的这一句,再往下跳就报错,且停留在汇编语句”test dword ptr [eax],eax ; probe page.”上,原来是堆栈溢出了。
这跟局部数组变量定义所分配的最大空间设置大小有关。局部变量的申请空间是存放于栈中,windows里默认栈内存是1M,所以当申请空间大于1M时就会出现溢出错误。
在VS中:项目->属性->链接器->系统->堆栈保留大小修改默认值,如下图修改为2M
这里写图片描述

4.17.2 R6034
这里写图片描述
该问题未解决

4.17.3 断点不击打

在开发过程中,使用VS进行编程,调试的过程中会发现有些断点无法命中,提示调试信息不一致。
在Release模式下,保证调试模式相关配置正确配置

4.17.3.1 C/C++

这里写图片描述

4.17.3.2 链接器
这里写图片描述

4.17.3.3 断点函数

修改断点所在函数的代码,一种可能的原因是由于断点所在的函数没有任何变化,因此在编译链接时,该部分的函数链接使用的是之前的内容。因此可以通过在函数断点前后增加输出语句或者日志打印语句,强制重新编译链接,一行不行就多输出几行重新编译,即可命中断点。

4.17.4 访问冲突

DisplayMain.exe 中的 0x5d0038d0 处未处理的异常: 0xC0000005: 读取位置 0x00000004 时发生访问冲突。

4.17.4.1 回调传入空指针

同事在设置回调时传入了空指针,而引发了访问冲突,在进行代码调试时,主要观察函数的参数是否符合预期。

4.17.4.2 信号槽未指定连接方式

QObject::connect(this, SIGNAL(signalDepictDoorCard(DoorCard&)), this, SLOT(DepictDoorCard(DoorCard&)), Qt::QueuedConnection);

需要指定连接时的类型。

bool QObject::connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)

可选的类型参数描述了建立的连接类型,尤其是,它确定了一个具体的信号是立刻发送给槽还是放入队列稍后发送。如果信号放入队列,参数必须是Qt meta-object系统知道的类型,因为Qt需要在幕后进行参数的拷贝到事件中,如果你尝试使用队列queued连接并获取错误信息:

QObject::connect: Cannot queue arguments of type 'MyType'
(Make sure 'MyType' is registered using qRegisterMetaType().)

调用qRegisterMetaType()进行类型注册,在你建立连接之前。
参见

http://doc.qt.io/archives/qt-4.8/qmetatype.html
http://doc.qt.io/archives/qt-4.8/qmetatype.html#Q_DECLARE_METATYPE

4.18 函数返回值定义

typedef int DRV_RET;
//error define
#define DRV_ERR_SUCCESS                       0 //成功
#define DRV_ERR_INVALID_ARG                   1 //错误参数
#define DRV_ERR_NOT_ENOUGH_MEMORY             2 //没有足够内存
#define DRV_ERR_NOT_SUPPORTED                 3 //不支持
#define DRV_ERR_DRIVE_UNLOADED                4 //未加载驱动
#define DRV_ERR_DRIVE_LOAD_FAILED             5 //加载驱动失败
#define DRV_ERR_NOT_IMPT                      6 //未实现
#define DRV_ERR_ERROR                         7 //未知错误
#define DRV_ERR_NOT_FOUND                     8 //未找到
#define DRV_ERR_CONNECT_FAILED                9 //连接失败
#define DRV_ERR_INIT_FAILED                  10 //初始化失败
#define DRV_ERR_LAST                        155

在定义类时可简单进行如下的定义:在下述的部分类中,有单例模式的演示,也有一些函数声明,在函数声明中可以看到上述定义的返回值的用法,比较正规。

class DeviceManager : public IDriver, public IGlbStatAbility, public IServerAbility
{
public:
    static DeviceManager& Instance()
    {
        static DeviceManager d;
        return d;
    }
    virtual ~DeviceManager();

    bool Init();
    void UnInit();

    virtual DRV_RET GetDevice(const std::string& Code, DevicePtr & pDev);
    virtual DRV_RET GetDevice(const std::string& Code, DevicePtr & pDev, CChannelInfos& Info);   
    virtual DRV_RET AddDevice(DeviceInfo& Dev, PicSvrInfo& Pic, CChannelInfos& Chas, std::string& Msg);
    virtual DRV_RET DelDevice(const std::string& DevIndexCode, std::string& Msg);
    virtual DRV_RET ModDevice(DeviceInfo& Dev, PicSvrInfo& Pic, CChannelInfos& Chas, std::string& Msg);
    virtual DRV_RET SetPicSvr(const std::string& SvrIndexCode, CDomainInfos& Domains, std::string& Msg);
    virtual DRV_RET SetDevicePicSvrInfo(stringlist& DevIndexs, PicSvrInfo& Pic, std::string& Msg);
    virtual DRV_RET GetMonitorData(unsigned int Delay, unsigned int Freq, std::string& Msg);

    virtual DRV_RET GetAllDevStatus(CConnectStatues& statues, std::string& Msg);

    virtual void PostEvent(PDevice pDev, const std::string& ability, const EventInfo& ei);

protected:
    DeviceManager();

private:
    typedef std::map<std::string, PDevice> CDevices;  //key为设备indexcode
    typedef std::map<std::string, CDomainInfos> CSvrDomainInfos;
};

4.19 注意事项

4.19.1 VS开发配置

4.19.1.1 开发配置

开发时可以使用的背景如下:
工具–>选项–>环境–>字体和颜色:
这里写图片描述
项目背景色点击自定义,设置如下:
这里写图片描述
色调84、饱和度91、亮度205。

4.19.1.2 常用快捷键

VS中常用的快捷键有
Ctrl + J 强制智能提示,很便捷

4.19.2 NotePad++开发配置

4.19.2.1 快捷键重新读取
这里写图片描述
这里写图片描述

4.19.2.2 背景色设置

设置–>语言格式设置
这里写图片描述
模式是色调85, 饱和度120, 亮度208

4.20 回调函数

在项目进行过程中,需要使用回调函数的机制,大体上是我不断的接收平台发过来的回调,然后进行解析,如果平台数据有更新,则需要主动的去调用以获取数据。
使用回调函数实际上就是在调用某个函数(通常是 API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK(相当于FAR PASCAL),这主要是说明该函数的调用方式。
注意::回调函数就好像是一个中断处理函数,系统在符合你
设定的条件时自动调用。为此,你需要做三件事:
1. 声明;
2. 定义;
3. 设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。一般会有设置回调函数供我们调用
声明和定义时应注意:回调函数由系统调用,所以可以认为它属于WINDOWS系统,不要把它当作你的某个类的成员函数。

4.20.1 回调函数的定义
一般在项目开发过程中,需要使用回调机制的场景中,主调方要把头文件提供给开发者,在头文件中包含回调函数的函数标签,参数类型以及设置回调的函数,可参见如下的示例:

#ifndef _MQUTILS_H
#define _MQUTILS_H

#include <string>
using namespace std;

#   if defined MQUTILS_EXPORTS
#       define MQUTILS_DECLARE extern "C" __declspec(dllexport)
#   else
#       define MQUTILS_DECLARE extern "C" __declspec(dllimport)
#   endif
#   define MQUTILS_API __stdcall

namespace NAME_MQUTILS
{
    /** @fn         DevEventCallBack
    *  @brief      回调函数
    *  @para       [OUT]char* szDataInfo  json格式字符串
    *  @para       [OUT]int iDataLen     字符串长度
    *  @para       [OUT]void* pUserData     设置回调时传入的用户自定义数据
    *  @return     void
    */
    typedef void (_stdcall* DevEventCallBack)(char* szDataInfo, int iDataLen, void* pUserData);
    ……
/** @fn         SetACSEventCallback
    *  @brief      设置门禁事件回调
    *  @para       DevEventCallBack pFun    回调函数指针
    *  @para       void* pUserData     用户自定义数据
    *  @return     void
    */
    MQUTILS_DECLARE void MQUTILS_API SetACSEventCallback(DevEventCallBack pFun, void* pUserData);
#endif

可以看到在上述头文件中定义了回调函数的形式[与惯常用法类似],该函数指针DevEventCallBack带有CallBack字样,并且参数为char*指针,int长度,和一个用户类型的指针。同时之后的是设置门禁事件的回调函数的形式。

4.20.2 回调函数的定义和设置

首先我们需要进行回调函数定义,一般回调函数会定义成全局函数,定义的方式也比较简单,只需要拷贝回调函数指针的声明,并修改成合适的函数名即可。

/*typedef*/ void /*(_stdcall* DevEventCallBack)*/DoorCardCallBack (char* szDataInfo, int iDataLen, void* pUserData)
{

}

可以看到,仅仅是把typedef删掉,并且把(_stdcall* DevEventCallBack)替换为DoorCardCallBack函数名即为定义的回调函数,然后添加函数体即完成了回调函数的定义,以门禁回调函数为例:

void DoorCardCallBack(char* szDataInfo, int iDataLen, void* pUserData)
{
    DISPLAYMAIN_TRACE("DoorCardCallBack Func Starts");
    DoorCard doorCard;
    BuildingDisplay *bd = static_cast<BuildingDisplay*>(pUserData);
    if (!CommonTools::JsonToAcsUpdate(szDataInfo, iDataLen, doorCard))
    {
        DISPLAYMAIN_ERROR("ASC Information Parse Fails");
        return ;
    }
   //在下述函数中发射信号
   bd->DealWithDoorCardSignals(doorCard);

}

然后需要在程序开始运行之前,在合适的地方进行回调函数的设置,调用主调方头文件中的setCallBack之类的函数即可,如下例:

NAME_MQUTILS::SetACSEventCallback((NAME_MQUTILS::DevEventCallBack)DoorCardCallBack, this);
    NAME_MQUTILS::SetRVSEventCallback((NAME_MQUTILS::DevEventCallBack)VisitorUpdateCallBack, this);
    NAME_MQUTILS::SetFaceEventCallback((NAME_MQUTILS::DevEventCallBack)FaceGrabCallBack, this);

这样就完成了回调函数的定义和设置了,在程序运行时,如果主调方调用了定义的回调函数,会自动进入回调函数的函数体中,开发者需要以该回调函数作为交互的位置进行合适的代码控制。
4.20.3 参数定义
在项目开始时,有几个非常困惑的问题,
1, 回调函数如何定义
2, 回调函数在何时进行设置
3, 回调函数的参数是由开发者进行主动的创建[通过New或者char szMsg[128]在堆栈中生成内存空间]还是由调用回调函数的一方进行内存的分配,这个问题也很困惑
第三个问题,也是在使用了回调函数自己才明白,因为回调函数不是自己主动调用的,所以内存空间并不由自己分配和管理,因此结论是回调函数中的参数内存空间自己是不用操心的,而是由调用回调函数的一方主动分配和释放。回调函数定义方只要对回调函数进行解析即可。
所以在上述的DoorCardCallBack函数中,可以观察到,szDataInfo,iDataLen, pUserData等参数在函数执行时就已经被赋予了值,而这些值所占据的内存空间并没有在函数中进行分配和管理。
在使用WebService拉取访客信息时使用了下面的函数

/** @fn         WS_QueryVistorInfo
    *  @brief      查询访客信息(包含车牌信息)
    *  @para       const char* szBeginTime    查询时间范围的开始
    *  @para       const char* szEndTime     查询时间范围的结束
    *  @para       const char* szDestination     目的地
    *  @para       [out]char* szResponse     查询结果返回,json格式字符串
    *  @para       int iResponseLength     查询结果字符串存储的内存空间大小,单位:字节
    *  @return     void
    */
    CMSWSUTILS_DECLARE void CMSWSUTILS_API WS_QueryVistorInfo(const char* szBeginTime, const char* szEndTime, const char* szDestination, char* szResponse, int iResponseLength);

该函数的主要作用是通过平台获取访客列表数据。由于该函数是由自己主动调用的,因此对于szResponse参数而言,需要回调函数定义方主动分配,在开发时,使用了如下的方式:

char szResponse[1024*1024] = {0};

因此结论如下:
回调函数的参数内存管理回调函数定义方不用管。
而对于主动调用的函数,参数内存管理则由主动调用方来管理

5 代码片段

下述的代码片段是使用rapidjson解析json字符串的过程,如下所示:

CommonTools.h
#pragma once
#ifndef COMMONTOOLS_H
#define COMMONTOOLS_H

#include <vector>

#include <QWidget>
#include <QString>
#include "VisitorInfo.h"
#include "DoorCard.h"
#include "document.h"
#include "rapidjson.h"
#include "FaceInfo.h"

#define SAFENEW new(std::nothrow)
#define SAFECALL(fun) { try { fun; } catch (...) {} }
#define SAFEDELETE(p) { if ((p) != NULL) { SAFECALL(delete (p)); (p) = NULL; } }
#define SAFEARRAYDELETE(p) { if ((p) != NULL) { SAFECALL(delete [](p)); (p) = NULL; } }

using namespace RAPIDJSON_NAMESPACE;

class CommonTools
{
public:
    //CommonTools(void);
    //~CommonTools(void);
    //访客信息JSON字符串转换格式
    static bool JsonToVisitorInfo(char* szResponse,std::vector<VisitorInfo> &visitorList);

    //访客更新事件判断是否需要更新
    static bool JsonToVisitUpdate(char* szDataInfo, int iDataLen);

    //停车场事件JSON字符串转换
    static bool JsonToParkUpdate(char* szDataInfo, int iDataLen,std::string &sCarNumPic,std::string &sCarBodyPic);

    //门禁上报事件JSON字符串转换
    static bool JsonToAcsUpdate(char* szDataInfo, int iDataLen,DoorCard& doorCard);

    //人脸抓拍事件JSON字符串转换
    static bool JsonToFaceUpdate(char* szDataInfo, int iDataLen,FaceInfo& faceInfo);

    static void FillWidgetWithBg(QWidget *widget, QString& fileName);

};
#endif /*COMMONTOOLS_H*/

CommonTools.cpp

#include "CommonTools.h"
#include "hlog1.h"
#include <string>
using std::string;


bool CommonTools::JsonToVisitorInfo(char* szResponse,std::vector<VisitorInfo> &visitorList)
{
    Document document;
    bool ret = true;
    do 
    {
        if (document.Parse(szResponse).HasParseError())
        {
            DISPLAYMAIN_ERROR("VisitorUpdateCallBack Gets wrong json, Parse Fails");
            ret= false;
            break;
        }
        if (!document.HasMember("VisitorsInfo"))
        {
            DISPLAYMAIN_ERROR("visitorinfo Should Have Member VisitorInfo");
            ret = false;
            break;
        }
        Value& vVisitorInfoNode = document["VisitorsInfo"];
        if (!vVisitorInfoNode.IsArray())
        {
            DISPLAYMAIN_ERROR("vVisitorInfoNode Shoulud Be an Array");
            ret = false;
            break;
        }

        std::string sID;
        std::string sSex;
        std::string sName;
        std::string sCarName;
        std::string sArriveTime;
        std::string sValidTime;
        std::string sDestination;
        std::string sCardNum;//卡号
        for (SizeType i=0; i!=vVisitorInfoNode.Size(); i++)
        {   
            Value& VNode = vVisitorInfoNode[i];
            if ((!VNode.HasMember("IdentityCardNum")) || (!VNode["IdentityCardNum"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member and Type Be String");
                ret = false;
                break;
            }
            sID = VNode["IdentityCardNum"].GetString();
            if ((!VNode.HasMember("Sex")) || (!VNode["Sex"].IsString()))
            {
                ret = false;
                break;
            }
            Value& sexNode = VNode["Sex"];
            sSex = sexNode.GetString();
            if ((!VNode.HasMember("VisitorName")) || (!VNode["VisitorName"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member VisitorName and Type Be String");
                ret = false;
                break;
            }
            sName = VNode["VisitorName"].GetString();
            if ((!VNode.HasMember("CarNum")) || (!VNode["CarNum"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member CarNum and Type Be String");
                ret = false;
                break;
            }
            sCarName = VNode["CarNum"].GetString();
            if ((!VNode.HasMember("CurrentTime")) || (!VNode["CurrentTime"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member CurrentTime and Type Be String");
                ret = false;
                break;;
            }
            sArriveTime = VNode["CurrentTime"].GetString();
            if ((!VNode.HasMember("InvalidTime")) || (!VNode["InvalidTime"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member InvalidTime and Type Be String");
                ret = false;
                break;
            }
            sValidTime = VNode["InvalidTime"].GetString();
            if ((!VNode.HasMember("Destination")) || (!VNode["Destination"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member Destination and Type Be String");
                ret = false;
                break;
            }
            sDestination = VNode["Destination"].GetString();

            if ((!VNode.HasMember("CardNum")) || (!VNode["CardNum"].IsString()))
            {
                DISPLAYMAIN_ERROR("vVisitorInfoNode Should Have Member CardNum and Type Be String");
                ret = false;
                break;
            }
            sCardNum = VNode["CardNum"].GetString();
            VisitorInfo visitor(sName, sDestination, sArriveTime, sValidTime, sCarName,true, sCardNum, sID);
            visitorList.push_back(visitor);
        }
        if (!ret)
        {
            break;
        }

    } while (0);

    return ret;
}

bool CommonTools::JsonToVisitUpdate(char* szDataInfo, int iDataLen)
{
    string strJson(szDataInfo, iDataLen);
    Document document;
    bool ret = true;
    do 
    {
        if (document.Parse(strJson.c_str()).HasParseError())
        {
            DISPLAYMAIN_ERROR("VisitorUpdateCallBack Gets wrong json, Parse Fails");
            ret = false;
            break;
        }
        if ((!document.HasMember("Type")) || (!document["Type"].IsInt()) || (!document.HasMember("Changed")) || (!document["Changed"].IsInt()))
        {
            DISPLAYMAIN_ERROR("VisitorUpdate MQEvent Have Wrong Format");
            ret = false;
            break;
        }
        int type = document["Type"].GetInt();
        int changed = document["Changed"].GetInt();
        if (type != 4)
        {
            DISPLAYMAIN_ERROR("VisitorUpdateCallBack Type Should Be Type 4");
            ret = false;
            break;
        }
        if (changed != 1)
        {
            //Visitor Infomation Doesnot change
            DISPLAYMAIN_INFO("Visitor Infomation Doesnot Change");
            ret = false;
            break;
        }
        ret = true;
    } while (0);
    return ret;

}


bool CommonTools::JsonToParkUpdate(char* szDataInfo, int iDataLen,std::string &sCarNumPic,std::string &sCarBodyPic)
{
    bool ret = true;
    std::string strJson(szDataInfo, iDataLen);
    Document document;

    do 
    {
        if (document.Parse(strJson.c_str()).HasParseError())
        {
            DISPLAYMAIN_ERROR("Park Event Uploaded Has Wrong Json Format");
            ret = false;
            break;
        }
        if ((!document.HasMember("Type")) || (!document.HasMember("CarInfo")) || (!document["Type"].IsInt()))
        {
            DISPLAYMAIN_ERROR("ParkEvent Info Should Have Member CarInfo and Type and Correct Type");
            ret = false;
            break;
        }
        int type = document["Type"].GetInt();
        if (type != 2)
        {
            DISPLAYMAIN_ERROR("Park Event Type != 2");
            ret = false;
            break;
        }

        Value& vCarInfoNode = document["CarInfo"];
        if (!(vCarInfoNode.IsObject()))
        {
            DISPLAYMAIN_ERROR("vCarINfoNode Should Be an Object");
            ret = false;
            break;
        }

        if ((!(vCarInfoNode.HasMember("CarNumPic"))) || (!(vCarInfoNode["CarNumPic"].IsString())))
        {
            DISPLAYMAIN_ERROR("vCarInfoNode Should Have Member CarNumPic and Correct Type");
            ret = false;
            break;
        }

        sCarNumPic = vCarInfoNode["CarNumPic"].GetString();
        if ((!(vCarInfoNode.HasMember("CarBodyPic"))) || (!(vCarInfoNode["CarBodyPic"].IsString())))
        {
            DISPLAYMAIN_ERROR("vCarInfoNode Should Have Member CarBodyPic and Correct Type");
            ret = false;
            break;
        }
        sCarBodyPic = vCarInfoNode["CarBodyPic"].GetString();
        ret = true;
        break;
    } while (0);
    return ret;
}

bool CommonTools::JsonToAcsUpdate(char* szDataInfo, int iDataLen, DoorCard& doorCard)
{
    bool ret = true;
    Document document;
    std::string strJson(szDataInfo, iDataLen);
    do 
    {
        if (document.Parse(strJson.c_str()).HasParseError())
        {
            DISPLAYMAIN_ERROR("ASC Evnet Uploaded has Wrong Json Format");
            ret = false;
            break;
        }

        if ((!document.HasMember("Type")) || (!document.HasMember("EventInfo")) || (!document["Type"].IsInt()) || (!document["EventInfo"].IsObject()))
        {
            DISPLAYMAIN_ERROR("ASCEvent Should Have Member Type and EventInfo and Correct Member Type");
            ret = false;
            break;
        }
        int type = document["Type"].GetInt();
        if (3 != type)
        {
            DISPLAYMAIN_ERROR("ASCEvent Type Should Be Equal with 3");
            ret = false;
            break;
        }
        Value& vEventInfoNode = document["EventInfo"];
        if ((!vEventInfoNode.HasMember("PersonName")) || (!vEventInfoNode["PersonName"].IsString()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member PersonName or Correct PersonName Type");
            ret = false;
            break;
        }
        Value& vNameNode = vEventInfoNode["PersonName"];
        doorCard.m_strPersonName = vNameNode.GetString();
        if ((!vEventInfoNode.HasMember("CardNum")) || (!vEventInfoNode["CardNum"].IsString()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member CardNum or Correct CardNum Type");
            ret = false;
            break;
        }
        Value& vCardNumNode = vEventInfoNode["CardNum"];
        doorCard.m_strCardNum = vCardNumNode.GetString();
        if ((!vEventInfoNode.HasMember("Sex")) || (!vEventInfoNode["Sex"].IsString()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member Sex or Correct Sex Type");
            ret = false;
            break;
        }
        Value& vSexNode = vEventInfoNode["Sex"];
        doorCard.m_strSex = vSexNode.GetString();
        if ((!vEventInfoNode.HasMember("IdentityCardNum")) || (!vEventInfoNode["IdentityCardNum"].IsString()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member IdentityCardNum or Correct IdentityCardNum Type");
            ret = false;
            break;
        }
        Value& vIdentityCardNode = vEventInfoNode["IdentityCardNum"];
        doorCard.m_strIdentityCardNum = vIdentityCardNode.GetString();
        if ((!vEventInfoNode.HasMember("SnapPic")) || (!vEventInfoNode["SnapPic"].IsString()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member SnapPic or Correct SnapPic Type");
            ret = false;
            break;
        }
        Value& sSnapPicNode = vEventInfoNode["SnapPic"];
        doorCard.m_strSnapPic = sSnapPicNode.GetString();

        if ((!vEventInfoNode.HasMember("DoorId")) || (!vEventInfoNode["DoorId"].IsString()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member DoorId or Correct DoorId Type");
            ret = false;
            break;
        }
        Value& vDoorIdNode = vEventInfoNode["DoorId"];
        doorCard.m_strDoorId = vDoorIdNode.GetString();

        if ((!vEventInfoNode.HasMember("IsVisitor")) || (!vEventInfoNode["IsVisitor"].IsInt()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member IsVisitor or Correct IsVisitor Type");
            ret = false;
            break;
        }
        doorCard.m_bIsVisitor = vEventInfoNode["IsVisitor"].GetInt()==1?true:false;

        if ((!vEventInfoNode.HasMember("IsInsider")) || (!vEventInfoNode["IsInsider"].IsInt()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member IsInsider or Correct IsInsider Type");
            ret = false;
            break;
        }
        doorCard.m_bIsInsider = vEventInfoNode["IsInsider"].GetInt()==1 ? true: false;

        if ((!vEventInfoNode.HasMember("IsAuthorize")) || (!vEventInfoNode["IsAuthorize"].IsInt()))
        {
            DISPLAYMAIN_ERROR("vEventInfoNode Doesnot Have Member IsAuthorize or Correct IsAuthorize Type");
            ret = false;
            break;
        }
        doorCard.m_bIsAuthorize = vEventInfoNode["IsAuthorize"].GetInt()==1 ? true: false;
        ret = true;
        break;
    } while (0);
    return ret;  
}

bool CommonTools::JsonToFaceUpdate(char* szDataInfo, int iDataLen,FaceInfo& faceInfo)
{
    bool ret = false;
    Document document;
    std::string strJson(szDataInfo, iDataLen);
    do 
    {
        if (document.Parse(strJson.c_str()).HasParseError())
        {
            DISPLAYMAIN_ERROR("FaceGrab Event Has Wrong Json Format");
            ret = false;
            break;
        }

        if (!document.HasMember("Type") || !document.HasMember("FaceInfo") || !document["Type"].IsInt() || !document["FaceInfo"].IsObject())
        {
            DISPLAYMAIN_ERROR("FaceGrab Event Should Have Member Type and FaceInfo and Correnct Member Type");
            ret = false;
            break;
        }
        int type = document["Type"].GetInt();
        if (1 != type)
        {
            DISPLAYMAIN_ERROR("FaceGrap Event Type Sholuld Be Equal to 1");
            ret = false;
            break;
        }

        Value& vFaceInfoNode = document["FaceInfo"];

        if (!vFaceInfoNode.HasMember("Name") || !vFaceInfoNode["Name"].IsString())
        {
            DISPLAYMAIN_ERROR("vFaceInfoNode Should Have Name Member and Correct Name Type");
            ret = false;
            break;
        }
        faceInfo.m_strPersonName = vFaceInfoNode["Name"].GetString();

        if (!vFaceInfoNode.HasMember("IdentityCardNum") || !vFaceInfoNode["IdentityCardNum"].IsString())
        {
            DISPLAYMAIN_ERROR("vFaceInfoNode Should Have IdentityCardNum Member and Correct IdentityCardNum Type");
            ret = false;
            break;
        }
        faceInfo.m_strIdentityCardNum = vFaceInfoNode["IdentityCardNum"].GetString();

        if (!vFaceInfoNode.HasMember("Sex") || !vFaceInfoNode["Sex"].IsString())
        {
            DISPLAYMAIN_ERROR("vFaceInfoNode Should Have Sex Member and Correct Sex Type");
            ret = false;
            break;
        }
        faceInfo.m_strSex = vFaceInfoNode["Sex"].GetString();

        if (!vFaceInfoNode.HasMember("Similarity") || !vFaceInfoNode["Similarity"].IsDouble())
        {
            DISPLAYMAIN_ERROR("vFaceInfoNode Should Have Similarity Member and Correct Similarity Type");
            ret = false;
            break;
        }
        faceInfo.m_strSimilarity = vFaceInfoNode["Similarity"].GetDouble();

        if (!vFaceInfoNode.HasMember("SnapPic") || !vFaceInfoNode["SnapPic"].IsString())
        {
            DISPLAYMAIN_ERROR("vFaceInfoNode Should Have SnapPic Member and Correct SnapPic Type");
            ret = false;
            break;
        }
        faceInfo.m_strSnapPic = vFaceInfoNode["SnapPic"].GetString();

        if (!vFaceInfoNode.HasMember("BlackPic") || !vFaceInfoNode["BlackPic"].IsString())
        {
            DISPLAYMAIN_ERROR("vFaceInfoNode Should Have BlackPic Member and Correct BlackPic Type");
            ret = false;
            break;
        }
        faceInfo.m_strBlackPic = vFaceInfoNode["BlackPic"].GetString();
        ret = true;
        break;

    } while (0);
    DISPLAYMAIN_TRACE("CommonTools::JsonToFaceUpdate Exit");
    DISPLAYMAIN_DEBUG("ret: %d", ret);
    return ret;
}

void CommonTools::FillWidgetWithBg(QWidget *widget, QString& fileName)
{
    widget->setAutoFillBackground(true);
    QPixmap bgMap(fileName);
    /*qDebug()<<fileName;*/
    QPalette bgPalette;
    bgPalette.setBrush(QPalette::Background,QBrush(bgMap.scaled(widget->size(),
        Qt::IgnoreAspectRatio,
        Qt::SmoothTransformation)));
    widget->setPalette(bgPalette);
}

6 引用

https://blog.csdn.net/rl529014/article/details/51344881

https://blog.csdn.net/t46414704152abc/article/details/51057946

猜你喜欢

转载自blog.csdn.net/lk142500/article/details/81109083