QT View及Model源码解析

QT View及Model源码解析
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 root;会在构造的时候new出来,不过这个root在界面上是不显示的。
构造后行列,都是0,里面存储行列的vector为空
QVector columnHeaderItems;
QVector 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中使用QVectortableItems:进行保存的。
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 &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 {
        Q_Q(const QStandardItemModel);
        if (!index.isValid())
            return root.data();    // 使用root即可
        if (index.model() != q)
            return 0;
        QStandardItem *parent = static_cast(index.internalPointer()); // 每个QModelIndex内部实际上存储着parent的指针
        if (parent == 0)
            return 0;
        return parent->child(index.row(), index.column());   // 再通过parent指针,来根据row,column获取到对象指针
    }
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(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 values;
QWidgetItemData中保存着role和可以存储任意类型的QVariant,这样每次setData的时候会先判断是否有这个role,有则覆盖,没有则append

你可能感兴趣的:(C++)