QTreeView处理大量数据

本文介绍如何使用QTreeView控件高效显示大量数据,通过重写QAbstractItemModel相关函数,实现仅加载可视区域数据,减少内存占用,并优化刷新机制,提升显示速度。
  如何使QTreeView快速显示1000万条数据,并且内存占用量少呢?这个问题困扰我很久,在网上找了好多相关资料,都没有找到合理的解决方案,今天在这里把我的解决方案提供给朋友们,供大家相互学习。
我开始使用的QTreeWidget 控件来显示我的数据,发现该控件在显示10000行以下的数据还可以应付的过来,但超过10000条,就明显感觉到屏幕刷新就会有卡的现象,而且占据内存很大,虽然操作起来简单方便,但灵活性没有QTreeView强大。因为我要显示的数据量是非常大的,甚至过1000万,因此,采用QTreeWidget来显示,很显然不能满足性能要求,所以打算采用QTreeView来显示,不罗嗦了,赶快进入正题了,下面讲讲我是怎么通过QTreeView来快速显示1000万条数据的吧!
    1.通过从文件里面读取要显示的数据,交给QTreeView来显示,从而不用把数据一次性读到内存,就可以解决内存占用大的问题。
    2.我们知道数据显示是通过刷新来实现的,通过刷新时,每次只刷新屏幕可见区域,其他部分不用刷新的方法,从而解决速度显示慢的问题。
解决了上面的两个问题,显示1亿条数据都不会有任何问题,而且显示速度和内存占有量与显示1000条数据相当,听起来很诱人啊,下面是我是怎么通过QTreeView来解决这两个问题的:
1.重载QAbstractItemModel中的如下函数:  
         QVariant headerData(int section, Qt::Orientation orientation, 
        int role = Qt::DisplayRole) const;
        QVariant data(const QModelIndex &index, int role) const;
        int rowCount(const QModelIndex &parent = QModelIndex()) const;      
        int columnCount(const QModelIndex &parent = QModelIndex()) const;
        QModelIndex index(int row, int column, 
        const QModelIndex &parent = QModelIndex()) const;
        QModelIndex parent(const QModelIndex &index) const;    
(1)QVariant headerData(intsection, Qt::Orientationorientation, int role =Qt::DisplayRole)const;显示树视的标题,section表示列,从0开始,orientation表示标题的方向(水平还是垂直),role表式标题栏显示的方式,当role的角色为Qt::DisplayRole时,表示显示文本,当然还有其他角色,大家可以参考Qt开发手册。示例代码:
         QVariant CMyModel::headerData(int section, Qt::Orientation orientation, int role) const
        {
            if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
            {
                 return pData->headerData(section);
            }
            return QVariant();
        }
    显示水平标题,其中pData是我定义的一个数据类的对象,在这里我把该类命名为CData,headerData来取出要显示的数据。
(2)QVariant data(const QModelIndex &index,int role)const;显示数据,index表示树节点索引,树中的每个节点都有一个对应的该索引,当index = QModelIndex()时,表示该节点是根节点,否则为非根节点。index中存放了该节点在同存节点中的位置信息(行和列),以及节点的特殊信息,如index.internalPointer(),这是一个指针,我们可以通过该指针保存我们想要的节点信息,角色同上。示例代码:
         QVariant CMyModel::data(const QModelIndex &index, int role) const
        {
             if (!index.isValid())
                 return QVariant();
             int row = index.row();
             int col = index.column();
             if(role == Qt::DisplayRole)
             {
                  return pData->getData(row, col);
             }
             return QVariant();
        }
    通过getData来获取要显示的数据,该数据方在我们的文件里,每次只需要读取我们想要显示的数据,不需要把所有的数据都放到内存,从而节省了内存空间,这样就解决了上面讲的第一个问题。
(3)int rowCount(const QModelIndex &parent = QModelIndex())const;大家不难猜出该函数是返回该父节点下有多少个子节点,还是来看示例代码吧:
        int CMyModel::rowCount(const QModelIndex &parent)const
        {
             return pData->rowCount();
        }
    为了简单起见,这里不考虑父节点,认为任何节点都存在rowCount()个节点。
(4)int columnCount(const QModelIndex &parent = QModelIndex())const;返回父节点有多少列,示例代码:
        int CMyModel::columnCount(const QModelIndex &parent)const
        {
             return pData->colCount();
        }
(5)接下来看QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;的实现:       
        QModelIndex CMChModel::index(int row, int column, const QModelIndex &parent) const
        {
            return createIndex(row, column, NULL);
        }
通过该函数创建节点的索引,我这里简单一点,只创建一层节点,若大家要创建多层节点,则要通parent索引里的数据(如internalPointer())来创建对应的节点,通过createIndex的第3个参数来向节点传入指定的索引数据,由于我这里不需要创建多层节点,所以我传入NULL。那麽我们怎么建立节点的父子关系呢,聪明的你肯定想到了吧,QModelIndex parent(const QModelIndex &index) const;就是建立父子节点的父子关系的,如果只有一层节点,该函数无须重载,至此我们的显示数据模型已经实现了,接下来就是要把这个模型通过视图来表现出来(QTreeView)。
我们看看QTreeView是怎么来显示我们上面建立的模型的,看一段简单的代码,大家就明白了:
       QTreeView treeView; 
       CMyModel *pModel = new CMyModel();
       treeView.setModel(pModel);
       treeView.show();
2.是不是很简单,到这里通过QTreeView来处理大量的数据的实现方式差不多讲完了,当你按照上面的方式准备处理你的大批量数据时,你就会发现显示10000行数据还是会卡,这是什么原因呢?其实这个也困扰我很久,网上也没有找到相关资料,没办法啊,只有啃QTreeView实现的源代码,实际上QTreeView确实是显示可见部分数据(每次显示1000行数据,按理论上来说1000行足以占据计算机屏幕,这样一来不管你数据量是多大,我始终只取1000行数据,所以1亿条数据与1000条数据显示速度是一样的,只要你一次只读出要显示的数据,内存占有量也是一样的),既然问题都解决了,那麽为什么还会有卡的现象呢?
经过进一步研究QTreeView源代码,最后发现耗时的地方是QTreeView第一次刷新的时候会计算每一行的行高,这样刷新时,就要遍历所有行数据,原来卡是出在这个地方啊,那麽应该怎么解决呢?实现方法很简单,就是上面的代码多加一行,treeView.setUniformRowHeights(true);这样一来就不会刷新计算所有行的行高了,至此用QTreeView显示大批量数据问题得到解决。
最后再补充一下,该实现方式可以适应其他QTableView, QListView等视图。
<think> 我们之前讨论了如何在PyQt5的QTreeView显示图标,现在用户的新问题是关于如何清空QTreeView数据显示,特别是当使用自定义的QAbstractItemModel时。 根据引用[2]和引用[3],我们知道在PyQt5中,QTreeView数据是通过模型(model)来提供的。因此,要清空QTreeView数据,实际上就是要清空模型的数据。 注意:用户使用了自定义模型(基于QAbstractItemModel)和自定义的数据结构(如MyTreeItem)。因此,我们需要分别考虑两种情况: 1. 使用Qt内置的模型(如QStandardItemModel)时如何清空 2. 使用自定义模型(继承自QAbstractItemModel)并配合自定义的数据结构(如MyTreeItem)时如何清空 下面我将分别说明: 一、使用QStandardItemModel清空数据 QStandardItemModel提供了clear()方法,可以直接清空所有数据。 示例代码: model = QStandardItemModel() treeView.setModel(model) # 添加一些数据... item1 = QStandardItem("Item1") model.appendRow(item1) # 清空数据 model.clear() # 这会移除所有行(包括表头) 注意:使用clear()后,模型会重置(包括表头也会被清空),如果需要重新设置表头,可以在clear之后重新设置。 二、使用自定义模型(基于QAbstractItemModel)和自定义数据结构(如MyTreeItem)清空数据 在这种情况下,由于模型的数据结构是自定义的,我们需要自己实现清空方法。通常,我们需要: 1. 在自定义模型类中添加一个清空数据的方法(例如clear()) 2. 在这个方法中,首先要移除所有行(这意味着要通知视图数据即将改变) 3. 然后重置内部的数据结构(如根节点) 4. 最后通知视图数据已经重置 根据引用[3]中自定义树模型的数据结构(MyTreeItem),我们可以这样实现: 步骤: a. 在自定义模型中定义clear方法: def clear(self): # 清除所有数据,重置为只有根节点 self.beginResetModel() # 通知视图模型即将重置 # 重新初始化根节点(清空所有子节点) self.rootItem = MyTreeItem(("Column1", "Column2")) # 假设根节点数据为表头 # 或者如果你之前有多个根节点,现在只需要重置为一个空的根节点 self.endResetModel() # 通知视图模型重置完成 b. 注意:MyTreeItem类需要能够支持清空子节点。在MyTreeItem中,我们可以添加一个方法来清空子节点,但通常我们在模型重置时直接替换根节点更为简单。 但是,如果你的模型已经有很多数据,直接替换根节点可以避免内存泄漏(确保旧的根节点被删除)。这样,视图就会刷新并显示空白。 三、另一种情况:使用QFileSystemModel 对于文件系统模型,清空数据通常不需要,因为它是动态加载的。如果你要改变根路径,可以使用setRootPath方法。但如果你要清空,可以设置一个空路径(如空字符串)或者重置模型。 四、注意事项 1. 在清空大量数据时,使用beginResetModel()/endResetModel()是最有效的方式,因为它会通知视图整个模型结构发生了变化,视图会重新加载所有数据。 2. 如果你只想删除部分数据,应该使用beginRemoveRows()和endRemoveRows(),但清空全部数据时使用重置更高效。 示例代码(自定义模型的clear方法): class CustomTreeModel(QAbstractItemModel): def __init__(self, parent=None): super().__init__(parent) self.rootItem = MyTreeItem(("Header1", "Header2")) # 根节点,初始化为表头 def clear(self): self.beginResetModel() # 释放根节点的所有子节点(如果有的话),避免内存泄漏 # 这里我们直接新建一个根节点,旧节点会被垃圾回收(注意:如果子节点有循环引用,可能需要额外处理) self.rootItem = MyTreeItem(("Header1", "Header2")) self.endResetModel() # 使用: model = CustomTreeModel() treeView.setModel(model) # ...添加数据... model.clear() # 清空数据 如果你的自定义模型中,根节点本身不显示(作为所有顶层节点的父节点),那么清空就是删除根节点的所有子节点。这时,你可以: def clear(self): if self.rootItem.childCount() > 0: self.beginRemoveRows(QModelIndex(), 0, self.rootItem.childCount()-1) # 删除所有子节点 self.rootItem.childItems = [] # 或者调用一个清除子节点的方法 self.endRemoveRows() 但是,由于删除的行数可能很大,使用重置模型(reset)更简单且性能也可以接受。 总结: 1. 对于标准模型(QStandardItemModel),使用clear()方法。 2. 对于自定义模型,实现一个clear方法,在方法中使用beginResetModel()和endResetModel()来重置整个模型。 最后,清空后如果需要重新设置表头,可以在clear之后重新设置表头标签。 相关问题: 1. 如何在PyQt5的QTreeView中动态加载大量数据? 2. QAbstractItemModel中beginResetModel和endResetModel的作用是什么? 3. 如何在自定义模型中实现节点的删除操作? 4. 清空数据后如何避免内存泄漏? </think> ### PyQt5中清空QTreeView数据的实现方法 在PyQt5中使用QTreeView和QAbstractItemModel清空数据,主要有以下两种实现方式: #### 一、使用内置模型的clear()方法(推荐) ```python # 使用QStandardItemModel作为模型 self.model = QStandardItemModel() self.treeView.setModel(self.model) # 清空数据方法 def clear_tree_data(self): self.model.clear() # 核心清空方法 self.model.setHorizontalHeaderLabels(['列1', '列2']) # 可选:重置表头 ``` > **特点**: > - 单行代码即可清空所有数据节点 > - 自动处理视图刷新 > - 适用于QStandardItemModel[^1] #### 二、自定义模型的清空实现(高级) 当使用继承QAbstractItemModel的自定义模型时,需实现自定义清空逻辑: ```python class CustomTreeModel(QAbstractItemModel): def __init__(self, parent=None): super().__init__(parent) self.rootItem = MyTreeItem(()) # 根节点 # 清空方法实现 def clear(self): self.beginResetModel() # 通知视图即将重置 # 清空根节点的所有子节点 self.rootItem.childItems = [] self.endResetModel() # 通知视图重置完成 # 自定义树节点类 (参考引用[3]) class MyTreeItem: def __init__(self, data, parent=None): self.parentItem = parent self.childItems = [] # 子节点集合 self.itemData = data # 使用示例 model = CustomTreeModel() self.treeView.setModel(model) # 清空数据 model.clear() ``` > **关键点**: > 1. `beginResetModel()`/`endResetModel()`:必须成对使用,通知视图模型结构变化 > 2. 清空原理:移除根节点的所有子节点引用 > 3. 内存管理:Python垃圾回收机制会自动处理节点对象[^2] #### 三、清空操作的注意事项 1. **视图刷新**: ```python # 强制刷新视图(清空后自动处理,一般不需额外调用) self.treeView.reset() ``` 2. **表头处理**: ```python # 清空后保留表头 headers = self.model.horizontalHeaderLabels() self.model.clear() self.model.setHorizontalHeaderLabels(headers) ``` 3. **批量删除优化**: ```python # 大量数据时使用分批删除 while self.model.rowCount() > 0: self.model.removeRow(0) ``` #### 四、方法对比 | 方法 | 适用场景 | 优点 | 缺点 | |------|----------|------|------| | `clear()` | 标准模型 | 简单高效 | 会清除表头 | | 自定义clear | 自定义模型 | 灵活控制 | 需手动实现 | | 逐行删除 | 选择性删除 | 精确控制 | 性能较差 | > **最佳实践**:优先使用内置模型的`clear()`方法;自定义模型必须实现`beginResetModel()/endResetModel()`机制[^3] ### 相关问题 1. 如何在清空QTreeView时保留特定的根节点? 2. QTreeView大量数据删除时的性能优化方法? 3. 如何实现QTreeView数据撤销/重做功能? 4. QAbstractItemModel中beginResetModel和endResetModel的工作原理是什么? 5. 如何防止QTreeView清空数据时界面卡顿? [^1]: QStandardItemModel内置清空方法实现 [^2]: 自定义树形数据结构的内存管理机制 [^3]: 模型重置通知机制的正确使用方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值