QTableView Optimization

QTableView 的 优化 包括两部分

首先,我们来聊聊性能上的优化

performance optimization

ResizeToContents

如果对 QTableView 的 verticalHeader 或 horizontalHeader 设置 setSectionResizeMode(QHeaderView::ResizeToContents) 的话,QTableView 会遍历整张表,造成卡顿

所以在表格数据量大的时候,

建议将 mode 设置为 QHeaderView::Interactive 或 QHeaderView::Fixed

QAbstractTableModel

写了两个简单的 Demo,加载 7 列 20 万行 Student 数据,在我 6700K 的 CPU 上跑 release 版本

用 QAbstractTableModel 写的模型加载完数据需要 90 ms

用 QStandardItemModel 写的模型加载完数据需要 690 ms

接近 8 倍的性能差距

测试代码1

有些同学习惯了使用 QTableWidget(它性能更差)

推荐尝试下继承 QAbstractTableModel 的自定义 Table 模型,用法其实不难

它有 4 个纯虚函数需要实现,rowCount、columnCount、data 和 headerData

(如果是 QAbstractItemModel,其实还有 index 和 parent 两个纯虚函数,这里先按下不表)

rowCount 代表 table 总的行数,columnCount 代表 table 总的列数,data 代表某个单元格的内容(最重要的函数),headerData 代表每一列(或每一行)表头的内容

我们用下面的代码举例:

第24行 QStringList m_headers 定义了一个表示所有列名称的变量

第25行 QList<Student*> m_itemList 定义了一个记录所有单元格内容的变量

所以 int rowCount(const QModelIndex &) 函数只要用 m_itemList 的大小就能确定

int columnCount(const QModelIndex &) 函数只要用 m_headers 的大小就能确定

QVariant headerData(int section, Qt::Orientation orientation, int role) 函数有3个参数

section 代表第几列,orientation 代表表头在最上边还是在最左边(Qt::Horizontal 代表上边,Qt::Vertical 代表左边)

role 有很多值,在这里我们只判断它是否等于 Qt::DisplayRole,即表头显示的文字

返回值因为是 QVariant,所以你返回 int,QString,char* 都可以

data(const QModelIndex &index, int role) 函数有2个参数,QModelIndex 代表一个单元格

index.column() 可以得到该单元格在第几列,index.row() 可以得到该单元格在第几行

我们可以想象一下,table 开始绘制时,每个单元格都通过这个函数去访问它的数据,它的数据包括单元格的背景色、前景色、显示的文字、鼠标放上去时弹出的tip框文字、单元格的字体、是否包含一个图标、显示的勾选框的状态、单元格的大小等等

这里我们只判断当 role == Qt::DisplayRole 时应该返回什么,即单元格的文本文字

通过 m_itemList 的索引,我们很容易就能获得任意单元格的文本文字,即代码第 48-57 行

第70行是自定义的函数,外部每次调用它,可以插入20万条数据,append 到 m_itemList 中

为了代码简化,每条数据都是一样的内容

beginInsertRows

如果你有大量数据需要插入 model,推荐将多次 insert 合并为一次

我们也可以使用 beginResetModel 来重置模型,缺点是会丢失之前的选中项状态

我们还可以使用 layoutAboutToBeChanged + layoutChanged 来更新模型

一般我们还需要使用 changePersistentIndexList() 更新持久索引(例子中没用到)

测试代码2

QContiguousCache

如果使用 QContiguousCache,可以优化内存的占用

用 QList<Student*> 存储数据时,程序运行占用 33.8M 内存

而用 QContiguousCache 存储数据,程序运行占用 8M 内存,

但加载 20万 条数据,耗时 60ms

测试代码3

fetchMore

一般的Model都是针对固定的数据源,在数据源巨大的情况下, 比如大型数据库每个表可能有数万甚至百万级的数据, 如果是用基本的Model一次性把数据都取出来显示那将是一个恐怖的过程, 搞不好你的系统就玩完了

fetchMore 就是当你需要增量填充模型时,必须重新实现的函数

如下图所示,参考 Qt 的 example,笔者这里就不展开了

img

To be continued....