计算机系统应用教程网站

网站首页 > 技术文章 正文

Qt Model/View结构原理之QAbstractTableModel基本使用

btikc 2024-10-20 05:03:36 技术文章 7 ℃ 0 评论

一、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赋初值。

新增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();
}

返回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;
}
}

行列的动态增删

因为这个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())

最最重要的一个接口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
};

默认情况下,双击是不能编辑的,可以重写成员函数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;
}


使用代理(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);
}

三、最后

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

【领QT开发教程学习资料,点击下方链接莬费领取↓↓,先码住不迷路~】

点击这里:「链接」

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表