TableView超多列的解决办法
问题描述
在QML中我们经常商用TableView来进行数据的展示。大部分情况下使用Qt Quick Controls 1自带的TableView是问题不大的。但在某些情况下则会有力不从心的感觉,比如表格的列可能有一百甚至数百列的时候就会出现刷新效率低下的问题。
查看TableView的源码你会发现这是TableView的实现是以行为基本单位,在行数很多时只会实现真正显示的那十几行的委托。所以在行数过多时问题也不大。
但在Qt Quick Controls 1中的TableView只针对行进行了优化,它实现时没有考虑列数过多的情况。当表格的列有300列时,哪怕只刷新表格显示的20行,也会有将近6000个对象进行绘制。
解决办法
对于这个问题,你可以将Qt Quick Controls 1的TableView源码分离出来,自己针对这一部分进行优化。但这个方法比较费力不讨好,出现新的bug的可能性更大。
索性Qt官方也知道自己的这个问题了,所以在后来增加了一个新的TableView。下面针对这个新的TableView说一下。
前提条件
在Qt 5.12的版本中官方增加了这个他们宣传更加高效快速的TableView。所以如果你的无法升级到Qt 5.12以上的版本,这篇文章对你意义不大可以右上角了。
新TableView的引用
众所周知Qt Quick Controls 1有很多问题,Qt官方处于种种原因又重复造了一次轮子这就是Qt Quick Controls 2。而这里介绍到的TableView也是Qt官方在5.12版本又造的一次轮子。所以在QML项目中如果不注意应用很容易混淆这两个控件。
- 使用之前的TableView一般是引用以下插件
import QtQuick.Controls 1.4
在Qt creator中是这些属性

- 而新版本的TableVew是来自以下插件
import QtQuick 2.12
在Qt creator中是这些属性

新TableView的效果
Qt官方给出了两个TableView的效果,其实我更愿意称之为Excel,其用法和我们想要的TableView效果差距还是很大的。

看到这个图大伙可能知道我为什么这么说了。因为这个Table没有表头没有列名……
新TableView的问题
新TableView是以单元格为基本单位进行绘制,也就是说它只会绘制你表格显示在界面中的数据。即时你有3000列x1000行,它也只会绘制你界面实际显示的6x20行。绘制对象少了不止一个量级。
所以在效率上确实不需要担心。本人实测300列插入10000行的时间也不到1ms。
-
但有利有弊,以单元格作为基本单位进行选择绘制的第一个问题就是新TableView没有行的概念!

这句话的意思不是没有行数这个属性了,在新TableView中还是有rows这个参数的,我说没有行是指不存在类型之前TableView的rowDelegate这种用法。 -
第二个问题就是新TableView不自带表头
所以得自己去处理列名、表头绘制…… -
第三个问题就是无法再通过role属性直接访问表格的值
自定义TableView
如果你看到这里还没有放弃看下去的想法,那看来也是被QML的这个问题逼得够呛。如果你没有更好的解决方案暂且看看我的例子,希望对你有所帮助。
代码我也传一份到CSDN,为了照顾部分没分的同学,我这里贴一些。写的时候部分地方用到了公司的代码所有进行了部分调整,颜色搭配需要下下来后再次调整。
- 自定义表头
新TableView没有表头只有我们自己来手动实现一个,这部分算最简单的。几十行代码也够用了。
一下是TableHead的代码
TableHeader.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
Item{
id: header_horizontal
//暂存鼠标拖动的位置
property int posXTemp: 0
MouseArea{
anchors.fill: parent
onPressed: header_horizontal.posXTemp=mouseX;
onPositionChanged: {
if(table_view.contentX+(header_horizontal.posXTemp-mouseX)>0){
table_view.contentX+=(header_horizontal.posXTemp-mouseX);
}else{
table_view.contentX=0;
}
header_horizontal.posXTemp=mouseX;
}
}
Row {
id: header_horizontal_row
anchors.fill: parent
leftPadding: -table_view.contentX
clip: true
Repeater {
model: table_view.columns > 0 ? table_view.columns : 0
Rectangle {
id: header_horizontal_item
width: table_view.columnWidthProvider(index)+table_view.columnSpacing
height: parent.height
color: "red"//自己配色
border.width: 1
border.color: "#1a2b3c"
Label {
width:parent.width
height:control.horHeaderHeight
horizontalAlignment: Text.AlignHCenter
//anchors.fill: parent
text: _sourceModel.headerData(index,Qt.Horizontal)
}
Rectangle {
width: parent.width
height: filterMode*25
anchors.bottom: parent.bottom
color: "blue"
visible:height !== 0
TextInput{
anchors.fill: parent
onTextChanged:{
// console.log(text,index)
//设置过滤条件
_displayModel.setCondition(index,text)
table_view.forceLayout()
}
}
Behavior on height {
PropertyAnimation{ duration : 200 }
}
}
Rectangle{
width: 1
height: parent.height
anchors.right: parent.right
color: "black"
opacity: 0.5
}
MouseArea{
width: 3
height: parent.height
anchors.right: parent.right
cursorShape: Qt.SplitHCursor
onPressed: header_horizontal.posXTemp=mouseX;
onPositionChanged: {
if((header_horizontal_item.width-(header_horizontal.posXTemp-mouseX))>10){
header_horizontal_item.width-=(header_horizontal.posXTemp-mouseX);
}else{
header_horizontal_item.width=10;
}
header_horizontal.posXTemp=mouseX;
//改变某列的宽度
control._columnWidthArr[index]=(header_horizontal_item.width-table_view.columnSpacing);
//刷新布局,这样宽度才会改变
table_view.forceLayout();
}
}
}
}
}
}
TableScrollBar.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
ScrollBar {
id:scroRoot
// policy: ScrollBar.AlwaysOn
contentItem: Rectangle{
visible: (scroRoot.size<1.0)
color: scrollBarColor
}
}
TableCell.qml
import QtQuick 2.12
import QtQuick.Controls 1.4
Rectangle{
color: currentIndex === model.row ?
"green" :"#1a2b3c"
border.width :0
Label{
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//elide: Text.ElideRight
text:model.value
}
Rectangle {
height: 2
width: parent.width
anchors.bottom: parent.bottom
color: "#00000000"
border.color: "white"
}
}
EfficientTable.qml
import QtQuick 2.12
//import QtQuick.Controls.Styles 1.4
import QtQuick.Controls 2.12
import EfficientModel 0.1
import SortFilterModel 0.1
//自定义QtQuick 2中的TableView
Item {
id: control
//行表头-竖向的
property int rowHeight: 30
//列表头-横向的
property int horHeaderHeight: 30
//滚动条
property color scrollBarColor: "yellow"
property int scrollBarWidth: 11
//行数
property alias rows: table_view.rows
//当前选中行
property int currentIndex: -1
//是否使用过滤模式
property bool filterMode:true
property alias table: table_view
//列宽
property variant _columnWidthArr: []
EfficientModel{
id:_sourceModel
}
SortFilterModel {
id:_displayModel
}
Component.onCompleted: {
_displayModel.setModel(_sourceModel)
}
//表格内容(不包含表头)
TableView{
id: table_view
anchors{
fill: parent
topMargin: _tableHeader.height
}
clip: true
boundsBehavior: Flickable.StopAtBounds
//rowSpacing: 1
//视图的高度
//contentHeight: rowHeightProvider(0) * rows + rowHeightProvider(rows-1)
//视图的宽度
//contentWidth:
//content内容区域边距,但是不影响滚动条的位置
//leftMargin:
//topMargin:
//此属性可以包含一个函数,该函数返回模型中每行的行高
rowHeightProvider: function (row) {
return control.rowHeight;
}
//此属性可以保存一个函数,该函数返回模型中每个列的列宽
columnWidthProvider: function (column) {
return control._columnWidthArr[column];
}
ScrollBar.vertical: TableScrollBar {
id: scroll_vertical
anchors.right: parent.right
implicitWidth: scrollBarWidth
}
ScrollBar.horizontal: TableScrollBar {
id: scroll_horizontal
anchors.bottom: parent.bottom
implicitHeight:scrollBarWidth
}
//model是在C++中定义的
model:_displayModel
delegate: TableCell{}
MouseArea{
anchors.fill: parent
onClicked: {
control.currentIndex = parseInt(mouse.y/control.rowHeight)
}
}
}
//横项表头
TableHeader{
id:_tableHeader
anchors{
left: parent.left
right: parent.right
}
height: control.horHeaderHeight + filterMode*25
Behavior on height {
PropertyAnimation{ duration : 200 }
}
z: 2
}
//添加新列
function insertColumn(tableIndex,columnName,roleName,width){
_columnWidthArr.splice(tableIndex,0,width)
_sourceModel.insertColumn(tableIndex,columnName,roleName)
}
//添加数据
function insertRow(row,data){
_sourceModel.insertRow(row,data)
}
//添加数据
function insertRows(row,datas){
_sourceModel.insertDatas(row,datas)
}
//删除数据
function removeRow(row,count){
_sourceModel.removeRows(row,count)
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.4
Window {
id:root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
EfficientTable {
id:tableRoot
anchors.fill: parent;
anchors.bottomMargin: 50
}
/*
TabView{
id:tabRoot
Tab{
title: "第一页"
}
Tab{
title: "第二页"
EfficientTable {
anchors.fill: parent
}
}
Tab{
title: "第三页"
EfficientTable {
anchors.fill: parent
}
}
Tab{
title: "第四页"
EfficientTable {
anchors.fill: parent
}
}
}*/
Row {
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
Button {
text:"添加数据"
onClicked: {
if (dataTimer.running){
dataTimer.stop()
}else{
dataTimer.start()
}
}
}
Button {
text:"删除数据"
onClicked: {
tableRoot.removeRow(tableRoot.currentIndex,1)
}
}
Button {
text:"过滤数据"
onClicked: {
tableRoot.filterMode = !tableRoot.filterMode
}
}
Button {
text:"打印"
onClicked: {
console.log(tableRoot.rows)
}
}
}
Component.onCompleted: {
for (var j=0; j <300;j++){
tableRoot.insertColumn(j,"列"+String(j) , "C"+String(j) ,100)
}
dataTimer.start();
}
property var ss: 0
Timer {
id:dataTimer
interval:1093
repeat: true
onTriggered: {
console.time("test1")
var data = {}
for (var i=0; i < 300; i++){
var roleName = "C"+String(i)
data[roleName]= "----"+String(ss)
}
var datas = []
for (var j=0; j <10000;j++){
ss = ss +1
datas.push(data)
}
console.timeEnd("test1")
tableRoot.insertRows(tableRoot.rows,datas)
// tableRoot.insertRows(tableRoot.rows,datas)
// tableRoot.table.contentY = tableRoot.table.contentHeight +
// tableRoot.height
}
}
}
EfficientModel.h
#ifndef EFFICIENTMODEL_H
#define EFFICIENTMODEL_H
#include <QAbstractTableModel>
#include <QJsonObject>
#include <QJsonArray>
#include <QJSValue>
class EfficientModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit EfficientModel(QObject *parent = nullptr);
// Header:
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// Basic functionality:
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;
// Remove data:
Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
Q_INVOKABLE QHash<int,QByteArray> roleNames() const override;
//插入新列
Q_INVOKABLE bool insertColumn(int column, QString columnName,QString roleName);
//插入新行
Q_INVOKABLE bool insertRow(int row, QJSValue data,const QModelIndex &parent = QModelIndex());
Q_INVOKABLE bool insertDatas(int row, QJSValue datas,const QModelIndex &parent = QModelIndex());
//通过角色名获得对应的单元格的值
//Q_INVOKABLE QVariant modelData(const int row, QString roleName) const ;
private:
//表格的头部
QStringList _header;
//角色映射
QMap<int,QString> _roleMap;
//表格的原始数据
QVector<QJSValue> _modelData;
};
#endif // EFFICIENTMODEL_H
EfficientModel.cpp
#include "EfficientModel.h"
#include <QDebug>
#include <QTime>
EfficientModel::EfficientModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
QVariant EfficientModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// FIXME: Implement me!
if(orientation==Qt::Horizontal&&role==Qt::DisplayRole){
if (section < _header.size()){
return _header.at(section);
}
}
return QAbstractTableModel::headerData(section,orientation,role);
}
int EfficientModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return _modelData.size();
// FIXME: Implement me!
}
int EfficientModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return _header.size();
// FIXME: Implement me!
}
QVariant EfficientModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role)
if (!index.isValid())
return QVariant();
int nRow = index.row();
int nColumn = index.column();
//找到映射
if (!_roleMap.contains(nColumn)){
return "";
}
if (nRow >= _modelData.size()){
return "";
}
QString strRole = _roleMap.value(nColumn);
return _modelData.at(nRow).property(strRole).toString();
// QJsonDocument jsonDoc = QJsonDocument::fromVariant(.toVariant());
}
bool EfficientModel::insertColumn(int column, QString columnName,QString roleName)
{
beginInsertColumns(QModelIndex(), column, column);
_header.insert(column,columnName);
//记录角色名和列索引直接的关系
_roleMap[column] = roleName;
endInsertColumns();
return true;
}
bool EfficientModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row + count - 1);
// FIXME: Implement me!
_modelData.remove(row,count);
endRemoveRows();
return true;
}
bool EfficientModel::removeColumns(int column, int count, const QModelIndex &parent)
{
beginRemoveColumns(parent, column, column + count - 1);
// FIXME: Implement me!
endRemoveColumns();
return true;
}
bool EfficientModel::insertRow(int row,QJSValue data,const QModelIndex &parent)
{
beginInsertRows(parent, row, row );
_modelData.insert(row,data);
endInsertRows();
return true;
}
bool EfficientModel::insertDatas(int row, QJSValue datas,const QModelIndex &parent)
{
int arrayCount = datas.property("length").toInt();
beginInsertRows(parent, row, row+arrayCount -1 );
for (int i =0; i < arrayCount; i++){
_modelData.push_back(datas.property(i));
}
endInsertRows();
return true;
}
QHash<int, QByteArray> EfficientModel::roleNames() const
{
//value表示取值,edit表示编辑
return QHash<int,QByteArray>{
{ Qt::DisplayRole,"value" },
{ Qt::EditRole,"edit" }
};
}
SortFilterModel.h
#ifndef SORTFILTERMODEL_H
#define SORTFILTERMODEL_H
#include <QSortFilterProxyModel>
#include "EfficientModel.h"
class SortFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit SortFilterModel(QObject *parent = 0);
//设置过滤条件
Q_INVOKABLE void setCondition(const int column, const QString& strCondition);
Q_INVOKABLE QHash<int,QByteArray> roleNames() const override;
Q_INVOKABLE void setModel(EfficientModel* pModel);
protected:
//该函数为虚函数,继承下来可以重写,返回值决定了是否将内容显示到表格中
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
signals:
private:
//所有的过滤条件
QMap<int,QString> _conditionMap;
};
#endif // SORTFILTERMODEL_H
SortFilterModel.cpp
#include "SortFilterModel.h"
#include <QDebug>
#include <QAbstractTableModel>
SortFilterModel::SortFilterModel(QObject *parent) :
QSortFilterProxyModel(parent)
{
//setDynamicSortFilter(false);
}
void SortFilterModel::setCondition(const int column, const QString& strCondition)
{
//如果没有限制条件,则将过滤条件置为空
if (strCondition.isEmpty()){
_conditionMap.remove(column);
}else{
_conditionMap.insert(column,strCondition);
}
//setFilterFixedString("");
invalidateFilter();
}
//该函数为虚函数,继承下来可以重写,返回值决定了是否将内容显示到表格中
bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (_conditionMap.size() == 0){
return true;
}
//遍历所有的过滤条件,有一个不满足就不显示
QMap<int, QString>::const_iterator iter = _conditionMap.constBegin();
while (iter != _conditionMap.end() ) {
QModelIndex index = sourceModel()->index(source_row, iter.key(), source_parent);
QString strData = sourceModel()->data(index).toString();
if (!strData.contains(iter.value())){
return false;
}
iter++;
}
return true;
}
QHash<int, QByteArray> SortFilterModel::roleNames() const
{
//value表示取值,edit表示编辑
return QHash<int,QByteArray>{
{ Qt::DisplayRole,"value" },
{ Qt::EditRole,"edit" }
};
}
void SortFilterModel::setModel(EfficientModel* pModel){
setSourceModel(pModel);
}
代码大概就贴全了,这个是我之前自己测试时写的代码,在过滤那块我记得还有个bug。但TableView的大体思路就是这样,在实际项目中我也已经用上了,暂时没什么问题。
本文探讨了在QML中遇到的TableView列过多导致效率低下的问题,并介绍了Qt 5.12新引入的更高效的TableView。新TableView以单元格为基础进行绘制,显著提升了性能,但缺少表头和行概念,需要自定义实现。文章提供了自定义解决方案及部分代码示例。

628

被折叠的 条评论
为什么被折叠?



