Qt Model/View结构原理之QAbstractTableModel基本使用,详细代码

一、Model/View基本原理

GUI应用程序开发中往往少不了列表框,表格,树形结构等表现形式的应用。当然Qt中也提供了相应的视图类QListView,QTableView, QTreeView,这些类使用模型/视图Model/View架构来管理数据之间的关系及其呈现给用户的方式。这种体系结构引入的功能分离为开发人员提供了更大的灵活性来定制数据项的表示,并提供了一个标准模型接口,允许在现有的视图中使用广泛的数据源。

请添加图片描述

Data:是实际数据,可以数据库的一个数据表或SQL查询结果,内存中的StringList,或文件等等。

View:GUI界面组件,视图从数据模型获得每个数据线的模型索引(Model index),通过模型缩影获取数据,然后为界面组件提供显示数据。比如QListView,QTableView, QTreeView等。

Model:与实际数据通信,并为视图组件提供数据接口。可以理解成数据adapter,数据wrapper。它从原始数据提取需要的内容,用于视图组件进行显示和编辑。

这样设计的好处有:

  • 通过Model/View使数据源与显示界面分离,代码解耦;
  • 另外还可以将同一数据模型在不同的视图中显示;
  • 还可以在不修改数据模型的情况下,设计特殊的视图。

Delegate:在model/view结构中,还提供了代理功能(Delegate),代理功能可以让用户定制数据的界面显示和编辑方式。

model,view,delegate之间使用信号和槽进行通信。当数据发生变化是,model通过信号通知view;

当用户在UI上操作数据时(选中,点击等),view通过信号表示这些操作信息;

当用户编辑数据是,delegate通过信号通知model和view编辑器的状态。

1.数据模型 Model

QAbstractItemModel是所有数据模型的基类,这个类定义了view和delegate存取数据的接口。但原始数据不一定要存储在model里。

请添加图片描述

而通常情况是我们使用QListView,QTableView, QTreeView都会使用与之相应的模型类,分别继承自QAbstractListModel,QAbstractTableModel,QAbstractItemModel,生成自己定制的数据模型类。

2.视图组件 View

视图组件View就是显示数据模型的数据的界面组件,Qt提供如下常用视图组件:

  • QListView:显示单列的列表数据,适用于一维数据的操作;
  • QTreeView:显示树状结构数据,适用于树状结构数据的操作;
  • QTableView:显示表格状数据,适用于二维表格型数据的操作。

视图类的setModel()函数,即可完成view和model的数据绑定,同时在view上的修改能自动关联到model。

3.代理 delegate

代理就是视图组件上为编辑数据提供编辑器,如在table组件中双击一个单元格编辑数据是,缺省是使用QLineEdit编辑框。代理的作用首先是从model中取数据,然后显示在编辑器中,修改数据后,又将其保存到model中。

通常使用需要派生自QStyledItemDelegate类,创建自定义代理类。

二、QAbstractTableModel使用

通过上面的分析,我们知道QAbstractTableModel,主要为QTableView提供数据模型接口,我们可以子类化该抽象类并实现相关接口。下面我们做一个简单9*9乘法口诀的demo来看一下具体使用方法:

请添加图片描述

必须要实现的接口如下3个:

//返回行数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
//返回列数
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
//根据模型索引返回当前的数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

当然只有必须的3个接口,好像还不能很好的工作,首先我们需要给model赋初值。

  1. 新增setInitData成员函数,加载数据并刷新。
void MyTableModel::setInitData(QList<CellInfo*>& data)
{
    //重置model数据之前调用beginResetModel,此时会触发modelAboutToBeReset信号
    beginResetModel();
    //重置model中的数据
    m_datas = data;
    m_rowNum = ceil(data.size()*1.0/m_columnNum);   //行数=数据总数/列数,然后向上取整
    //数据设置结束后调用endResetModel,此时会触发modelReset信号
    endResetModel();
}
  1. 返回rowCount(),columnCount(),因为这里行列都是9,所以返回固定的9
int MyTableModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid()) {
        return 0;
    } else {
        return m_rowNum;
    }
}

int MyTableModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid()) {
        return 0;
    } else {
        return m_columnNum;
    }
}
  1. 行列的动态增删

因为这个9*9乘法口诀表行列是固定的,所以这里暂不需要对行列的操作,相关接口如下,可以按需增加。

//插入相关接口
bool insertColumn(int column, const QModelIndex &parent = QModelIndex())
virtual bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex())
bool insertRow(int row, const QModelIndex &parent = QModelIndex())
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())
//删除相关接口
bool removeColumn(int column, const QModelIndex &parent = QModelIndex())
virtual bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex())
bool removeRow(int row, const QModelIndex &parent = QModelIndex())
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
  1. 最最重要的一个接口data()函数

view通过model提供的data()函数,进行自身的数据呈现,可以分不同维度提供不同的数据。

QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }
    if(index.row()*m_columnNum+index.column() < m_datas.count())
    {
        if (role == Qt::DisplayRole ||  role == Qt::EditRole) {
            return m_datas[index.row()*m_columnNum+index.column()]->content;//数据的呈现形式
        }
        else if(role == Qt::BackgroundColorRole){
            return m_datas[index.row()*m_columnNum+index.column()]->bgColor;//单元格背景色
        }
        else if (role == Qt::TextAlignmentRole) {    //对其方式
            return Qt::AlignCenter;
        }
        else if(role == Qt::ToolTipRole){
            return m_datas[index.row()*m_columnNum+index.column()]->toolTip;//数据的提示信息
        }
        else if(role == Qt::UserRole)
        {
            return QVariant::fromValue(m_datas[index.row()*m_columnNum+index.column()]);
        }
    }
    return QVariant();
}

可以看到ItemDataRole的枚举,想要view以何种维度来展示数据,往这里面的else if里面写即可。

enum ItemDataRole {
        DisplayRole = 0,
        DecorationRole = 1,
        EditRole = 2,
        ToolTipRole = 3,
        StatusTipRole = 4,
        WhatsThisRole = 5,
        // Metadata
        FontRole = 6,
        TextAlignmentRole = 7,
        BackgroundRole = 8,
        ForegroundRole = 9,
#if QT_DEPRECATED_SINCE(5, 13) // ### Qt 6: remove me
        BackgroundColorRole Q_DECL_ENUMERATOR_DEPRECATED = BackgroundRole,
        TextColorRole Q_DECL_ENUMERATOR_DEPRECATED = ForegroundRole,
#endif
        CheckStateRole = 10,
        // Accessibility
        AccessibleTextRole = 11,
        AccessibleDescriptionRole = 12,
        // More general purpose
        SizeHintRole = 13,
        InitialSortOrderRole = 14,
        // Internal UiLib roles. Start worrying when public roles go that high.
        DisplayPropertyRole = 27,
        DecorationPropertyRole = 28,
        ToolTipPropertyRole = 29,
        StatusTipPropertyRole = 30,
        WhatsThisPropertyRole = 31,
        // Reserved
        UserRole = 0x0100
    };
  1. 默认情况下,双击是不能编辑的,可以重写成员函数flags(),setData(),让其具有编辑功能,并编辑框更改后的值能更新到model里,也能通知到view。
Qt::ItemFlags MyTableModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;
    return Qt::ItemIsEnabled|Qt::ItemIsSelectable|Qt::ItemIsEditable;
}

bool MyTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.row()*m_columnNum+index.column() < m_datas.count())
    {
        if (index.isValid() && role == Qt::EditRole)
        {
            m_datas[index.row()*m_columnNum+index.column()]->content = value.value<QString>();
            emit dataChanged(index, index, QVector<int>() << role);                     //发送信号触发刷新
            return true;
        }
        if (index.isValid() && role == Qt::BackgroundColorRole)
        {
            m_datas[index.row()*m_columnNum+index.column()]->bgColor = value.value<QColor>();
            emit dataChanged(index, index, QVector<int>() << role);                     //发送信号触发刷新
            return true;
        }
    }
    return false;
}
  1. 使用代理(delegate)

tableview如果不指定delegate的话,默认是使用QLineEdit编辑框。通过前面介绍我们知道delegate的作用相当于model–view之间的桥梁,相互传递数据的作用。

我想实现双击,编辑单元格颜色的效果,如下图,该怎么做呢?

请添加图片描述

  • 首先子类化QStyledItemDelegate,在实现基类4个虚函数,4个函数的作用,看注释应该都很清楚。delegate是桥梁,是中间人的角色,所以model—view两边都要安排好。
class MyColorSelDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit MyColorSelDelegate(QObject *parent = nullptr);
    ~MyColorSelDelegate();

    //创建用于编辑模型数据的widget组件,如一个QSpinBox组件,或一个QComboBox组件;
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
    //从数据模型获取数据,供widget组件进行编辑;
    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    //将widget上的数据更新到数据模型;
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
    //用于给widget组件设置一个合适的大小;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
signals:

};
  • 其次createrEditor(),返回系统自带的QColorDialog颜色拾取框的QWidget对象指针,当然这里也可以是我们自定义的任何widget;
  • 然后setEditorData(),从数据模型获取数据,供widget组件进行编辑;
  • 最后setModelData(),将widget上的数据更新到数据模型;
QWidget *MyColorSelDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QWidget* editor = new QColorDialog(parent);
    return editor;
}

void MyColorSelDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{

}

void MyColorSelDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QColorDialog* dlg = static_cast<QColorDialog*>(editor);
    model->setData(index, dlg->selectedColor(), Qt::BackgroundColorRole);
}

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

三、最后

通过QAbstractTableModelQTableView的实际操作,我们应该能明白model/view的原理。

完整代码已经上传到码云

官方文档:https://doc.qt.io/qt-5/qabstracttablemodel.html

官方文档:https://doc.qt.io/qt-5/model-view-programming.html

  • 14
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Qt中的Model/View是一种基于MVCModel-View-Controller)设计模式的实现方式。Model/View架构将数据的存储和显示分离开来,使得程序的结构更加清晰,并且可以提高程序的可维护性和可扩展性。 在Qt中,Model/View是面向对象的。它由三个基础类组成:QAbstractItemModel、QAbstractTableModel和QAbstractListModel。QAbstractItemModel为QAbstractTableModel和QAbstractListModel提供了接口规范,使用它可以将数据模型View分离开来。QAbstractTableModel主要为表格型数据模型定义了一套标准。QAbstractListModel与之类似,为列表型数据模型定义了一套标准。 在Model/View中,Model提供了从数据源中获取数据并将其封装成数据项及其属性的方式。而View根据Model提供的数据项及其属性,对其进行可视化展示。对于数据的修改和删除等操作,则通过View传递给Model来进行实现。 Model的数据来源可以是任何类型的数据,例如数据库、XML文件、内存中的数据等等。ModelView之间的通信是通过信号和槽机制来实现的。当Model的数据发生变化时,它会发出数据变化的信号,View会从这些信号中得知数据发生了哪些改变,然后对其进行更新。 Model/View提供了一种灵活、高效、可扩展的方案来处理数据。在Qt中,开发者可以使用其提供的各种ModelView类,或者继承这些类来实现自己的数据模型视图类,以便更好地满足自己的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苏克贝塔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值