基于实例快速理解Qt模型视图的应用


Qt的开发过程中,经常会应用到它的模型视图结构,对于初学者来说,马上理解其原理,并且进行基础的应用,还是比较难的。另外,通过网络搜索查看别人发表有关模型视图的介绍,基本上都不能解决个人的一些疑惑。基于此,本文将结合实例来说明模型设计的应用,以便能够更加深刻的理解Qt模型视图的设计原理。

任何知识,如果想要能够应用自如,个人觉得首先需要对该知识,需要有一个整体的认识。所以,首先将对模型视图结构进行总体的说明,然后再结合实例来说明其应用,并且重点介绍代理的两种应用方式,最后再进行总结。

一、模型视图简介

Qt的模型视图与MVC设计模式类似,但是又不相同,MVC设计模式包括模型、视图和控制。模型表示数据,视图表示用户界面,控制定义了用户的操作。它能够有效将数据和显示分离,提高了代码的灵活性。Qt的模型视图同样有这样的效果。但是Qt的模型视图将视图和控制放在一起,以便简化框架。另外,为了能够更好的处理用户输入,Qt的模型视图加入的代理。通过代理能够自定义item的显示和编辑。

Qt的模型视图结构如下图所示,主要包含三个部分,分别为模型、视图和代理。模式与数据源通信,给其他部件提供接口。视图从模型中通过ModelIndex获取信息,代理用来显示和编辑item。

二、模型

模型的抽象基类为QAbstractItemModel,  下图显示了其继承关系。

QAbstractTableModel是表格的抽象基类,QAbstractListMode的列表的抽象基类。QAbstractProxyModel是代理的抽象基类。QDirModel是文件和目录的模型。QSqlQueryModel是有关数据库的模型。

模型的数据可以直接存在模型中,也可以由外部输入,由独立的类管理,还可以存在文件中,甚至是数据库。

模型中角色的功能是,根据不同的角色提供不同的数据。支持以下几种角色。

1、Qt::DisplayRole,   显示文字

2、Qt::DecorationRole,绘制数据

3、Qt::EditRole,  编辑器中的数据

4、Qt::ToolTipRole, 工具提示

5、Qt::StatusTipRole, 状态栏提示

6、Qt::SizeHintRole, 尺寸提示

7、Qt::FontRole, 字体

8、Qt::TextAlignmentRole,  对齐方式

9、Qt::BackgroundRole, 背景画刷

10、Qt::ForegroundRole, 前景画刷

11、Qt::CheckStateRole, 检查框状态

12、Qt::UseRole, 用户自定义数据的起始位置

三、视图

视图的抽象基类为QAbstractItemView,  它由五个基本视图类组成,分别是QTreeeView、QHeaderView、QListView、QColumnView和QTableVie w。为了用户简单方便的使用,还提供了三个模式视图集成的类,分别是QTreeWidget、QListWidget、QTableWidget。对于变动不大,并且简单的需求可以采用这三个类来快速开发。但是对于变动比较大的需求,就不建议使用这三个类,因为它们缺乏灵活性。

四、代理

代理的抽象基类为QAbstractItemDelegate,  如果需要改变item的显示或者item的编辑行为,那么可以考虑自定义代理类。

一般自定义代理类是继承QItemDelegate可以满足大部分的需求,如果直接继承QAbstractItemDelegate,则需要更多的开发工作量。

如果想要改变item的显示,那么可以通过继承QItemDelegate,然后重载paint。

如果想要改变item的编辑行为,同样的可以继承QItemDelegate,然后重载createEditor、setEditorData、setModelData和updateEditorGeometry。

下面的实例将详细介绍代理的这两种应用方式。

五、实例

首先实现模型QAbstractTableModel和表格QtableView的结合显示数据信息的实例。为了代码的清晰度,这里model直接存储了数据。

JWeaponModel模型的定义如下所示,rowCount返回行数,columnCount返回列数,data实现返回item的数据,headerData则是实现返回标题信息。flags和setData函数是为了支持代理而添加的,后面会讲解其作用。这里可以暂时不需要太关注。

class JWeaponModel : public QAbstractTableModel
{
public:
    JWeaponModel(QObject *parent = 0);

    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
    virtual int columnCount(const QModelIndex& parent = QModelIndex()) const;
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData(int section, Qt::Orientation orentation, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;

    bool setData(const QModelIndex &index, const QVariant &value, int role);

private:
    void InitData();

private:
    QVector<short>          m_army;         // 军种索引
    QVector<short>          m_weapon;       // 武器索引
    QMap<short, QString>    m_MapArmy;      // 军种映射表
    QMap<short, QString>    m_MapWeapon;    // 武器映射表
    QStringList             m_header;
};

JWeaponModel模型的实现如下,columnCount默认写死显示三列。date根据角色Qt::DisplayRole来说显示不同数据条目的数据。headerData返回水平标题信息。

JWeaponModel::JWeaponModel(QObject *parent)
    : QAbstractTableModel(parent)
{
    m_MapArmy[1] = tr("海军");
    m_MapArmy[2] = tr("空军");

    m_MapWeapon[1] = tr("战斗机");
    m_MapWeapon[2] = tr("轰炸机");

    InitData();

}

int JWeaponModel::rowCount(const QModelIndex& parent) const
{
    return m_MapArmy.size();
}

int JWeaponModel::columnCount(const QModelIndex& parent) const
{
    return 3;
}

QVariant JWeaponModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    if (role == Qt::DisplayRole)
    {
        switch (index.column())
        {
            case 0:
            {
                return m_MapArmy[m_army[index.row()]];
            }
            case 1:
            {
                return m_MapWeapon[m_weapon[index.row()]];
            }
            default:
            {
                return QVariant();
            }
        }
    }
    return QVariant();
}

QVariant JWeaponModel::headerData(int section, Qt::Orientation orentation, int role) const
{
    if (role == Qt::DisplayRole && orentation == Qt::Horizontal)
    {
        return m_header[section];
    }

    return QAbstractTableModel::headerData(section, orentation, role);
}

void JWeaponModel::InitData()
{
    m_header << tr("军种")  << tr("种类") << tr("部门") ;
    m_army << 1 << 2;
    m_weapon << 1 << 2;
}

// 需要添加ItemIsEditable属性,否则代理创建的部件显示不出来
Qt::ItemFlags JWeaponModel::flags(const QModelIndex &index) const
{
    return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}

bool JWeaponModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole)
    {
            int row = index.row();
            m_MapArmy[row+1] =  value.toString();
            //LOG(INFO) << "row :" << row;
            return true;
    }

    return false;
}

主程序添加如下代码,QTableView设置模式为JWeaponModel。

JWeaponModel *p_weapon_model = new JWeaponModel();
QTableView *p_view = new QTableView();
p_view->setModel(p_weapon_model);
p_view->resize(640, 480);
p_view->show();

编译运行之后的效果如下,显示的数据均来自模型JWeaponModel。

如果想要改变item的编辑行为,支持双击的时候,变成选择框QComboBox,那么考虑使用代码,其定义如下,createEditor创建控件,setEditorData设置控件初始数据,setModelData将编辑数据写入model,  则model需要实现setData(参加JWeaponModel类的实现),这样才能将数据显示到视图。updateEditorGeometry管理控件位置。

#include <QModelIndex>
#include <QVariant>
#include <QItemDelegate>


class JEditorDelegate : public QItemDelegate
{
    Q_OBJECT

public:
    JEditorDelegate(QObject *parent = 0);
    ~JEditorDelegate() override;

    QWidget *createEditor(QWidget *parent,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;

    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;

    void updateEditorGeometry(QWidget *editor,
                              const QStyleOptionViewItem &option,
                              const QModelIndex &index) const override;
};

JEditorDelegate对应的实现如下,这里只针对表格第一列进行处理。

JEditorDelegate::JEditorDelegate(QObject *parent)
    : QItemDelegate(parent)
{

}

JEditorDelegate::~JEditorDelegate()
{

}

QWidget *JEditorDelegate::createEditor(QWidget *parent,
                      const QStyleOptionViewItem &option,
                      const QModelIndex &index) const
{
    if (index.column() == 0)
    {
        QComboBox *p_editor = new QComboBox(parent);
        p_editor->addItem(tr("one"));
        p_editor->addItem(tr("two"));
        return p_editor;
    }
    else
    {
        return QItemDelegate::createEditor(parent, option, index);
    }
}

void JEditorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if (index.column() == 0)
    {
        QComboBox *p_combobox = qobject_cast<QComboBox*>(editor);
        if(p_combobox)
        {
            int i = p_combobox->findText("one");
            p_combobox->setCurrentIndex(i);
        }
    }
    else
    {
        return QItemDelegate::setEditorData(editor, index);
    }
}

void JEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    if (index.column() == 0)
    {
        QComboBox *p_combobox = qobject_cast<QComboBox*>(editor);
        if(p_combobox)
        {
            model->setData(index, p_combobox->currentText());
        }
    }
    else
    {
        return QItemDelegate::setModelData(editor, model, index);
    }
}

void JEditorDelegate::updateEditorGeometry(QWidget *editor,
                          const QStyleOptionViewItem &option,
                          const QModelIndex &index) const
{
    (void)index;
    editor->setGeometry(option.rect);
}

主程序添加代理,进行如下所示的修改,调用QTableView的setItemDelegate来添加代理。

JWeaponModel *p_weapon_model = new JWeaponModel();
QTableView *p_view = new QTableView();
p_view->setModel(p_weapon_model);
p_view->setItemDelegate(new JEditorDelegate());
p_view->resize(640, 480);
p_view->show();

编译运行之后,双击第一列第一行的item, 则出现如下的效果

如果想要改变item的显示,那么也是通过代理的方式来支持,其代理类定义如下,继承QItemDelegate,并重载paint。

#include <QModelIndex>
#include <QItemDelegate>

class JArrowDelegate : public QItemDelegate
{
Q_OBJECT

public:
   JArrowDelegate(QObject* parent = 0);
   virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
};

对应的实现如下,paint实现再item中画一个黑色的竖线。

JArrowDelegate::JArrowDelegate(QObject *parent)
   : QItemDelegate(parent)
{
}

void JArrowDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    (void)index;
    //int i_row = index.row();
    int i_x = option.rect.x();
    int i_y = option.rect.y();
    int i_width = option.rect.width();
    int i_height = option.rect.height();
    QPen pen;
    pen.setWidth(2);
    pen.setColor(QColor(Qt::black));
    QStyleOptionViewItem my_option = option;

    QPainterPath path;
    QPoint point_one;
    QPoint point_two;
    point_one.setX(i_x + i_width / 2);
    point_one.setY(i_y);
    point_two.setX(i_x + i_width / 2);
    point_two.setY(i_y + i_height);

    path.moveTo(point_one);
    path.lineTo(point_two);

    painter->setPen(pen);
    painter->drawPath(path);
}

主程序添加代理JArrowDelegate,调用QTableView的接口setItemDelegateForColumn只对表格的第三列添加代理。

JWeaponModel *p_weapon_model = new JWeaponModel();
QTableView *p_view = new QTableView();
p_view->setModel(p_weapon_model);
p_view->setItemDelegate(new JEditorDelegate());
p_view->setItemDelegateForColumn(2, new JArrowDelegate());
p_view->resize(640, 480);
p_view->show();

编译运行效果如下,第三列显示了一条竖线。

六、总结

QT模型视图的原理以及使用说明的讲解就到这里。本文先说明了模型视图的结构,然后再依次说明模型、视图和代理的类层次结构。最后结合具体实例来说明其应用方式,从中可以看出模型提供数据给视图显示,而代理则可以改变数据条目的显示,并通知视图,代理还可以改变数据条目的编辑行为,并通知模型。

QT私有方法成功消除的经验总结

工作过程中,涉及excel的读取,因此,通过搜索从网络下载开源库QXlsx来读写excel文档, 从实际的验证过程中,QXlsx支持windows、linux平台,不但能够读取excel文档,而且可以格式化excel文档,设置文档的字体类型,字体大小、字体颜色,对齐方式等,但是它有一个不好的地方,就是使用的时候,QtCreator创建的工程中pro文件需要加入gui-private, Qt官方不推荐使用的 gui-private, 并且如果升级Qt版本,gui-private提供的接口就有可能不能使用或者被删除,Qt官方不保证gui-private的稳定。所以,决定消除QXlsx使用QZipReader和QZipWriter这种私有的方法,通过尝试和研究,最终成功解决了这个问题。本文将记录其解决的过程方法,作为经验的积累和以后其他类似问题解决的借鉴。

一、前期准备工作

通过阅读QXlsx中ZipReader和ZipWriter源代码,ZipReader和ZipWriter类内部封装了Qt私有接口QZipReader和QZipWriter,而QZipReader的功能就是解压读取zip文件,QZipWriter写入文件并压缩成zip文件。因此,如果需要消除QZipReader和QZipWriter,那么首先需要找到QZipReader和QZipWriter的代替品,然后实现ZipReader和ZipWriter的功能。

通过网络搜索找到Zip Utils简化版本的zip库,它支持C++,具体下载地址可以参考文末的链接。

1、如果是在linux系统上使用,需要添加ZIP_STD宏,例如我是在QtCreator工具上使用,那么在pro文件添加如下所示的信息

DEFINES += ZIP_STD

2、如果是在window上使用,经验证可能需要添加部分接口,有编译的问题,欢迎留言一起探讨。

3、如果是在mac系统上使用,那么unzip.cpp文件中malloc.h需要修改为stdlib.h, 为了跨平台的使用,可以按照如下所示的方式进行修改

#ifdef __APPLE__
#include <stdlib.h>
#else
#include <malloc.h>
#endif

4、ZipReader的头文件定义

#include "xlsxglobal.h"

#include <QScopedPointer>
#include <QStringList>
#include <QIODevice>

#if QT_VERSION >= 0x050600
 #include <QVector>
#endif

class QZipReader;

QT_BEGIN_NAMESPACE_XLSX

class  ZipReader
{
public:
    explicit ZipReader(const QString &fileName);
    explicit ZipReader(QIODevice *device);
    ~ZipReader();
    bool exists() const;
    QStringList filePaths() const;
    QByteArray fileData(const QString &fileName) const;

private:
    Q_DISABLE_COPY(ZipReader)
    void init();
    QScopedPointer<QZipReader> m_reader;
    QStringList m_filePaths;
};

QT_END_NAMESPACE_XLSX

5、ZipWriter的头文件定义

#include <QString>
#include <QIODevice>

#include "xlsxglobal.h"

class QZipWriter;

QT_BEGIN_NAMESPACE_XLSX

class ZipWriter
{
public:
    explicit ZipWriter(const QString &filePath);
    explicit ZipWriter(QIODevice *device);
    ~ZipWriter();

    void addFile(const QString &filePath, QIODevice *device);
    void addFile(const QString &filePath, const QByteArray &data);
    bool error() const;
    void close();

private:
    QZipWriter *m_writer;
};

QT_END_NAMESPACE_XLSX

二、读取类的消除

1、修改ZipReader头文件,  其中添加USE_LOCAL_ZIP是为了方便开启和关闭我们自己加入zip utils库,如果定义了USE_LOCAL_ZIP,那么使用我们自己加入的zip utils库,否则使用原来的类。另外对比ZipReader前后修改的头文件,可以发现主要的修改就是将QZipReader修改为HZIP。

#include <QScopedPointer>
#include <QStringList>
#include <QIODevice>

#if QT_VERSION >= 0x050600
 #include <QVector>
#endif

if defined(USE_LOCAL_ZIP)

#include "unzip.h"

QT_BEGIN_NAMESPACE_XLSX

class  ZipReader
{
public:
    explicit ZipReader(const QString &fileName);
    explicit ZipReader(QIODevice *device);
    ~ZipReader();
    bool exists() const;
    QStringList filePaths() const;
    QByteArray fileData(const QString &fileName) const;

private:
    Q_DISABLE_COPY(ZipReader)
    void init();
    HZIP m_reader;
    QStringList m_filePaths;
    ZRESULT m_result;
};

QT_END_NAMESPACE_XLSX

#endif

2、实现ZipReader类,其中需要注意的点:1> 传入的QIODevice需要先close, 再调用zip库的接口OpenZip才能打开成功; 2> QString类型的字符串转换为char *类型,需要先调用toUtf8。

#if defined(USE_LOCAL_ZIP)

#include <QFile>

QT_BEGIN_NAMESPACE_XLSX

ZipReader::ZipReader(const QString &filePath) :
#ifdef UNICODE
    m_reader(OpenZip(reinterpret_cast<const wchar_t *>(filePath.utf16()), nullptr))
#else
    m_reader(OpenZip(filePath.toUtf8().constData(), nullptr))
#endif
    , m_result(ZR_OK)
{
    init();
}

ZipReader::ZipReader(QIODevice *device)
{

    QFile *file = dynamic_cast<QFile *>(device);
    if (nullptr != file)
    {
        QString str_filename = file->fileName();
        file->close();
    #ifdef UNICODE
        m_reader = OpenZip(reinterpret_cast<const wchar_t *>(str_filename.utf16()), nullptr);
    #else
        m_reader = OpenZip(str_filename.toUtf8().constData(), nullptr);
    #endif
    }
    else
    {
        // 如果使用下面的方式,那么会读取不到数据,需要解决,目前走的是上面的分支
        m_reader = OpenZip(device->readAll().data(), device->size(), nullptr);
    }

    init();
}

ZipReader::~ZipReader()
{
    CloseZip(m_reader);
}

void ZipReader::init()
{
    ZIPENTRY entry;
    GetZipItem(m_reader, -1, &entry);
    int i_numitems = entry.index;
    for (int zi = 0; zi <i_numitems; zi++)
    {
        GetZipItem(m_reader, zi, &entry);
        m_filePaths.append(QString::fromUtf8(entry.name));
    }
}

bool ZipReader::exists() const
{
    return IsZipHandleU(m_reader);
}

QStringList ZipReader::filePaths() const
{
    return m_filePaths;
}

QByteArray ZipReader::fileData(const QString &fileName) const
{
    ZIPENTRY entry;
    int i = -1;

    // 使用fileName.toUtf8().constData(), 而不是fileName.toStdString().c_str(),否则会读取不到
    FindZipItem(m_reader, fileName.toUtf8().constData(), true, &i, &entry);
    if (entry.unc_size < 0 || i < 0)
    {
        return QByteArray("");
    }

    char *p_buf = new char[entry.unc_size + 1];
    UnzipItem(m_reader,i, p_buf, entry.unc_size);
    QByteArray byte_array("");
    byte_array.append(p_buf, entry.unc_size);
    delete[] p_buf;
    p_buf = nullptr;
    return byte_array;
}
QT_END_NAMESPACE_XLSX

#endif

三、写入类的消除

1、修改ZipWriter头文件, 对比ZipWriter前后修改的头文件,可以发现主要的修改就是将QZipWriter修改为HZIP。

#if defined(USE_LOCAL_ZIP)
#include "zip.h"

QT_BEGIN_NAMESPACE_XLSX

class ZipWriter
{
public:
    explicit ZipWriter(const QString &filePath);
    explicit ZipWriter(QIODevice *device);
    ~ZipWriter();

    void addFile(const QString &filePath, QIODevice *device);
    void addFile(const QString &filePath, const QByteArray &data);
    bool error() const;
    void close();

private:
    HZIP m_writer;
    ZRESULT m_result;

};

QT_END_NAMESPACE_XLSX
#endif

2、实现ZipWriter类,其中需要注意的点:1> 传入的QIODevice需要先close, 再调用zip库的接口CreateZip才能创建成功; 2> QString类型的字符串转换为char *类型,需要先调用toUtf8。

#if defined(USE_LOCAL_ZIP)

#include <QFile>
#include <QDebug>

QT_BEGIN_NAMESPACE_XLSX

ZipWriter::ZipWriter(const QString &filePath)
{
#ifdef UNICODE
    m_writer = CreateZip(reinterpret_cast<const wchar_t *>(filePath.utf16()), nullptr);
#else
    m_writer = CreateZip(filePath.toUtf8().constData(), nullptr);
#endif
}

ZipWriter::ZipWriter(QIODevice *device)
{

    QFile *file = dynamic_cast<QFile *>(device);
    if (nullptr != file)
    {
        QString str_filename = file->fileName();
        // 需要先close,否则Create会失败
        file->close();
    #ifdef UNICODE
        m_writer = CreateZip(reinterpret_cast<const wchar_t *>(str_filename.utf16()), nullptr);
    #else
        m_writer = CreateZip(str_filename.toUtf8().constData(), nullptr);
    #endif
    }
    else
    {
    }

}

ZipWriter::~ZipWriter()
{
    if (m_writer)
    {
        CloseZip(m_writer);
        m_writer =nullptr;
    }
}

bool ZipWriter::error() const
{
    return !IsZipHandleZ(m_writer);
}

void ZipWriter::addFile(const QString &filePath, QIODevice *device)
{
    ZipAdd(m_writer, filePath.toUtf8().constData(),  (void *)device->readAll().data(), device->size());
}

void ZipWriter::addFile(const QString &filePath, const QByteArray &data)
{
    ZipAdd(m_writer, filePath.toUtf8().constData(),  (void *)data.data(), data.size());
}

void ZipWriter::close()
{
    if (m_writer)
    { 
        CloseZip(m_writer);
        m_writer =nullptr;
    }
}

QT_END_NAMESPACE_XLSX

#endif

四、总结

总的来说,消除私有方法,首先需要找到代替的类,如果是简单的功能,可以考虑自己实现,否则试试从网络上查找相关的开源代码。接着就是利用代替类重新实现相同的接口,另外,最好提前写好测试类,方便对比测试前后修改的功能。最后需要注意unicode字符类型和utf8字符类型的转换,否则有可能出现程序崩溃,功能不正常等问题。

五、参考链接

读写excel文档入门讲解一

zip_utils

读写excel文档入门讲解一

最近工作中需要将数据以各种格式存放到excel文档中,工程是基于C++,并且采用QT设计的界面,因此,通过网络搜索寻找到基于QT来读写excel文档的开源库QXlsx,本文将介绍开源库QXlsx的简单使用和效果,一方面提供入门知识,另一方面也给自己的学习成果做一个总结记录,方便后续查找复习。

一、简单的例子

1、 网上下载QXlsx开源库后,需要将QXlsx加载到Qt Creator工具,直接参考开源库代码中的HowToSetProject.md文件即可

2、展示写入数据,并且生成excel文档的例子

// 头文件
#include "xlsxdocument.h"
#include "xlsxformat.h"
#include "xlsxworkbook.h"

QXlsx::Document xlsx;
// 通过单元格名写入字符串
xlsx.write("A1", "Hello!");
// 通过行列方式写入字符串
xlsx.write(2, 1, "Everyone!");

std::string str_file_name = JPathHandler::GetDebugPath() + "excel_hello.xlsx";
// 生成excel文档
xlsx.saveAs(QString::fromStdString(str_file_name));

3、最后生成的excel文档的内容如下图所示

二、自动换行

有时候写入单元格的内容比较长,超过了单元格的展示范围,而我们又不想扩大单元格的宽度,怎么办呢,可以通过设置自动换行的方式,让写入单元格的内容自动适配当前的单元格。

1、携带配置好的格式写入到单元格

QXlsx::Document xlsx2;

// 初始化格式
QXlsx::Format format2;
format2.setFontSize(10);
format2.setHorizontalAlignment(QXlsx::Format::AlignHDistributed);

// 用上面的格式来初始化下面的单元格
xlsx2.write("A1", "onetwothree,onetwothree,onetwothree", format2);

// 最后生成excel文档(注意JPathHandler::GetDebugPath()自定义的类,用于获取路径,不是开源库的接口)
std::string str_file_name2 = JPathHandler::GetDebugPath() + "excel_align.xlsx";
xlsx2.saveAs(QString::fromStdString(str_file_name2));

2、最后生成的excel文档的内容效果如下图所示

三、多个表单

当需要多个表单来保存不同的数据的时候,这就涉及到创建多个表单的问题。那么将用创建两个表单的例子来作为简单的入门知识点。

1、生成两个表单,并且分别往表单中写入数据

QXlsx::Document xlsx3;

// 第一个表单
xlsx3.addSheet("first year");
xlsx3.write("A1", "first");

// 第二个表单
xlsx3.addSheet("second year");
xlsx3.write("A1", "second");

// 最后生成excel文档
std::string str_file_name3 = JPathHandler::GetDebugPath() + "excel_addsheet.xlsx";
xlsx3.saveAs(QString::fromStdString(str_file_name3));

2、最后生成的excel文档的内容效果如下图所示

四、读取excel文档

写入数据到excel文档之后,肯定有需求场景需要读取excel文档数据来展示,所以,接下来将说明读取excel文档的所有数据的方法。

1、先读取excel文档,然后获取每一个表单,读取每一个表单中的单元格内容

// 读取上一个步骤中生成的excel文档
QXlsx::Document xlsx4( QString::fromStdString(str_file_name3) );

int i_index_sheet = 0;
// 循环读取excel中的每一个表单,最后打印输出每个单元格的内容
foreach( QString current_sheetname, xlsx4.sheetNames() )
{
    QXlsx::AbstractSheet* p_current_sheet = xlsx4.sheet( current_sheetname );
    p_current_sheet->workbook()->setActiveSheet(i_index_sheet);
    QXlsx::Worksheet* wsheet = (QXlsx::Worksheet*) p_current_sheet->workbook()->activeSheet();

    int i_max_row = -1;
    int i_max_col = -1;
    QVector<QXlsx::CellLocation> clList = wsheet->getFullCells( &i_max_row, &i_max_col );
    for ( int i_cellindex = 0; i_cellindex < clList.size(); ++i_cellindex )
     {
        QXlsx::CellLocation cell_location = clList.at(i_cellindex);
        QVariant var = cell_location.cell.data()->value();
        QString str = var.toString();
        LOG(INFO)<< std::string( str.toLocal8Bit() );
    }
    i_index_sheet++;
}

2、最后输出的打印信息如下所示

[2019-10-20 21:09:48,039626] [bool JQtAttr::TestXlsx():135] first
[2019-10-20 21:09:48,039675] [bool JQtAttr::TestXlsx():135] second

五、总结

从上面的例子中,可以看出QXlsx::Document是控制整个excel文档的类,它可以控制表单的宽高,控制表单等功能,而QXlsx::Format则是控制单元格格式的类,它可以设置字体、字体大小,对齐方式等。

QXlsx的局限性,它不支持多线程,当表单比较多的时候,加载表单会很耗时。

参考链接:

Handling Microsoft Excel file format

QXlsx