python - 当源模型中的“removeRows()”时自定义代理模型崩溃
问题描述
我最近正在学习如何使用代理模型,并且我想创建一个可以将源树模型中的节点扁平化为列表模型的自定义代理模型,我找到了一个很好的解决方案: How to create a proxy model that would将 QAbstractItemModel 的节点展平为 PySide 中的列表?
但是,当我尝试removeRows()
从源树模型中(删除树节点)时,代理模型崩溃,我猜这是因为源模型没有layoutChanged
向代理模型发出信号以刷新self.m_rowMap和self.m_indexMap?
【问题1】:如何修复崩溃?
【问题2】:对于QSortFilterProxyModel
,removeRows()
从源模型不会使代理模型崩溃,所以我也想知道它的底层机制QSortFilterProxyModel
,特别是以下方法的实现:
setSourceModel(),
mapFromSource(),
mapToSource(),
mapSelectionFromSource(),
mapSelectionToSource()
sourceModel
尤其是它如何在和之间发出信号QSortFilterProxyModel
?
重现示例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import (QAbstractProxyModel, QModelIndex, pyqtSlot)
class FlatProxyModel(QAbstractProxyModel):
def __init__(self, parent=None):
# super(FlatProxyModel, self).__init__(parent)
super().__init__(parent)
def buildMap(self, model, parent=QModelIndex(), row=0):
"""
Usage:
* to build the rowMap and indexMap of the treeModel
"""
if row == 0:
self.m_rowMap = {}
self.m_indexMap = {}
rows = model.rowCount(parent)
for r in range(rows):
index = model.index(r, 0, parent)
print('row', row, 'item', model.data(index))
self.m_rowMap[index] = row
self.m_indexMap[row] = index
row = row + 1
if model.hasChildren(index):
row = self.buildMap(model, index, row)
return row
def setSourceModel(self, model):
QAbstractProxyModel.setSourceModel(self, model)
self.buildMap(model)
print(flush=True)
model.dataChanged.connect(self.sourceDataChanged)
def mapFromSource(self, index):
if index not in self.m_rowMap:
return QModelIndex()
# print('mapping to row', self.m_rowMap[index], flush = True)
return self.createIndex(self.m_rowMap[index], index.column())
def mapToSource(self, index):
if not index.isValid() or (index.row() not in self.m_indexMap):
return QModelIndex()
# print('mapping from row', index.row(), flush = True)
return self.m_indexMap[index.row()]
def columnCount(self, parent):
return QAbstractProxyModel.sourceModel(self).columnCount(self.mapToSource(parent))
def rowCount(self, parent):
# print('rows:', len(self.m_rowMap), flush=True)
return len(self.m_rowMap) if not parent.isValid() else 0
def index(self, row, column, parent):
# print('index for:', row, column, flush=True)
if parent.isValid():
return QModelIndex()
return self.createIndex(row, column)
def parent(self, index):
return QModelIndex()
@pyqtSlot(QModelIndex, QModelIndex)
def sourceDataChanged(self, topLeft, bottomRight):
self.dataChanged.emit(self.mapFromSource(topLeft),
self.mapFromSource(bottomRight))
class myWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel()
names = ['Foo', 'Bar', 'Baz']
for first in names:
row = QtGui.QStandardItem(first)
for second in names:
row.appendRow(QtGui.QStandardItem(first + second))
self.model.appendRow(row)
self.proxy = FlatProxyModel()
self.proxy.setSourceModel(self.model)
self.nestedProxy = FlatProxyModel()
self.nestedProxy.setSourceModel(self.proxy)
vLayout = QtWidgets.QVBoxLayout(self)
hLayout = QtWidgets.QHBoxLayout()
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.model)
self.treeView.expandAll()
self.treeView.header().hide()
hLayout.addWidget(self.treeView)
self.listView1 = QtWidgets.QListView()
self.listView1.setModel(self.proxy)
hLayout.addWidget(self.listView1)
self.listView2 = QtWidgets.QListView()
self.listView2.setModel(self.nestedProxy)
hLayout.addWidget(self.listView2)
vLayout.addLayout(hLayout)
removeButton = QtWidgets.QPushButton('Remove')
removeButton.clicked.connect(self.removeItems)
vLayout.addWidget(removeButton)
def removeItems(self):
index = self.treeView.currentIndex()
model = index.model()
model.removeRows(index.row(), 1, index.parent())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = myWidget()
w.show()
sys.exit(app.exec_())
解决方案
问题是当从源模型中删除一个项目时,不会通知代理并且“地图”没有更新。一种可能的解决方案是将 rowsRemoved 信号连接到 buildMap。
class FlatProxyModel(QAbstractProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.connections = []
def buildMap(self, model, parent=QModelIndex(), row=0):
# ...
def setSourceModel(self, model):
if self.sourceModel() is not None:
for connection in self.connections:
self.sourceModel().disconnect(connection)
QAbstractProxyModel.setSourceModel(self, model)
if self.sourceModel() is None:
self.connections = []
return
self.connections = [
self.sourceModel().dataChanged.connect(self.sourceDataChanged),
self.sourceModel().rowsRemoved.connect(self.reload_model),
self.sourceModel().modelReset.connect(self.reload_model),
self.sourceModel().rowsInserted.connect(self.reload_model)
]
self.reload_model()
def reload_model(self):
self.beginResetModel()
self.buildMap(self.sourceModel())
self.endResetModel()
# ...
def removeItems(self):
index = self.treeView.currentIndex()
if not index.isValid():
return
model = index.model()
model.removeRows(index.row(), 1, index.parent())
推荐阅读
- python - 为什么我从 QuantLib 中的不同信用定价引擎得到不同的结果
- kubernetes - 如何允许 Argo 工作流(Kind 集群)工作容器访问主机端口?
- angular - 以角度更改 intl-tel-input 字段宽度
- google-api - Youtube API 是否获得许可?
- tensorflow - 加载多个保存的 tensorflow/keras 模型进行预测
- sql - 雅典娜/普雷斯托 | 自加入时无法匹配 ID 行
- python - 高效切片三角稀疏矩阵
- tsql - SQL server 查询计算后得到结果
- javascript - 尝试将底部栏与响应式侧边栏放在一起
- ios - DispatchSemaphore 导致应用程序冻结,如何解决