PyQt5 Treeview нежелательное расширение индекса при использовании QSortFilterProxyModel в подклассе QtCore.QAbstractItemModel - PullRequest
0 голосов
/ 07 апреля 2020

Моя проблема возникает, когда я раскрываю любой элемент в своем дереве (кроме верхнего элемента) и выбираю его дочерний элемент. Затем верхний элемент также расширяется, чтобы показать его потомков.

Пример:
У меня есть 2 детей в возрасте до root, у каждого из которых есть собственный дочерний элемент:

root
|-A  
  |-child AA
| 
|-B
  |-child BB 

Проблема: Когда я раскрываю B и выбираю дочерний BB в своем дереве, он также почему-то открывает (расширяет) A.
Если мы добавим еще один элемент в root, C с ребенком CC и расширьте это вместо B; A будет по-прежнему расширяться, как только будет выбран дочерний CC.

Я использую пользовательский класс в качестве базовых элементов и использую свой собственный класс QAbstractItemModel в качестве sourceModel для QSortFilterProxyModel. Я уверен, что проблема лежит где-то в этих двух пользовательских классах, но я не могу понять, почему это происходит.

У меня есть рабочий пример ниже. Он включает в себя несколько фрагментов исходного скрипта, поэтому он может показаться немного грязным.

В верхнем представлении дерева используется прокси-сервер (и возникают проблемы), а в приведенном ниже примере пропускается прокси-сервер, и он не возникли проблемы.

from PyQt5 import QtWidgets, QtCore, QtGui

class MainWindow(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.custom_nodes = [] #A list that will contain all our custom nodes.
        #list of stand-in dicts:
        self.all_node_list = [{'node_name': 'A', 'parent': None, 'children': [{'node_name': 'AB', 'parent': 'A','children': [{'node_name': 'ABC','parent': 'AB','children': []}]}]},
                              {'node_name': 'Prop', 'parent': None, 'children': [{'node_name': 'SubProp', 'parent': 'Prop','children': [{'node_name': 'Asset', 'parent': 'SubProp', 'children': []}]}]}]
        self.MakeNodesFromDict(self.all_node_list,None)#Make custom nodes from dict
        self.tree_model = CustomModel(self.custom_nodes) #Create custom model
        self.CreateWindow()

    def CreateWindow(self):
        self.setWindowTitle("Top proxy - below normal")
        self.main_layout = QtWidgets.QVBoxLayout()

        #SEARCH BAR
        self.menu_search_bar = QtWidgets.QLineEdit()
        self.main_layout.addWidget(self.menu_search_bar)

        #TREE VIEW USING PROXY MODEL
        self.proxy_tree = QtWidgets.QTreeView()
        self.proxy_tree.setObjectName("treeView")
        self.proxy_tree.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self.proxy_tree.setHeaderHidden(True)
        self.proxy_tree.setExpandsOnDoubleClick(True)
        self.proxy_tree.setAnimated(True)
        self.proxy_tree.setIndentation(20)
        self.proxy_tree.setSortingEnabled(True)
        self.proxy_tree.setObjectName("proxy_treeView")
        self.proxy_tree.setWindowTitle("Dir View")

        #THE PROXY MODEL
        self.proxyModel = QtCore.QSortFilterProxyModel(self.proxy_tree)
        self.proxyModel.setSourceModel(self.tree_model)
        self.proxyModel.setRecursiveFilteringEnabled(True)
        self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.proxy_tree.setModel(self.proxyModel)
        self.proxyModel.sort(0, 0)

        #Connect proxy bar to tree
        self.menu_search_bar.textChanged.connect(self.ProxyUpdate)
        self.menu_search_bar.returnPressed.connect(self.SearchEnter)

        #TREE VIEW WITHOUT PROXY MODEL
        self.tree = QtWidgets.QTreeView()
        self.tree.setObjectName("treeView")
        self.tree.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self.tree.setHeaderHidden(True)
        self.tree.setExpandsOnDoubleClick(True)
        self.tree.setAnimated(True)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(True)
        self.tree.setModel(self.tree_model)

        self.main_layout.addWidget(self.proxy_tree)
        self.main_layout.addWidget(self.tree)
        self.setLayout(self.main_layout)

    ###########################################################
    #################### FUNCTIONS ############################
    ###########################################################
    def MakeNodesFromDict(self,cur_list, cur_parent):
        for t in cur_list:
            cur_node = CustomNode(node_name=t["node_name"], children=[],_parent=cur_parent)
            if cur_parent:
                cur_parent.addChild(cur_node)
            self.MakeNodesFromDict(t["children"], cur_node)
            self.custom_nodes.append(cur_node)

    @QtCore.pyqtSlot(str) # <--Not sure if this is needed?
    def ProxyUpdate(self,text):
        my_pattern = QtCore.QRegularExpression()
        my_pattern.setPatternOptions(QtCore.QRegularExpression.CaseInsensitiveOption)
        my_pattern.setPattern(text)
        self.proxyModel.setFilterRegularExpression(my_pattern)

    def SearchEnter(self): #opens or closes the tree view after search
        if self.menu_search_bar.text() == "":
            self.proxy_tree.collapseAll()
        else:
            self.proxy_tree.expandAll()


class CustomModel(QtCore.QAbstractItemModel): #Custom model to work with custom nodes
    def __init__(self, nodes): #Give all custom nodes at init
        QtCore.QAbstractItemModel.__init__(self)
        self._root = CustomNode() #Create root object of custom model as a custom node aswell
        self.nodes = nodes
        for node in self.nodes:
            if node.parent()==None: #Go through and add nodes with no parents (top nodes)
                self._root.addChild(node)

    def emitDataChanged(self):
        self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

    def rowCount(self, index):
        if index.isValid():
            node = index.internalPointer()
            return node.childCount()
        return self._root.childCount()

    def addChild(self, node, _parent):
        if not _parent or not _parent.isValid():
            parent = self._root
        else:
            parent = _parent.internalPointer()
        parent.addChild(node)

    def index(self, row, column, _parent=None):
        if not _parent or not _parent.isValid():
            node_parent = self._root
        else:
            node_parent = _parent.internalPointer()

        if not QtCore.QAbstractItemModel.hasIndex(self, row, column, _parent):
            return QtCore.QModelIndex()

        child = node_parent.child(row)
        if child:
            return QtCore.QAbstractItemModel.createIndex(self, row, column, child)
        else:
            return QtCore.QModelIndex()

    def parent(self, index):
        if index.isValid():
            p = index.internalPointer().parent()
            if p:
                return QtCore.QAbstractItemModel.createIndex(self, p.row(), 0, p)
        return QtCore.QModelIndex()

    def columnCount(self, index):
        if index.isValid():
            return index.internalPointer().columnCount()
        return self._root.columnCount()

    def data(self, index, role):
        if not index.isValid():
            return None
        node = index.internalPointer()
        if role == QtCore.Qt.DisplayRole:
            return node.data(index.column())
        return None

    def getNode(self, index):
        return index.internalPointer()

    def update(self, index):
        pass

class CustomNode(object):
    def __init__(self, column_count=1,row=0,children=[],node_name="root",_parent=None):
        self._column_count = column_count
        self._row = row
        self.node_name = node_name
        self._parent = _parent
        self._children = children

    def addChild(self, child):
        child._parent = self
        child._row = self.childCount()
        self._children.append(child)
        self._column_count = max(child.columnCount(), self._column_count)

    def GetName(self):
        return self.node_name

    def GetChildren(self):
        return self._children

    def data(self, column):
        return self.node_name

    def columnCount(self):
        return self._column_count

    def childCount(self):
        return len(self._children)

    def child(self, row):
        if row >= 0 and row < self.childCount():
            return self._children[row]

    def parent(self):
        return self._parent

    def row(self):
        return self._row


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()

sys.exit(app.exec_())

У меня возникли проблемы с разработкой подкласса QAbstractItemModel и использованием собственного класса объектов в качестве элементов. Итак, вы видите половину проб и ошибок и половину ответов stackoverflow, все вместе взятые :)

...