(PyQt)QTreeView - 想要扩展/折叠所有子孙

时间:2010-11-04 18:50:38

标签: qt pyqt expand collapse qtreeview

我希望能够在QTreeView中扩展或折叠特定分支的所有子项。我正在使用PyQt4。

我知道QTreeView有一个扩展所有绑定*的子功能,但我需要两件事:它需要绑定到不同的键组合(shift-space),我还需要能够折叠所有孩子们也是。

这是我到目前为止所尝试的内容: 我有一个QTreeView的子类,其中我正在检查shift-space键组合。我知道QModelIndex会让我选择具有“子”功能的特定孩子,但这需要知道孩子的数量。我能够通过查看internalPointer来获取子项的计数,但这只能为我提供层次结构的第一级信息。如果我尝试使用递归,我可以得到一堆子计数,但后来我迷失了如何将这些转换回有效的QModelIndex。

以下是一些代码:

def keyPressEvent(self, event):
    """
    Capture key press events to handle:
    - enable/disable
    """
    #shift - space means toggle expanded/collapsed for all children
    if (event.key() == QtCore.Qt.Key_Space and 
        event.modifiers() & QtCore.Qt.ShiftModifier):
        expanded = self.isExpanded(self.selectedIndexes()[0])
        for cellIndex in self.selectedIndexes():
            if cellIndex.column() == 0: #only need to call it once per row
                #I can get the actual object represented here
                item = cellIndex.internalPointer()
                #and I can get the number of children from that
                numChildren = item.get_child_count()
                #but now what? How do I convert this number into valid
                #QModelIndex objects? I know I could use: 
                #   cellIndex.child(row, 0)
                #to get the immediate children's QModelIndex's, but how
                #would I deal with grandchildren, great grandchildren, etc...
                self.setExpanded(cellIndex, not(expanded))
        return

这是我正在研究的递归方法的开始,但是当我真正尝试设置扩展状态时我陷入困境,因为一旦进入递归,我就会失去与任何有效QModelIndex的“联系”......

def toggle_expanded(self, item, expand):
    """
    Toggles the children of item (recursively)
    """
    for row in range(0,item.get_child_count()):
        newItem = item.get_child_at_row(row)
        self.toggle_expanded(newItem, expand)
    #well... I'm stuck here because I'd like to toggle the expanded
    #setting of the "current" item, but I don't know how to convert
    #my pointer to the object represented in the tree view back into
    #a valid QModelIndex
    #self.setExpanded(?????, expand)   #<- What I'd like to run
    print "Setting", item.get_name(), "to", str(expand) #<- simple debug statement that indicates that the concept is valid

感谢大家花时间看这个!

4 个答案:

答案 0 :(得分:4)

好的......兄弟姐妹实际上并没有把我送到我想去的地方。我设法使代码工作如下(它似乎是一个体面的实现)。 Kudos仍然感谢Prof.Ebral让我按照兄弟姐妹的想法走上正轨(结果我需要使用QModelIndex.child(行,列)并从那里递归迭代)。

请注意,代码中有以下假设:它假定您的基础数据存储对象能够报告他们拥有多少个孩子(我的代码中有get_child_count())。如果不是这种情况,你将不知何故必须以不同的方式计算一个孩子...也许只是通过随意尝试获取子索引 - 使用QModelIndex.child(row,col) - 随着行数不断增加直到你回来索引无效? - 这就是Prof.Ebral所建议的,我可能仍然会尝试(只是我已经有一种简单的方法可以通过从我的数据存储中请求来计算子数)。

另请注意,我实际上是根据我是在扩展还是在折叠时在递归的不同点扩展/碰撞每个节点。这是因为,通过反复试验,我发现如果我只是在代码中的某个位置执行它,动画树视图会断断续续。现在,通过根据我是否处于最高级别(即我正在影响的分支的根目录 - 而不是整个树视图的根目录)来反转我执行它的顺序,我得到了一个很好的平滑动画。这将在下面记录。

以下代码位于QTreeView子类中。

#---------------------------------------------------------------------------
def keyPressEvent(self, event):

    if (event.key() == QtCore.Qt.Key_Space and self.currentIndex().column() == 0):
        shift = event.modifiers() & QtCore.Qt.ShiftModifier
        if shift:
            self.expand_all(self.currentIndex())
        else:                
            expand = not(self.isExpanded(self.currentIndex()))
            self.setExpanded(self.currentIndex(), expand)


#---------------------------------------------------------------------------
def expand_all(self, index):
    """
    Expands/collapses all the children and grandchildren etc. of index.
    """
    expand = not(self.isExpanded(index))
    if not expand: #if collapsing, do that first (wonky animation otherwise)
        self.setExpanded(index, expand)    
    childCount = index.internalPointer().get_child_count()
    self.recursive_expand(index, childCount, expand)
    if expand: #if expanding, do that last (wonky animation otherwise)
        self.setExpanded(index, expand)


#---------------------------------------------------------------------------
def recursive_expand(self, index, childCount, expand):
    """
    Recursively expands/collpases all the children of index.
    """
    for childNo in range(0, childCount):
        childIndex = index.child(childNo, 0)
        if expand: #if expanding, do that first (wonky animation otherwise)
            self.setExpanded(childIndex, expand)
        subChildCount = childIndex.internalPointer().get_child_count()
        if subChildCount > 0:
            self.recursive_expand(childIndex, subChildCount, expand)
        if not expand: #if collapsing, do it last (wonky animation otherwise)
            self.setExpanded(childIndex, expand)

答案 1 :(得分:2)

model.rowCount(index)是你想要的方法。

model = index.model()   # or some other way of getting it
for i in xrange(model.rowCount(index)):
  child = model.index(i,0, index)
  # do something with child

model.index(row,col,parent)与调用index.child(row,col)基本相同;只是间接较少。

答案 2 :(得分:1)

我建议使用继承QTreeView的QTreeWidget。然后,您可以将子项作为QTreeWidgetItem获取。

由于您不想使用QTreeWidget但希望坚持使用当前模型..您可以使用.isValid()遍历'可能'的子项。您不应该使用internalPointer()。而是使用你拥有的cellItem,因为它是原始的ModalIndex ..然后尝试找到它的兄弟姐妹。像

这样的东西
x = 0; y =0
while cellIndex.sibling(x, y).isValid():
    child = cellIndex.sibling(x, y)
    x += 1

答案 3 :(得分:1)

我为此创建了一个 evnetFilter 类。 我的特定用例是按 Shift 单击放置指示器,然后展开或折叠所有子节点,如软件 maya 大纲。


class MTreeExpandHook(QtCore.QObject):
    """
    MTreeExpandHook( QTreeView )
    """

    def __init__(self, tree):
        super(MTreeExpandHook, self).__init__()
        tree.viewport().installEventFilter(self)
        self.tree = tree

    def eventFilter(self, receiver, event):
        if (
            event.type() == QtCore.QEvent.Type.MouseButtonPress
            and event.modifiers() & QtCore.Qt.ShiftModifier
        ):
            pos = self.tree.mapFromGlobal(QtGui.QCursor.pos())
            index = self.tree.indexAt(pos)
            if not self.tree.isExpanded(index):
                self.tree.expandRecursively(index)
                return True
        return super(MTreeExpandHook, self).eventFilter(self.tree, event)

下面的使用示例


import sys
from PySide2 import QtCore,QtGui,QtWidgets

class MTreeExpandHook(QtCore.QObject):
    """
    MTreeExpandHook( QTreeView )
    """

    def __init__(self, tree):
        super(MTreeExpandHook, self).__init__()
        self.setParent(tree)
        # NOTE viewport for click event listen
        tree.viewport().installEventFilter(self)
        self.tree = tree

    def eventFilter(self, receiver, event):
        if (
            # NOTE mouse left click 
            event.type() == QtCore.QEvent.Type.MouseButtonPress
            # NOTE keyboard shift press
            and event.modifiers() & QtCore.Qt.ShiftModifier
        ):
            # NOTE get mouse local position
            pos = self.tree.mapFromGlobal(QtGui.QCursor.pos())
            index = self.tree.indexAt(pos)
            if not self.tree.isExpanded(index):
                # NOTE expand all child
                self.tree.expandRecursively(index)
                return True
        return super(MTreeExpandHook, self).eventFilter(self.tree, event)
    

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    
    model = QtGui.QStandardItemModel()
    
    # NOTE create nested data
    for i in range(3):
        parent = QtGui.QStandardItem('Family {}'.format(i))
        for j in range(3):
            child = QtGui.QStandardItem('Child {}'.format(i*3+j))
            for k in range(3):
                sub_child = QtGui.QStandardItem("Sub Child")
                child.appendRow([sub_child])
                for x in range(2):
                    sub_child_2 = QtGui.QStandardItem("Sub Child 2")
                    sub_child.appendRow([sub_child_2])
            parent.appendRow([child])
        model.appendRow(parent)

        
    treeView = QtWidgets.QTreeView()
    treeView.setHeaderHidden(True)
    MTreeExpandHook(treeView)
    treeView.setModel(model)
    treeView.show()
    
    sys.exit(app.exec_())

example gif