Search code examples
pythonpyqtpyqt4qtreeviewqsortfilterproxymodel

lazy Filtering QTreeview with QsortFIlterProxymodel


I want to extend a question I asked earlier with a filter. In my previous question I got help in lazy loading the treeview, parents first and only adding the parent's children when the user clicks on the node more or less as follows

from PyQt4 import QtGui


class Widget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        self.mytreeview = QtGui.QTreeView(self)
        self.setLayout(QtGui.QVBoxLayout())
        self.layout().addWidget(self.mytreeview)
        self.model = QtGui.QStandardItemModel(self.mytreeview)
        self.mytreeview.setModel(self.model)
        self.mytreeview.clicked.connect(self.update_model)
        self.initialise_model()

    def initialise_model(self):
        for text in ["parent1", "parent2", "parent3"]:
            item = QtGui.QStandardItem(text)
            self.model.appendRow(item)

    def update_model(self, index):
        parent = self.model.itemFromIndex(index)
        for text in ["children1", "children2", "children3"]:
            children = QtGui.QStandardItem("{}_{}".format(parent.text(), text))
            parent.appendRow(children)
        self.mytreeview.expand(index)

I now want to extend and make use of a filter for the treeview, so that a user can type in the name of a parent node and the treeview will filter itself down to the appropriate relevant nodes. I still want to keep the ability for the user to click on a parent node and even when filtered only then would the child nodes be added to the parent.

How can i adapt the following to do so? I have set the treeview up as follows

proxyModel = QSortFilterProxyModel(treeView)
proxyModel.setSourceModel(self.model)

# set model
treeView.setModel(proxyModel);   
treeView.setSortingEnabled(true)

def update_model(self, index):
    parent = self.model.itemFromIndex(index)
    ##not sure about this now in the light of the proxyModel
    for text in ["children1", "children2", "children3"]:
        children = QtGui.QStandardItem("{}_{}".format(parent.text(), text))
        parent.appendRow(children)
    self.mytreeview.expand(index)#not sure about this either as the index is of the proxyModel

I have two main questions firstly the proxyModel Index and the source model index, I am not sure how this works and also when expanding the clicked on node,


Solution

  • The classes that inherit from QAbstractProxyModel as QSortFilterProxyModel have the mapToSource() method that returns the index of the source model passing the index of the proxy model, also has another method called mapFromSource() that does the inverse so you must use that method to be able to add the items to the correct model.

    To filter the data I added a QLineEdit where the textChanged signal is connected, the text that provides the signal we used for the filter.

    class Widget(QtGui.QWidget):
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)
            self.filterLe = QtGui.QLineEdit(self)
            self.mytreeview = QtGui.QTreeView(self)
            self.setLayout(QtGui.QVBoxLayout())
            self.layout().addWidget(self.filterLe)
            self.layout().addWidget(self.mytreeview)
            self.model = QtGui.QStandardItemModel(self.mytreeview)
    
            self.proxyModel = QtGui.QSortFilterProxyModel(self.mytreeview)
            self.proxyModel.setSourceModel(self.model)
            self.mytreeview.setSortingEnabled(True)
    
            # set model
            self.mytreeview.setModel(self.proxyModel)
            self.mytreeview.clicked.connect(self.update_model)
            self.filterLe.textChanged.connect(self.onTextChanged)
            self.initialise_model()
    
        @QtCore.pyqtSlot(str)
        def onTextChanged(self, text):
            self.proxyModel.setFilterRegExp(text)
    
        def initialise_model(self):
            for text in ["parent1", "parent2", "parent3"]:
                item = QtGui.QStandardItem(text)
                self.model.appendRow(item)
    
        def update_model(self, index):
            ix = self.proxyModel.mapToSource(index)
            parent = self.model.itemFromIndex(ix)
            for text in ["children1", "children2", "children3"]:
                children = QtGui.QStandardItem("{}_{}".format(parent.text(), text))
                parent.appendRow(children)
            self.mytreeview.expand(index)