QT的Model用于保存数据,而QTView只是用于显示界面,大概关系如下,View的数据private中一般会有drawcell等方法,用于从model里面取数据绘制文字,表格,绘制的时候会根据每个QModelIndex的flag属性来进行不同绘制,所以如果我们继承了flag并且设置特殊的role角色,会改变绘制的效果,当然我们也可以直接通过setflag也是一样的。
各个QView之间的类关系如下,不同的view实现方式会略有差别,而TableWidget,TreeWidget是已经包含了一个model的,我们直接可以使用的,它里面的很多方法都是透传给QModel的调用的。QTableVIew,QTreeView等,则需要我们自己new一个model,自己来控制数据。
各个model之间的关系大概如下,使用treeview的时候,我们可以new一个QStandardItemModel来使用即可。当然我们可以继承QAbstractItemModel,然后自己实现一些model方法,不过我们是无法使用QTableMode,QTreeModel的,它们是QT里面没有导出的类,是专门给QTableWidget和QTreeWidget来使用的。
下面来看一下一些主要使用的接口的大概实现过程。我们以QTableView和QStandardItemModel为例来说明,其它的思想大致也是如此
1、构造函数过程:
QTableView构造煤什么特别之处,QStandardItemModel的构造,会new数据成员类QStandardItemModelPrivate,里面有个很重要的变量,QStandardItemModelPrivate::root。类型为QScopedPointer<QStandardItem> root;会在构造的时候new出来,不过这个root在界面上是不显示的。
构造后行列,都是0,里面存储行列的vector为空
QVector<QStandardItem*> columnHeaderItems;
QVector<QStandardItem*> rowHeaderItems;
QStandardItemModel::QStandardItemModel(QObject *parent)
: QAbstractItemModel(*new QStandardItemModelPrivate, parent)
{
Q_D(QStandardItemModel);
d->init();
d->root->d_func()->setModel(this); //会将root的mode也设置为this
}
// model的数据类构造root节点
QStandardItemModelPrivate::QStandardItemModelPrivate()
: root(new QStandardItem),
{
}
2、setModel
会设置model的信号和view的槽进行关联,如rowsInserted,columnsInserted,rowsRemoved等重要的信号,QTableView中的行列view参数
verticalHeader,horizontalHeader中也会保存model,行列属性也会接受model的这几个信号。
3、setColumnCount
设置列数,如果不设置,列为0,我们insertrow是看不到任何数据的
QStandardItemModel保存所有item的方法与QTable会有所不同,它是使用root所有的child进行保存的,这样也是为了方便Treeview使用,有子节点的概念,而tableView是没有这个概念的,tableview是在model中使用QVector<QTableWidgetItem*>tableItems:进行保存的。
void QStandardItemModel::setColumnCount(int columns)
{
d->root->setColumnCount(columns);
}
void QStandardItem::setColumnCount(int columns)
{
if (cc < columns)
insertColumns(qMax(cc, 0), columns - cc);
else
removeColumns(qMax(columns, 0), cc - columns);
}
接下来就是重要的插入和移除列的接口了,先看insert操作
因为我们只是设置列数,并没有传入设置列的值,所以下面接口的最后一个参数为空
bool QStandardItemPrivate::insertColumns(int column, int count, const QList<QStandardItem*> &items)
{
columnsAboutToBeInserted(); //将要插入信号
if (columnCount() == 0)
{
children.resize(rowCount() * count);
columns = count;
}
else
{
columns += count;
int index = childIndex(0, column);
for (int row = 0; row < rowCount(); ++row) {
children.insert(index, count, 0);
index += columnCount();
}
}
columnsInserted(); // 插入完成信号
}
inline int childIndex(int row, int column) const
{
if ((row < 0) || (column < 0)
|| (row >= rowCount()) || (column >= columnCount())) {
return -1;
}
return (row * columnCount()) + column;
}
children即所有的item节点信息,childindex是通过行,列信息获取到它在vector中的位置,这里使用了一个补齐的方法,如果添加了某列,会在每行结尾处插入一个空指针,保证了每行列数相等,这也实现了通过row,column能够在一维的QVector中取到它的index,这还有个好处,当删除某行元素时,所有vector上移,对应的row,column计算出来也是刚好往上移的,如果开始使用结构体之类的记录row,column信息的话(如QLayout),这就需要每行的row都减1才能保证界面上的rowindex与实际存储的保持一致,这也是QLayout没有做到的。
4、insertrow
插入某一行,与上面类似
5、QStandardItem* pItem = pTableModel->item(0 ,0);
此处我们获取的话,其实获取到的是一个空指针,因为我们前面只是使用一个空指针占一个行,列位置并没有值。
6、赋值过程
QModelIndex modelindex1 = pTableModel->index(0, 0);
pTableModel->setData(modelindex1, strTest, Qt::EditRole);
6.1 pTableModel->index(0, 0);的实现
QModelIndex QStandardItemModel::index(int row, int column, const QModelIndex &parent) const
{
判断行,列是否超出范围
QStandardItem *parentItem = d->itemFromIndex(parent);
return createIndex(row, column, parentItem); // 会将parent指针传给这个QModelIndex。此时0,0位置的指针仍然是空的。
}
inline QModelIndex QAbstractItemModel::createIndex(int arow, int acolumn, void *adata) const
{ return QModelIndex(arow, acolumn, adata, this); }
此处itemFromIndex也可以看一下过程。
inline QStandardItem *itemFromIndex(const QModelIndex &index) const {6.2、setData过程
bool QStandardItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid())
return false;
QStandardItem *item = itemFromIndex(index);
if (item == 0)
return false;
item->setData(value, role);
return true;
}
itemFromIndex会帮我们构造一个指针出来,代码如下
QStandardItem *QStandardItemModel::itemFromIndex(const QModelIndex &index) const
{
QStandardItem *parent = static_cast<QStandardItem*>(index.internalPointer()); //parent是有值的,即上面6.1中model->index时返回的
if (parent == 0)
return 0;
QStandardItem *item = parent->child(index.row(), index.column());
// lazy part
if (item == 0) {
item = d->createItem(); // 创建这个Item,CreateItem也就是new QStandardItem,我们在外面也可以new一个然后设置的。
parent->d_func()->setChild(index.row(), index.column(), item); //保存到root中
}
return item;
}
7.QStandardItem ::setData的过程
我们经常使用过程中,会对每个节点设置不同的role,那它的实现过程是怎么样的呢?
其实很简单的。
每个item都有一个vector
QVector<QWidgetItemData> values;
QWidgetItemData中保存着role和可以存储任意类型的QVariant,这样每次setData的时候会先判断是否有这个role,有则覆盖,没有则append