我有一个我想建模的树数据结构。树由不同的项类型组成,比如TypeA,TypeB和TypeC。
TypeA只能有TypeA或TypeB的孩子。 TypeB只能拥有TypeB或TypeC的子级,而TypeC只能拥有TypeC的子级。
我希望将整个树保留在内部结构中,并且只提供TypeA / B / C的主题部分视图。此外,如果某些子树相同,我想压缩视图。
我认为我可以使用代理执行此操作,但我无法让它正常工作。到目前为止,这是我的代码:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.Qt import Qt, QVariant, QModelIndex
from PyQt5.QtCore import QAbstractItemModel, QIdentityProxyModel, pyqtSignal
class TreeItem():
"""
Adapted for python from http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
"""
def __init__(self, data, parent=None):
self._child_items = []
self._item_data = data
self._parent_item = parent
if parent:
parent.append_child(self)
def __str__(self):
return "%s: %s" % (self.__class__, self._item_data)
def append_child(self, child):
if not isinstance(child, TreeItem):
raise ('Some exception todo')
self._child_items.append(child)
def get_child(self, item_data):
"""
Check if a child of the passed item data exists. if so return it
:param item_data:
:return: The found child. None if matching child exists
"""
child_item_data = [child._item_data for child in self._child_items]
if item_data in child_item_data:
idx = child_item_data.index(item_data)
return self.child(idx)
return None
def child(self, row):
"""
In the tree several directories, can be under the current one as children.
:param row: Index of sub-directory to return
:return: a sub-directory
"""
return self._child_items[row]
def child_count(self):
return len(self._child_items)
def column_count(self):
"""
A PathTreeItem has exactly one column, the directory name
:return: 1
"""
return 1
def data(self, column):
"""
PathTreeItem has only one colum, returning its data
:param column: can only be 0 for PathTreeItem
:return: the path part / directory name
"""
if column != 0:
raise IndexError
return self._item_data
def row(self):
"""
:return: Index in the parent tree item
"""
if self._parent_item:
return self._parent_item._child_items.index(self)
return 0
def parent_item(self):
return self._parent_item
class ItemTypeA(TreeItem):
pass
class ItemTypeB(TreeItem):
pass
class ItemTypeC(TreeItem):
pass
class InternalTreeModel(QAbstractItemModel):
"""
Adapted for python from http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
"""
items_changed = pyqtSignal(QAbstractItemModel)
def __init__(self, parent=None):
super(InternalTreeModel, self).__init__(parent)
self._root = ItemTypeA('root')
def append_row(self, row):
self._root.append_child(row)
@property
def root(self):
return self._root
def data(self, index: QModelIndex, role: int):
if not index.isValid():
return QVariant()
if not role == Qt.DisplayRole:
return QVariant()
item = index.internalPointer()
return item.data(index.column())
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()):
if not self.hasIndex(row, column, parent):
return QModelIndex()
if not parent.isValid():
parent_item = self._root
else:
parent_item = parent.internalPointer()
child_item = parent_item.child(row)
if child_item:
return self.createIndex(row, column, child_item)
else:
return QModelIndex()
def parent(self, child: QModelIndex):
if not child.isValid():
return QModelIndex()
child_item = child.internalPointer()
parent_item = child_item.parent_item()
if parent_item == self._root:
return QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def rowCount(self, parent: QModelIndex = QModelIndex()):
if parent.column() > 0:
return 0
if not parent.isValid():
parent_item = self._root
else:
parent_item = parent.internalPointer()
return parent_item.child_count()
def columnCount(self, parent: QModelIndex = QModelIndex()):
if parent.isValid():
return parent.internalPointer().column_count()
else:
return self._root.column_count()
class TypeAModel(QIdentityProxyModel):
def mapFromSource(self, sourceIndex: QModelIndex):
proxy_index = super().mapFromSource(sourceIndex)
if not proxy_index.isValid():
return QModelIndex()
tree_item = proxy_index.internalPointer()
if not isinstance(tree_item, ItemTypeA):
return QModelIndex()
return proxy_index
class TypeBModel(QIdentityProxyModel):
def mapToSource(self, proxy_index: QModelIndex):
if not self.sourceModel() or not proxy_index.isValid():
return QModelIndex()
assert proxy_index.model() == self
# tree_item = proxy_index.internalPointer()
# print("proxy data: r:%d, c:%d, %s" % (proxy_index.row(), proxy_index.column(), tree_item.data(0)))
return self.sourceModel().createIndex(proxy_index.row(), proxy_index.column(), proxy_index.internalPointer())
def mapFromSource(self, source_index: QModelIndex):
if not self.sourceModel() or not source_index.isValid():
return QModelIndex()
assert source_index.model() == self.sourceModel()
tree_item = source_index.internalPointer()
if not source_index.parent().isValid():
# this is a top level item
# map it to the first FileTreeItem in its tree
child_item = source_index.child(0,0)
parent = source_index
while not isinstance(child_item.internalPointer(), ItemTypeB):
if not child_item.isValid():
# no FileTreeItem found in this tree
return QModelIndex()
# print(child_item.data(0))
parent = child_item
child_item = child_item.child(0,0)
child_inst = child_item.internalPointer()
parent_inst = parent.internalPointer()
return self.createIndex(child_item.row(), child_item.column(), child_item.internalPointer())
# return self.createIndex(source_index.row(), source_index.column(), child_item.internalPointer())
tree_item = source_index.internalPointer()
if not isinstance(tree_item, ItemTypeB):
return QModelIndex()
print(
"direct map: col: %d, row: %d, %s" % (source_index.column(), source_index.row(), source_index.internalPointer().data(0)))
return self.createIndex(source_index.row(), source_index.column(), source_index.internalPointer())
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
model = InternalTreeModel()
a_items = ['Foo', 'Bar', 'Baz']
b_items = ['red', 'green', 'blue']
c_items = ['alpha', 'beta', 'gamma']
for first in a_items:
row1 = ItemTypeA("TypeA " + first, model.root)
for second in a_items:
row2 = ItemTypeA("TypeA " + second, row1)
for third in a_items:
row3 = ItemTypeA("TypeA " + third, row2)
for fourth in b_items:
row4 = ItemTypeB("TypeB " + fourth, row3)
for fifth in b_items:
row5 = ItemTypeB("TypeB " + fifth, row4)
for sixth in c_items:
row6 = ItemTypeC("TypeC " + sixth, row5)
for seventh in c_items:
row7 = ItemTypeC("TypeC " + seventh, row6)
proxy = TypeAModel()
proxy.setSourceModel(model)
proxy2 = TypeBModel()
proxy2.setSourceModel(model)
w = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(w)
view = QtWidgets.QTreeView()
view.setModel(model)
view.expandAll()
# view.header().hide()
layout.addWidget(view)
view = QtWidgets.QTreeView()
view.setModel(proxy)
view.expandAll()
layout.addWidget(view)
view = QtWidgets.QTreeView()
view.setModel(proxy2)
view.expandAll()
layout.addWidget(view)
w.show()
# view.reset()
sys.exit(app.exec_())
左侧显示完整的树。如果我让它正常工作,我会有这个内部,而不是向用户显示它。
在中间我只显示TypeA项目的树。这似乎工作正常。但是,即使视图不允许,叶子仍然显示为可扩展。
在右边我想显示TypeB的项目。但是,显示的树太多了。由于树木相同,我只想展示其中一棵。即视图/代理应该合并重复的树。
我的最终目标是能够从每个视图中选择项目,并在内部树中生成所有匹配项目的选择。首先必须让视图工作。