QTreeView的系类文章如下,可根据熟练程度自行食用
QTreeView的使用(一)
QTreeView的使用(二)
QTreeView的使用(三)
PS.本文主要是依据 QTreeView的使用(二)上的代码改过来的!
写作思路
本片博客主要是解决一些在使用QTreeView中遇到的问题
1、在使用QTreeView中使用doubleClicked信号和改变role == Qt.CheckStateRole对象时候的冲突问题
2、刷新树时,层级的收缩状态没有保存的问题
3、自定义过滤器QSortFilterProxyModel的使用技巧
4、在QTreeView中self.selectedIndexes()的坑
1、在使用QTreeView中使用doubleClicked信号和改变role == Qt.CheckStateRole对象时候的冲突问题
问题描述: 如下图gif所示,在点击左侧checkbox的时候,同时会触发doubleClicked信号,这并不是我们想要的效果,初始关键代码如下:
窗口即数据model关键代码如下,详细请看上前面系列文章
def __init__(self):
......
self.treeView.doubleClicked.connect(self.onDoubleClicked)
.....
def onDoubleClicked(self, index):
print("onDoubleClicked", index)
def data(self, index, role):
......
elif role == Qt.CheckStateRole:
if item.isShow is True:
return Qt.Checked
else:
return Qt.Unchecked
......
def setData(self, index, value, role):
item = index.internalPointer()
if role == Qt.CheckStateRole:
item.isShow = False
.......
else:
item.isShow = True
.......
return True
为了避免上述问题,我们需要在自定义item的时候做一点小手脚
def __init__(self):
......
self.isChecking = True
self.treeView.setItemDelegate(ItemDelegate(self.treeView))
self.treeView.doubleClicked.connect(self.onDoubleClicked)
.....
def onDoubleClicked(self, index):
if self.isChecking is False:
print("onDoubleClicked", index)
class ItemDelegate(QItemDelegate):
def __init__(self, parent):
QItemDelegate.__init__(self, parent)
......
self.checkDistance = 0
self.iconWidth = 0
def drawCheck(self, painter, option, rect, state):
self.checkDistance = rect.left() - option.rect.left()
self.iconWidth = rect.width()
super(ItemDelegate, self).drawCheck(painter, option, rect, state)
def editorEvent(self, event, model, option, index):
checkRect = QRect(option.rect.left()+self.checkDistance, option.rect.top(),
self.iconWidth, self.iconWidth)
if checkRect.contains(event.pos()):
hierarchy.isChecking = True
else:
hierarchy.isChecking = False
.......
变化:
相比起之前,我们设置了代理 “self.treeView.setItemDelegate(ItemDelegate(self.treeView))”,
并且多了一个变量,剩下的就是在 editorEvent这个方法里面判断其是否在CheckBox的Rect内
2、刷新树时,层级的收缩状态没有保存的问题
每次都需要重新整理之前的收缩状态!十分烦!!!
做一点小手脚:
def createTree(self):
self.model = FloderTreeModel(self.data)
self.proxy.setSourceModel(self.model)
self.isReload = True
self.treeView.collapseAll()
self.isReload = False
if len(BluePrint.editorView.scene.hierachyItemNameIndexMap.items()) == 0:
self.treeView.expandAll()
else:
for name, data in BluePrint.editorView.scene.expandStateDict.items():
pos = data[len(data)-1]
index = self.treeView.model().index(pos[0], pos[1], self.treeView.rootIndex())
for i in range(len(data)-2, -1, -1):
pos = data[i]
index = self.treeView.model().index(pos[0], pos[1], index)
self.treeView.expand(index)
def onCollapsed(self, index):
self.treeView.model().data(index, Qt.UserRole).isCollapsed = True
if self.isReload is False:
if BluePrint.editorView.scene.expandStateDict.get(self.treeView.model().data(index, Qt.UserRole).itemData[0], False):
BluePrint.editorView.scene.expandStateDict.pop(self.treeView.model().data(index, Qt.UserRole).itemData[0])
def onExpanded(self, index):
self.treeView.model().data(index, Qt.UserRole).isCollapsed = False
if self.isReload is False:
tempIndex = index
itemPosList = [[index.row(), index.column()]]
while tempIndex.parent().row() != -1 or tempIndex.parent().column() != -1:
itemPosList.append([tempIndex.parent().row(), tempIndex.parent().column()])
tempIndex = tempIndex.parent()
BluePrint.editorView.scene.expandStateDict[self.treeView.model().data(index, Qt.UserRole).itemData[0]] = itemPosList
变化:
树收缩状态变化的时候记录下该节点的这个状态,加载的时候让这个节点重新变成该状态,这个方法很笨,但是却能解决这个问题,并且性能消耗其实也不会很多,除非树的深度及节点十分多(应该会优化的)
3、自定义过滤器QSortFilterProxyModel的使用技巧
def __init__(self, parent=None):
self.treeView = QTreeView()
self.treeView.setModel(self.proxy)
self.proxy = SearchFilterProxyModel()
self.model = QAbstractItemModel(self.data)
self.proxy.setSourceModel(self.model)
class SearchFilterProxyModel(QSortFilterProxyModel):
def __init__(self):
super(SearchFilterProxyModel, self).__init__()
def filterAcceptsRow(self, row_num, source_parent):
model = self.sourceModel()
source_index = model.index(row_num, 0, source_parent)
.....
if "填入你想要的条件":
return True
技巧:
1、首先将数据data给SearchFilterProxyModel,SearchFilterProxyModel给SearchFilterProxyModel,最后将SearchFilterProxyModel设置到QTreeView
2、在“filterAcceptsRow”方法中,QSortFilterProxyModel会让返回True的item显示出来,至于最重要的东西,我们可以在自定义model中先赋好值,然后通过“model().data(index, Qt.UserRole)”获取我们想要的值对象,并拿来进行判断
4、在QTreeView中QTreeView.selectedIndexes()的坑
在一次使用 QTreeView.selectedIndexes() 的时候,发现返回的list长度与选中的节点数量不一致,当时的代码如下
class TreeItem(object):
def __init__(self, data, parent=None, isShow=True):
self.parentItem = parent
self.itemData = data
self.childItems = []
self.isShow = isShow
self.isCollapsed = True
......
def columnCount(self):
return len(self.itemData)
.......
(详细代码往前翻之前的文章!)
之前一直没有发现的坑 这里的columeCount,因为QTreeView实际上他的列数,最多就是1,如果这里返回的值不是1,就会导致在调用 QTreeView.selectedIndexes() 的时候,返回的列表不对,比如返回的是2,那么此时返回的就会是两个item,如下图
并且,得到的QTreeView.selectedIndexes()就会是原来的两倍!因此在这里应该这么写
class TreeItem(object):
def __init__(self, data, parent=None, isShow=True):
self.parentItem = parent
self.itemData = data
self.childItems = []
self.isShow = isShow
self.isCollapsed = True
......
def columnCount(self):
return 1
.......
当然了,如果真的有需要两行,最好的建议是使用QTreeWidget这个类去继承