Qt学习:项视图类之自定义模型

本文介绍如何使用Qt自定义模型实现货币汇率表,通过优化存储空间,仅存储每种货币相对于美元的汇率,从而计算出与其他货币的汇率。实现过程包括创建自定义模型类、存储货币汇率数据及使用QTableView展示数据。
部署运行你感兴趣的模型镜像

    我们先前介绍了三种Qt的预定义模型,分别是QStringListModel,QDirModel(QFileSystemModel), QSortFilterProxyMoel,这些预定义模型为数据的处理和查看提供了很好的方法。但是,有些数据源不能有效的和预定义模型一起工作,因为我们大批量数据的存储是各种各样的,这时候用我们上边提到的三种预定义模型可能能解决问题,但却同时带来了效率和处理上的不便,因此我们需要定义自己的模型,以方便对底层数据集进行优化。
    Qt的MVC架构中有一些比较重要的概念在这里有必要先提一下。
    一个model中的每个数据元素都有一个model索引。这个索引指明这个数据位于model的位置,比如行、列等。这就是前面我们曾经说到过的QModelIndex。每个数据元素还要有一组属性值,称为角色(roles)。这个属性值并不是数据的内容,而是它的属性,比如说,这个数据是用来展示数据的,还是用于显示列头的?因此,这组属性值实际上是Qt的一个enum定义的,比较常见的有Qt::DisplayRole和Qt::EditRole,另外还有Qt::ToolTipRole, Qt::StatusTipRole, 和Qt::WhatsThisRole等。并且,还有一些属性是用来描述基本的显示属性的,比如Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole, Qt::BackgroundColorRole等。 
    对于list model而言,要定位其中的一个数据只需要有一个行号就可以了,这个行号可以通过QModelIndex::row()函数进行访问;对于table model而言,这种定位需要有两个值:行号和列号,这两个值可以通过QModelIndex::row()和QModelIndex::column()这两个函数访问到。另外,对于tree model而言,用于定位的可以是这个元素的父节点。实际上,不仅仅是tree model,并且list model和table model的元素也都有自己的父节点,只不过对于list model和table model,它们元素的父节点都是相同的,并且指向一个非法的QModelIndex。对于所有的model,这个父节点都可以通过QModelIndex::parent()函数访问到。这就是说,每个model的项都有自己的角色数据,0个、1个或多个子节点。既然每个元素都有自己的子元素,那么它们就可以通过递归的算法进行遍历,就像数据结构中树的遍历一样。关于父节点的描述,请看下面这张图(出自C++ GUI Programming with Qt4, 2nd Edition):

    下面我们通过一个简单的例子来看看如何实现自定义model。这个例子来自C++ GUI Programming with Qt4, 2nd Edition。首先描述一下需求。这里我们要实现的是一个类似于货币汇率表的table。或许你会想,这是一个很简单的实现,直接用QTableWidget不就可以了吗?的确,如果直接使用QTableWidget确实很方便。但是,试想一个包含了100种货币的汇率表。显然,这是一个二维表,并且,对于每一种货币,都需要给出相对于其他100种货币的汇率(在这里,我们把自己对自己的汇率也包含在内,只不过这个汇率永远是1.0000)。那么,这张表要有100 x 100 = 10000个数据项。现在要求我们减少存储空间。于是我们想,如果我们的数据不是显示的数据,而是这种货币相对于美元的汇率,那么,其他货币的汇率都可以根据这个汇率计算出来了。比如说,我存储的是人民币相对美元的汇率,日元相对美元的汇率,那么人民币相对日元的汇率只要作一下比就可以得到了。我没有必要存储10000个数据项,只要存储100个就够了。于是,我们要自己实现一个model。
    我们定义一个CurrencyModel类来实现这个功能。 CurrencyModel将和一个标准的QTableWidget类一同使用。这个自定义模型的底层数据被封装在一个QMap<QString, double>中,每一个键都是一个货币编码,并且每一个值都是以美元为单位的货币值。这次我们先看这个类怎么用的,以下是main函数的实现部分:

//main.cpp
#include "currencymodel.h"
#include <QApplication>
#include <QTableView>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QMap<QString, double> currencyMap;
    currencyMap.insert("AUD", 1.3257);
    currencyMap.insert("CHF", 1.2980);
    currencyMap.insert("SGD", 1.6911);
    currencyMap.insert("USD", 1.0000);

    CurrencyModel currencyModel(currencyMap);

    QTableView tableView;
    tableView.setModel(¤cyModel);
    tableView.setAlternatingRowColors(true);
    tableView.show();
    
    return a.exec();
}


            我们用自己定义的一个关联容器来存储我们的值,并在构造CurrencyModel 对象的时候把这个数据集存进我们自定义的model中,然后就用QTableView来观察这些数据。

//currencymodel.h
#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H

#include <QAbstractTableModel>
#include <QMap>

class CurrencyModel : public QAbstractTableModel
{
    Q_OBJECT
    
public:
    CurrencyModel(const QMap<QString, double> &map, QObject *parent = 0);

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

private:
    QString currencyAt(int offset) const;

    QMap<QString, double> currencyMap;
};

#endif // CURRENCYMODEL_H

//currencymodel.cpp
#include "currencymodel.h"

CurrencyModel::CurrencyModel(const QMap<QString, double> &map, QObject *parent)
    : QAbstractTableModel(parent)
{
    currencyMap = map;
}

int CurrencyModel::rowCount(const QModelIndex &/*parent*/) const
{
    return currencyMap.count();
}

int CurrencyModel::columnCount(const QModelIndex &/*parent*/) const
{
    return currencyMap.count();
}

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

    if (role == Qt::TextAlignmentRole) {
        return int(Qt::AlignRight | Qt::AlignVCenter);
    } else if (role == Qt::DisplayRole) {
        QString rowCurrency = currencyAt(index.row());
        QString columnCurrency = currencyAt(index.column());

        if (currencyMap.value(rowCurrency) == 0.0)
            return "####";

        double amount = currencyMap.value(columnCurrency)
                / currencyMap.value(rowCurrency);
        return QString("%1").arg(amount, 0, 'f', 4);
    }
    return QVariant();
}

QVariant CurrencyModel::headerData(int section,
                                  Qt::Orientation /*orientation*/, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();
    return currencyAt(section);
}

QString CurrencyModel::currencyAt(int offset) const
{
    return (currencyMap.begin() + offset).key();
}


    我们选择了继承QAbstractTableModel。虽然是自定义model,但各种model之间也会有很多共性。Qt提供了一系列的抽象类供我们继承,以便让我们只需要覆盖掉几个函数就可以轻松地定义出我们自己的model。Qt提供了QAbstractListModel和QAbstractTableModel两类,前者是一维数据model,后者是二维数据model。如果你的数据很复杂,那么可以直接继承QAbstractItemModel。
    data()函数返回单元格的数据。它有两个参数:第一个是QModelIndex,也就是单元格的位置;第二个是role,也就是这个数据的角色。这个函数的返回值是QVariant。至此,我们还是第一次见到这个类型。这个类型相当于是Java里面的Object,它把绝大多数Qt提供的数据类型都封装起来,起到一个数据类型“擦除”的作用。比如我们的table单元格可以是string,也可以是int,也可以是一个颜色值,那么这么多类型怎么返回呢?于是,Qt提供了这个QVariant类型,你可以把这很多类型都存放进去,到需要使用的时候使用一系列的to函数取出来即可。比如你把int包装成一个QVariant,使用的时候要用QVariant::toInt()重新取出来。这里需要注意的是,QVariant类型的放入和取出必须是相对应的,你放入一个int就必须按int取出,不能用toString(), Qt不会帮你自动转换。
    程序的运行效果:

 


    以下是这个类的实现部分:

您可能感兴趣的与本文相关的镜像

Qwen3-8B

Qwen3-8B

文本生成
Qwen3

Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一整套密集型和专家混合(MoE)模型。基于广泛的训练,Qwen3 在推理、指令执行、代理能力和多语言支持方面取得了突破性进展

<think>我们正在讨论Qt自定义视图(Item View)的模型实现。根据引用内容,Qt的Model/View架构中,模型需要提供标准接口供视图和委托访问数据,这个标准接口由QAbstractItemModel定义。无论底层数据结构如何,所有QAbstractItemModel的子都将数据表示为包含目表的分层结构[^2]。 用户意图是了解如何实现自定义视图所使用的模型。因此,我们将重点放在如何从QAbstractItemModel派生出自定义模型。 ### 自定义模型实现步骤 #### 1. **理解模型角色(Roles)** - 模型通过`data()`函数返回数据,该函数使用角色(如`Qt::DisplayRole`、`Qt::EditRole`等)来区分不同型的数据[^2]。 - 常用角色: - `DisplayRole`: 显示的文本 - `DecorationRole`: 图标 - `EditRole`: 编辑时的数据 - `ToolTipRole`: 工具提示 #### 2. **必须重写的纯虚函数** 自定义模型必须继承`QAbstractItemModel`(或它的子如`QAbstractListModel`、`QAbstractTableModel`)并实现以下纯虚函数: - `rowCount()`: 返回行数 - `columnCount()`: 返回列数(对于列表模型,列数通常为1) - `index()`: 为给定行、列和父索引创建模型索引 - `parent()`: 返回父索引(对于列表或表格模型,通常返回无效索引) - `data()`: 返回索引对应的数据(根据角色) - `headerData()`: 返回表头数据(可选重写) - `setData()`: 设置数据(支持编辑时需要) - `flags()`: 返回索引的标志(如是否可选、可编辑等) #### 3. **示例:自定义列表模型** 以下是一个简单的自定义列表模型实现,它管理一个字符串列表: ```cpp #include <QAbstractListModel> #include <QStringList> class CustomListModel : public QAbstractListModel { Q_OBJECT public: explicit CustomListModel(QObject *parent = nullptr) : QAbstractListModel(parent) {} // 必须重写的函数 int rowCount(const QModelIndex &parent = QModelIndex()) const override { if (parent.isValid()) // 对于列表模型,只有根有效 return 0; return m_data.size(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (!index.isValid() || index.row() >= m_data.size()) return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) return m_data.at(index.row()); return QVariant(); } // 支持编辑:重写setData和flags bool setData(const QModelIndex &index, const QVariant &value, int role) override { if (index.isValid() && role == Qt::EditRole) { m_data.replace(index.row(), value.toString()); emit dataChanged(index, index); // 通知视图数据变化 return true; } return false; } Qt::ItemFlags flags(const QModelIndex &index) const override { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } // 自定义方法:添加和删除数据 void addItem(const QString &item) { beginInsertRows(QModelIndex(), m_data.size(), m_data.size()); m_data.append(item); endInsertRows(); } void removeItem(int row) { if (row < 0 || row >= m_data.size()) return; beginRemoveRows(QModelIndex(), row, row); m_data.removeAt(row); endRemoveRows(); } private: QStringList m_data; }; ``` #### 4. **在视图中使用自定义模型** ```cpp int main(int argc, char *argv[]) { QApplication app(argc, argv); CustomListModel model; model.addItem("Apple"); model.addItem("Banana"); model.addItem("Orange"); QListView view; view.setModel(&model); view.setWindowTitle("Custom List Model"); view.show(); return app.exec(); } ``` #### 5. **高级功能实现** - **拖放支持**:重写`mimeTypes()`, `mimeData()`, `dropMimeData()`等函数[^3]。 - **大数据优化**:实现`canFetchMore()`和`fetchMore()`进行分批加载。 - **自定义角色**:除了标准角色,可以定义自己的角色(如`MyCustomRole = Qt::UserRole + 1`)。 #### 6. **性能优化建议** - 对于大数据集,避免在`data()`中进行复杂计算。 - 使用`beginInsertRows()`/`endInsertRows()`等函数批量更新。 - 在模型重置时使用`beginResetModel()`/`endResetModel()`。 ### 引用说明 - 引用[2]指出,Qt模型/视图架构中,模型通过QAbstractItemModel定义的接口提供数据访问[^2]。 - 引用[3]提到,模型/视图支持拖放操作,并允许自定义行为[^3]。 §§相关问题§§ 1. 如何在自定义模型中实现树形结构的数据? 2. 如何为自定义模型添加拖放支持? 3. 在Qt模型中,`beginInsertRows()`和`endInsertRows()`的作用是什么?</think>### Qt 自定义视图模型实现指南 在 Qt 中实现自定义视图模型,需要继承 `QAbstractItemModel` 或其子(如 `QAbstractListModel` 或 `QAbstractTableModel`),并重写关键方法。以下是核心实现步骤: #### 1. **选择模型** 根据数据结构选择基: - `QAbstractListModel`:单列列表数据 - `QAbstractTableModel`:表格数据 - `QAbstractItemModel`:树形结构数据[^2] ```cpp #include <QAbstractListModel> #include <QStringList> class CustomListModel : public QAbstractListModel { Q_OBJECT public: explicit CustomListModel(QObject *parent = nullptr); // 必须重写的纯虚函数 int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // 可选重写函数 QVariant headerData(int section, Qt::Orientation orientation, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; private: QStringList m_data; // 实际数据存储 }; ``` #### 2. **实现核心方法** ```cpp // 返回行数 int CustomListModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; // 列表模型无父 return m_data.size(); } // 返回数据 QVariant CustomListModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_data.size()) return QVariant(); switch(role) { case Qt::DisplayRole: // 文本显示 case Qt::EditRole: // 编辑时数据 return m_data.at(index.row()); case Qt::ToolTipRole: // 工具提示 return QString("Item %1").arg(index.row() + 1); } return QVariant(); } // 设置数据(支持编辑) bool CustomListModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole && index.isValid()) { m_data.replace(index.row(), value.toString()); emit dataChanged(index, index); // 通知视图更新 return true; } return false; } ``` #### 3. **实现数据操作接口** ```cpp // 添加数据 void CustomListModel::addItem(const QString &item) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_data.append(item); endInsertRows(); } // 删除数据 void CustomListModel::removeItem(int row) { if (row < 0 || row >= m_data.size()) return; beginRemoveRows(QModelIndex(), row, row); m_data.removeAt(row); endRemoveRows(); } ``` #### 4. **在视图中使用模型** ```cpp int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建自定义模型 CustomListModel model; model.addItem("Item A"); model.addItem("Item B"); // 创建视图并设置模型 QListView view; view.setModel(&model); view.setEditTriggers(QAbstractItemView::DoubleClicked); view.show(); return app.exec(); } ``` #### 5. **高级功能实现** 1. **自定义角色**: ```cpp // 在模型头文件中定义 enum CustomRoles { UserRole1 = Qt::UserRole + 1, UserRole2 }; // 在data()中处理 QVariant data(...) const { if (role == UserRole1) return customData1; ... } ``` 2. **拖放支持**: ```cpp // 重写拖放相关方法 Qt::ItemFlags flags(const QModelIndex &index) const override { return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | QAbstractListModel::flags(index); } QStringList mimeTypes() const override { return {"text/plain"}; } QMimeData* mimeData(const QModelIndexList &indexes) const override { // 创建包含拖放数据的QMimeData } ``` 3. **大数据优化**: ```cpp // 分批加载数据 bool canFetchMore(const QModelIndex &parent) const override { return m_data.size() < totalDataCount; } void fetchMore(const QModelIndex &parent) override { int remainder = totalDataCount - m_data.size(); int itemsToAdd = qMin(100, remainder); // 每次加载100条 beginInsertRows(QModelIndex(), m_data.size(), m_data.size() + itemsToAdd - 1); // 添加新数据 endInsertRows(); } ``` #### 模型-视图交互流程 当视图需要显示数据时: 1. 视图调用模型的 `rowCount()` 获取目数量 2. 对每个可见索引调用 `data()` 获取显示内容 3. 用户编辑时调用 `setData()` 更新模型 4. 模型通过 `dataChanged()` 信号通知视图更新[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值