您的位置:

QAbstractItemModel:理解Qt中的模型视图框架

在Qt中,模型视图框架是许多应用程序的核心。这种框架使用模型来表示数据,使用视图(如QTableView或QTreeView)来将该数据可视化。Qt中提供了一些内置的模型类,但是,如果您需要为自己的应用程序定义自定义的数据源,那么您需要了解QAbstractItemModel的工作方式。

一、QAbstractItemModel简介

QAbstractItemModel是Qt的一个关键类,它为开发人员提供了一个接口,用于指定模型数据的组织方式。它是QAbstractTableModel和QAbstractListModel等模型的基类,这些模型提供了一些常见的实现,例如表格和列表。QAbstractItemModel用于QTreeView,QListView,QTableView等视图类的基类,它们都使用基于文件夹的数据模型来显示数据。

QAbstractItemModel所有直接的子类应该在以下一个函数中实现两个不同的索引:index()和parent()。这些函数定义了静态树形结构,元素的位置不会改变。这些函数能够让Qt高效地处理大型树形结构。index()和parent()会被Qt视图类用于将数据排列为TreeView,ListView和TableView等典型模型视图结构。

二、QAbstractItemModel的子类实现

1、自定义QAbstractItemModel

class CustomModel : public QAbstractItemModel
{
public:
    CustomModel(QObject *parent = 0);
    ~CustomModel();

    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
    QModelIndex parent(const QModelIndex &child) const;
    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent = QModelIndex()) const;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
};

上面是一个自定义的QAbstractItemModel的框架。在这个例子中,根节点是一个空的QModelIndex,它没有坐标以及没有父节点。可以看到,index()和parent()都需要返回一个QModelIndex,它们指定了行和列的位置。rowCount()和columnCount()函数返回模型中行的数量和列的数量。

在此例子中,data()和setData()函数的实现也是必不可少的。它们定义了数据将如何存储和访问。在大多数情况下,将使用QVariant来存储数据,而且在data()函数中使用role参数来返回适合给定视图类型的数据。setData()负责更新模型数据。

2、Qt自带的Model Index

QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex &parent = QModelIndex()) const

index()函数返回一个QModelIndex对象,此对象由行号和列号组成,该索引属于parent。在许多情况下,一些QAbstractItemModel的派生类经常使用这些方法来创建一个自己的index()。

3、返回父级QModelIndex

QModelIndex QAbstractItemModel::parent(const QModelIndex &index) const

parent()函数返回一个QModelIndex对象,该对象传递回索引的父级项的位置。parent()和index()通常应该是相互的逆。parent()为顶级ItemModel或已知索引返回无效的QModelIndex。因此,QModelIndex的行号和列号都是0。

4、获取行数和列数

int QAbstractItemModel::rowCount(const QModelIndex &parent = QModelIndex()) const
int QAbstractItemModel::columnCount(const QModelIndex &parent = QModelIndex()) const

这两个函数返回模型中给定父项的行数或列数。如果没有父项,则返回顶层项的行数或列数。row()和column()一样,都从0开始计数。在许多情况下,这些函数将用于初始化一个ItemView部件。

5、获取和设置ItemData

QVariant QAbstractItemModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const
bool QAbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole)

data()函数返回与给定模型索引关联的数据。可以为给定角色请求数据。setData()函数将给定值存储在模型索引处。如果此处发生错误,则使用Rollback数据。

三、QAbstractItemModel的应用举例

1、创建树型结构

这个应用程序是一个树型表格。它的数据源是硬编码,但该例子很好地展示了如何扩展QAbstractItemModel来支持树型结构:

class TreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    TreeModel(QObject *parent = 0);
    ~TreeModel();

    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
    QModelIndex parent(const QModelIndex &child) const;

    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent = QModelIndex()) const;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);

private:
    TreeItem *rootItem;
};

具体的实现过程就不再贴出,可以参考Qt自带例子,这里给出参考:

简易树模型

2、扩展现有的模型类

下面是一个QAbstractItemModel的派生类,该类从QAbstractListModel扩展:

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    explicit StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings)
    {
    }

    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        if (parent.isValid())
            return 0;
        return stringList.count();
    }

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

        if (index.row() >= stringList.size())
            return QVariant();

        if (role == Qt::DisplayRole)
            return stringList.at(index.row());
        else
            return QVariant();
    }

private:
    QStringList stringList;
};

StringListModel扩展了QAbstractListModel并实现了rowCount()和data()函数来处理一个QStringList中的内容。这是一个非常基本的示例,但当您需要单行列表模型时,这可能是一个很有用的例子。

3、处理排序和过滤

排序和过滤是QAbstractItemModel的高级应用。排序需要将数据按特定顺序排列,而过滤则需要以某种方式选择数据,例如将其筛选掉。这是一个基本示例:

class SortFilterModel : public QSortFilterProxyModel
{
public:
    SortFilterModel(QObject *parent = nullptr)
        : QSortFilterProxyModel(parent)
    {
    }

    bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
    {
        QModelIndex index0 = sourceModel()->index(source_row, 0, source_parent);

        QRegExp regExp("^\\[0-9\\.]+(.*)");
        QString str = index0.data(Qt::DisplayRole).toString();
        if (regExp.indexIn(str) != -1)
        {
            QString capStr = regExp.cap(1);
            if (capStr < "30")
                return false;
        }

        return true;
    }
};

在此示例中,过滤器使用QRegExp来查找所有标题是数字(例如,[20]Title 3)的行。如果标题中的数字小于30,则该行将被过滤掉。

四、总结

QAbstractItemModel为开发者提供了一个用于管理自定义数据的模型视图框架。开发者可以通过继承并实现QAbstractItemModel的相关函数来创建自己的模型,或者继承并扩展现有的模型类。此外,QAbstractItemModel还支持排序和过滤。学会使用QAbstractItemModel可以让应用程序在数据管理方面完美地运转。