天天看點

Qt Model/View結構原理之QAbstractTableModel基本使用

作者:QT教程

一、Model/View基本原理

GUI應用程式開發中往往少不了清單框,表格,樹形結構等表現形式的應用。當然Qt中也提供了相應的視圖類QListView,QTableView, QTreeView,這些類使用模型/視圖Model/View架構來管理資料之間的關系及其呈現給使用者的方式。這種體系結構引入的功能分離為開發人員提供了更大的靈活性來定制資料項的表示,并提供了一個标準模型接口,允許在現有的視圖中使用廣泛的資料源。

Qt Model/View結構原理之QAbstractTableModel基本使用

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裡。

Qt Model/View結構原理之QAbstractTableModel基本使用

而通常情況是我們使用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來看一下具體使用方法:

Qt Model/View結構原理之QAbstractTableModel基本使用

必須要實作的接口如下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之間的橋梁,互相傳遞資料的作用。

我想實作輕按兩下,編輯單元格顔色的效果,如下圖,該怎麼做呢?

Qt Model/View結構原理之QAbstractTableModel基本使用

首先子類化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開發教程學習資料,點選下方連結莬費領取↓↓,先碼住不迷路~】

點選這裡:「連結」

繼續閱讀