(PyQt) 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

Спасибо всем за то, что нашли время, чтобы посмотреть на это!


person bvz    schedule 04.11.2010    source источник


Ответы (4)


Хорошо... братья и сестры на самом деле не привели меня туда, куда я хотел. Мне удалось заставить код работать следующим образом (и это кажется достойной реализацией). Престижность по-прежнему профессору Эбралу, который заставил меня пойти по правильному пути с идеей братьев и сестер (оказывается, мне нужно было использовать QModelIndex.child(row, column) и рекурсивно повторять оттуда).

Обратите внимание, что в коде есть следующее предположение: предполагается, что ваши базовые объекты хранилища данных могут сообщать, сколько у них дочерних элементов (get_child_count() в моем коде). Если это не так, вам каким-то образом придется по-разному подсчитывать количество дочерних элементов... возможно, просто произвольно пытаясь получить дочерние индексы - используя QModelIndex.child(row, col) - с постоянно увеличивающимся количеством строк, пока вы не вернетесь неверный индекс? - это то, что предложил профессор Эбрал, и я все еще могу попробовать это (просто у меня уже есть простой способ получить количество детей, запросив его из моего хранилища данных).

Также обратите внимание, что я фактически расширяю/сворачиваю каждый узел в другой момент рекурсии в зависимости от того, расширяюсь я или сворачиваюсь. Это потому, что методом проб и ошибок я обнаружил, что анимированные древовидные представления будут заикаться и всплывать, если я просто сделаю это в одном месте кода. Теперь, изменив порядок, в котором я это делаю, в зависимости от того, нахожусь ли я на верхнем уровне (то есть на корень ветки, на которую я воздействую, а не на корень всего дерева), я получаю красивую плавную анимацию. Это задокументировано ниже.

Следующий код находится в подклассе 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)
person bvz    schedule 17.11.2010

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); просто с меньшим количеством косвенных действий.

person Paul Du Bois    schedule 15.02.2011

Я бы рекомендовал использовать QTreeWidget, который наследует QTreeView. Затем вы можете захватить дочерние элементы как QTreeWidgetItem.

Поскольку вы не хотите использовать QTreeWidget, но хотите придерживаться своей текущей модели... вы можете перебирать "возможных" дочерних элементов, используя .isValid(). Однако вы не должны использовать internalPointer(). Вместо этого используйте имеющийся у вас cellItem, так как это исходный ModalIndex.. затем попытайтесь найти его братьев и сестер. Что-то типа

x = 0; y =0
while cellIndex.sibling(x, y).isValid():
    child = cellIndex.sibling(x, y)
    x += 1
person Community    schedule 09.11.2010
comment
Спасибо, но я полностью привержен использованию модели/представления, и, насколько я знаю, я не могу сделать это с QTreeWidget, не так ли? - person bvz; 09.11.2010
comment
Из документации --- Класс QTreeWidget является удобным классом, который предоставляет стандартный виджет дерева с классическим интерфейсом на основе элементов, подобным тому, который используется классом QListView в Qt 3. Этот класс основан на архитектуре Qt Model/View и использует модель по умолчанию для хранения элементов, каждый из которых является QTreeWidgetItem. - person ; 09.11.2010
comment
Но поскольку у меня уже есть модель, которая выполняет много пользовательской работы, я все еще думаю, что не могу переключиться, потому что модель по умолчанию, встроенная в QTreeWidget, не будет делать ничего из того, что мне нужно. - person bvz; 10.11.2010
comment
Ок.. Тогда еще посмотрю. - person ; 13.11.2010
comment
Братья и сестры! Может просто в этом дело. Спасибо! - person bvz; 17.11.2010

Для этого я создаю класс evnetFilter. Мой конкретный вариант использования — Shift, щелчок по индикатору перетаскивания, а затем разворачивание всех или свертывание всех дочерних узлов, таких как программное обеспечение maya Outliner.


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_())

пример gif

person Tim    schedule 14.07.2021