Qt模型视图框架:自定义模型

来自官方文档,有改动。

模型/视图组件之间的功能分离允许创建可以利用现有视图的模型。

QAbstractItemModel 类提供了一个足够灵活的接口,以支持以分层结构排列信息的数据源,允许以某种方式插入、删除、修改或排序数据。它还提供对拖放操作的支持。

QAbstractListModel 和 QAbstractTableModel 类为更简单的非分层数据结构的接口提供支持,并且更容易用作简单列表和表模型的起点。

设计模型

在为现有数据结构创建新模型时,重要的是要考虑应该使用哪种类型的模型来提供数据接口。

如果数据结构可以表示为一个列表或项目表格,可以将 QAbstractListModel QAbstractTableModel 子类化,因为这些类为许多函数提供了合适的默认实现。

但是,如果底层数据结构只能用层次树结构表示,就需要对QAbstractItemModel进行子类化。

在本节中,我们实现了一个基于字符串列表的简单模型,QAbstractListModel 提供是一个理想的构建基类。

只读模型示例

这里实现的模型是一个基于标准 QStringListModel 类的简单、非分层、只读的数据模型。它有一个 QStringList 作为其内部数据源。

在实现模型时,重要的是要记住 QAbstractItemModel 本身不存储任何数据,它仅提供视图用来访问数据的接口

类声明如下:

 class StringListModel : public QAbstractListModel
 {
     Q_OBJECT

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

     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
     QVariant data(const QModelIndex &index, int role) const override;
     QVariant headerData(int section, Qt::Orientation orientation,
                         int role = Qt::DisplayRole) const override;

 private:
     QStringList stringList;
 };

除了模型的构造函数,我们只需要实现两个函数:

  • rowCount() 返回模型中的行数
  • data() 返回与指定模型索引对应的数据项。

还可以实现 headerData() 提供一些显示在其标题中的内容。

如果的模型是分层的,还必须实现 index() 和 parent() 函数。

字符串列表内部存储在 stringList 私有成员变量中。

模型尺寸

行数和列数:

 int StringListModel::rowCount(const QModelIndex &parent) const
 {
     return stringList.count();
 }

 int StringListModel::columnCount(const QModelIndex &parent) const
 {
     return 2;
 }

模型头和数据

data() 函数负责返回对应于 index 参数的数据项:

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

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

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

某些视图,例如 QTreeView 和 QTableView,能够显示标题以及项目数据。可以通过子类化 headerData() 函数来提供有关标题的信息:

 QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                      int role) const
 {
     if (role != Qt::DisplayRole)
         return QVariant();

     if (orientation == Qt::Horizontal)
         return QStringLiteral("Column %1").arg(section);
     else
         return QStringLiteral("Row %1").arg(section);
 }

可编辑的模型

只读模型显示了如何向用户呈现简单的选择,但对于许多应用程序,可编辑列表模型更有用。 我们可以修改只读模型,通过更改我们为只读实现的 data() 函数,并通过实现两个额外的函数:flags() 和 setData() 来使项目可编辑。 以下函数添加到类中:

 Qt::ItemFlags flags(const QModelIndex &index) const override;
 bool setData(const QModelIndex &index, const QVariant &value,
                  int role = Qt::EditRole) override;

​
 Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
 {
     if (!index.isValid())
         return Qt::ItemIsEnabled;

     return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
 }

​​
 bool StringListModel::setData(const QModelIndex &index,
                               const QVariant &value, int role)
 {
     if (index.isValid() && role == Qt::EditRole) 
     {
         stringList.replace(index.row(), value.toString());
         emit dataChanged(index, index, {role});
         return true;
     }
     return false;
 }

设置数据后,模型必须让视图知道某些数据已更改。这是通过发出 dataChanged() 信号来完成的。由于只有一项数据发生了变化,因此信号中指定的项目范围仅限于一个模型索引。

还需要更改 data() 函数:

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

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

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

汇总:

#ifndef STRINGLISTMODEL_H
#define STRINGLISTMODEL_H

#include <QAbstractListModel>

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

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

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

    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                     int role = Qt::EditRole) override;
    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

private:
    QStringList stringList;
};

#endif // STRINGLISTMODEL_H
#include "stringlistmodel.h"

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

int StringListModel::columnCount(const QModelIndex &parent) const
{
    return 2;
}

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

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

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

QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return tr("第 %1 列").arg(section);
    else
        return tr("第 %1 行").arg(section);
}

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

bool StringListModel::setData(const QModelIndex &index,
                              const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole)
    {
        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return true;
}

bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

测试:

    QStringList numbers;
    numbers << "One" << "Two" << "Three" << "Four" << "Five";

    StringListModel * model = new StringListModel(numbers);
    QTableView view;
    view.setModel(model);
    view.show();

 

 

改一下data(),修改项目出现位置:

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

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

    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        if(index.column() == 0)
            if(index.row() % 2 == 0)
                return stringList.at(index.row());
            else
                return QVariant();
        else
            if(index.row() % 2 == 0)
                return QVariant();
            else
                return stringList.at(index.row());
    }
    else
        return QVariant();
}

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一个比较复杂的问题,需要一些代码实现。以下是一个基本的实现: ```cpp #include <QtXml> #include <QAbstractItemModel> class QTreeItem { public: explicit QTreeItem(QDomElement element, QTreeItem *parent = nullptr); ~QTreeItem(); void appendChild(QTreeItem *child); void removeChild(QTreeItem *child); QTreeItem *child(int row); int childCount() const; int row() const; QDomElement element() const; bool setData(const QVariant &value, int column); private: QList<QTreeItem*> m_childItems; QDomElement m_element; QTreeItem *m_parentItem; }; class QTreeModel : public QAbstractItemModel { public: explicit QTreeModel(QDomDocument document, QObject *parent = nullptr); ~QTreeModel(); QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override; bool insertRows(int row, int count, const QModelIndex &parent) override; bool insertColumns(int column, int count, const QModelIndex &parent) override; bool removeRows(int row, int count, const QModelIndex &parent) override; bool removeColumns(int column, int count, const QModelIndex &parent) override; QDomDocument document() const; QTreeItem *getItem(const QModelIndex &index) const; private: void setupModelData(QDomElement element, QTreeItem *parentItem); QDomDocument m_document; QTreeItem *m_rootItem; }; ``` 上面的实现中,QTreeItem 表示树形结构中的一个节点,包含了一个 QDomElement 和它的子节点。QTreeModel 是一个实现了 QAbstractItemModel 的类,用于将 QDomDocument 显示为可编辑的树形结构。 接下来是实现的代码: ```cpp QTreeItem::QTreeItem(QDomElement element, QTreeItem *parent) : m_element(element), m_parentItem(parent) { if (m_parentItem) { m_parentItem->appendChild(this); } } QTreeItem::~QTreeItem() { qDeleteAll(m_childItems); } void QTreeItem::appendChild(QTreeItem *child) { m_childItems.append(child); } void QTreeItem::removeChild(QTreeItem *child) { m_childItems.removeOne(child); } QTreeItem *QTreeItem::child(int row) { if (row < 0 || row >= m_childItems.size()) { return nullptr; } return m_childItems.at(row); } int QTreeItem::childCount() const { return m_childItems.size(); } int QTreeItem::row() const { if (m_parentItem) { return m_parentItem->m_childItems.indexOf(const_cast<QTreeItem*>(this)); } return 0; } QDomElement QTreeItem::element() const { return m_element; } bool QTreeItem::setData(const QVariant &value, int column) { if (column == 0) { m_element.setTagName(value.toString()); } else { QDomText text = m_element.firstChild().toText(); if (text.isNull()) { text = m_element.ownerDocument().createTextNode(value.toString()); m_element.appendChild(text); } else { text.setData(value.toString()); } } return true; } QTreeModel::QTreeModel(QDomDocument document, QObject *parent) : QAbstractItemModel(parent), m_document(document) { QDomElement rootElement = m_document.documentElement(); m_rootItem = new QTreeItem(rootElement); setupModelData(rootElement, m_rootItem); } QTreeModel::~QTreeModel() { delete m_rootItem; } QModelIndex QTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } QTreeItem *parentItem = getItem(parent); QTreeItem *childItem = parentItem->child(row); if (childItem) { return createIndex(row, column, childItem); } else { return QModelIndex(); } } QModelIndex QTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return QModelIndex(); } QTreeItem *childItem = getItem(child); QTreeItem *parentItem = childItem->parent(); if (!parentItem || parentItem == m_rootItem) { return QModelIndex(); } return createIndex(parentItem->row(), 0, parentItem); } int QTreeModel::rowCount(const QModelIndex &parent) const { QTreeItem *parentItem = getItem(parent); return parentItem->childCount(); } int QTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 2; // 标签名和文本 } QVariant QTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role != Qt::DisplayRole && role != Qt::EditRole) { return QVariant(); } QTreeItem *item = getItem(index); if (index.column() == 0) { return item->element().tagName(); } else { QDomText text = item->element().firstChild().toText(); if (text.isNull()) { return ""; } else { return text.data(); } } } bool QTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } if (role != Qt::EditRole) { return false; } QTreeItem *item = getItem(index); return item->setData(value, index.column()); } Qt::ItemFlags QTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::NoItemFlags; } Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; if (index.column() == 0) { flags |= Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable; } return flags; } QVariant QTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == 0) { return "Tag Name"; } else { return "Text"; } } return QVariant(); } bool QTreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (orientation == Qt::Horizontal && role == Qt::EditRole) { if (section == 0) { m_document.documentElement().setTagName(value.toString()); emit headerDataChanged(orientation, section, section); return true; } } return false; } bool QTreeModel::insertRows(int row, int count, const QModelIndex &parent) { QTreeItem *parentItem = getItem(parent); beginInsertRows(parent, row, row + count - 1); for (int i = 0; i < count; ++i) { QDomElement newElement = m_document.createElement("NewElement"); QTreeItem *newItem = new QTreeItem(newElement, parentItem); parentItem->appendChild(newItem); } endInsertRows(); return true; } bool QTreeModel::insertColumns(int column, int count, const QModelIndex &parent) { Q_UNUSED(column); Q_UNUSED(count); Q_UNUSED(parent); return false; } bool QTreeModel::removeRows(int row, int count, const QModelIndex &parent) { QTreeItem *parentItem = getItem(parent); beginRemoveRows(parent, row, row + count - 1); for (int i = 0; i < count; ++i) { QTreeItem *childItem = parentItem->child(row); parentItem->removeChild(childItem); delete childItem; } endRemoveRows(); return true; } bool QTreeModel::removeColumns(int column, int count, const QModelIndex &parent) { Q_UNUSED(column); Q_UNUSED(count); Q_UNUSED(parent); return false; } void QTreeModel::setupModelData(QDomElement element, QTreeItem *parentItem) { for (QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.toElement().isNull()) { continue; } QDomElement childElement = node.toElement(); QTreeItem *childItem = new QTreeItem(childElement, parentItem); setupModelData(childElement, childItem); } } QDomDocument QTreeModel::document() const { return m_document; } QTreeItem *QTreeModel::getItem(const QModelIndex &index) const { if (index.isValid()) { QTreeItem *item = static_cast<QTreeItem*>(index.internalPointer()); if (item) { return item; } } return m_rootItem; } ``` 实现的过程中,需要注意以下几个关键点: 1. QTreeItem 类需要实现树形结构中节点的基本操作,包括添加子节点、删除子节点、获取子节点等。 2. QTreeModel 类需要实现 QAbstractItemModel 中的一系列操作,包括获取索引、获取数据、设置数据、获取标志、获取行数和列数等。 3. 在 QTreeModel 类中,可以使用 QDomDocument 和 QDomElement 来表示 XML 文件的结构,通过 QDomDocument::createElement() 和 QDomDocument::createTextNode() 可以创建元素和文本节点,在 QTreeItem 类中,可以使用 QDomElement::firstChild() 和 QDomNode::nextSibling() 来遍历子节点。 4. 在 QTreeModel 类中,需要重载 insertRows() 和 removeRows() 函数来实现插入和删除行的操作。 5. 在 QTreeModel 类中,可以使用 QVariant 类型来存储和获取数据。在 data() 函数中,需要根据列号来返回相应的数据。在 setData() 函数中,需要根据 QVariant 类型来设置元素的标签名或文本内容。 6. 在 QTreeModel 类中,需要重载 flags() 函数来获取节点的标志,包括是否可编辑、是否可选择、是否可拖拽、是否可拖放等。 7. 在 QTreeModel 类中,需要重载 headerData() 和 setHeaderData() 函数来设置和获取表头信息。 8. 在 QTreeModel 类中,需要重载 index() 和 parent() 函数来获取索引和父节点。 9. 在 QTreeModel 类中,需要重载 rowCount() 和 columnCount() 函数来获取行数和列数。 最后,在使用 QTreeModel 的时候,需要将 QDomDocument 传递给 QTreeModel 的构造函数,通过 QTreeView 来将其显示出来,并通过一些信号和槽来实现编辑和保存操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值