How to customize QSortFilterProxyModel to display specific folders?



  • i want to make a custom file manager that just shows specific folders.
    like this screenshot: only normal folders are displayed in the treeView, and only special folders ending with .asset are diaplayed in the listView.
    alt text

    my current approach is to take two separate QFileSystemModel and put them into two QSortFilterProxyModel, one to remove the .asset folder and one to display only the .asset folder.
    but it never works as expected.

    some interface code:

    root_path = "f:/root_folder"
    
    # two separate QFileSystemModel
    treeModel = QFileSystemModel()
    listModel = QFileSystemModel()
    
    # only folders
    treeModel.setFilter(QDir.NoDotAndDotDot | QDir.Dirs)
    listModel.setFilter(QDir.NoDotAndDotDot | QDir.Dirs)
    
    # i just want the model to search 'f:/root_folder':
    treeModel.setRootPath(root_path)
    listModel.setRootPath(root_path)
    
    # one custom QSortFilterProxyModel, it determines whether to display specific folders based on the parameters.
    treeProxy = myProxyModel(False)
    treeProxy.setSourceModel(treeModel)
    listProxy = myProxyModel(True)
    listProxy.setSourceModel(listModel)
    
    # view
    # 'ui' is a QWidget object, contains treeView and listView.
    ui.treeView.setModel(treeProxy)
    ui.treeView.setRootIndex(treeProxy.mapFromSource(treeModel.index(root_path)))
    ui.listView.setModel(listProxy)
    ui.listView.setRootIndex(listProxy.mapFromSource(listModel.index(root_path)))
    
    # signal and slot
    ui.treeView.clicked.connect(lambda index:
                                ui.listView.setRootIndex(
                                    listProxy.mapFromSource(
                                        listModel.setRootPath(
                                            treeModel.fileInfo(
                                                treeProxy.mapToSource(index)
                                            ).absoluteFilePath()
                                        ))
                                ))
    

    I first tested QSortFilterProxyModel, which returns True anyway, which means not filtering.

    # test: DO NOTHING.
    class myProxyModel(QSortFilterProxyModel):
        def __init__(self, isAsset=True, parent=None):
            super(myProxyModel, self).__init__(parent)
            self._isAsset = isAsset
        def filterAcceptsRow(self, source_row, source_parent):
            return True
    

    So far everything has worked, so I've added the ability to filter .asset.

    class myProxyModel(QSortFilterProxyModel):
        def __init__(self, isAsset=True, parent=None):
            super(myProxyModel, self).__init__(parent)
            self._isAsset = isAsset # True only show .asset, False not show .asset
        def filterAcceptsRow(self, source_row, source_parent):
            # get item name in the directory
            source_index = self.sourceModel().index(source_row, 0, source_parent)
            filename = source_index.data(Qt.DisplayRole)
            # check if the file name has .asset suffix
            if self._isAsset:
                if filename.endswith(".asset"): return True
                else: return False
            else:
                if filename.endswith(".asset"): return False
                else: return True
    

    the result listView is empty. why is that?

    another test:

    class myProxyModel(QSortFilterProxyModel):
        def __init__(self, isAsset=True, parent=None):
            super(myProxyModel, self).__init__(parent)
            self._isAsset = isAsset
        def filterAcceptsRow(self, source_row, source_parent):
            if not source_parent.isValid():
                print("error")
                return False
            else: return True
    

    because there's no filtering, the treeView and listView are all working fine.
    but there were 4 "error" in the console. what are they??
    what exactly does filterAcceptsRow() do??



  • Okay curious why not just "read" the values in put them into a in-code "TreeList" and then work with that TreeList instead? You would have much better control of what is filtered and what is not simply by creating a MainTreeList with everything and then your 2 display TreeLists : NonAssetTreeList and AssetTreeList. Note I did define the "TreeList" type as that is dependent upon how you want to implement that and I figured you could figure that one out on your own assuming you want to implement this way.

    Your situation is not uncommon when dealing with data in the View that is directly tied to your Model without implementing a Controller in-between -- aka MVC is very useful methodology and helps facilitate such things as this.



  • @denni-0 I thought filtering would be very simple in qt, but it's actually quite complicated. I wrote the program this way because both the qt manual and people who i asked said: to filtering out items using QSortFilterProxyModel.
    I am new, and no knowledge at C++, so I don't quite understand what you mean. do you want me to write a custom QFileSystemModel? if you're familiar with qt, can you explain What exactly does filterAcceptsRow() do?



  • @jackkkkk said in How to customize QSortFilterProxyModel to display specific folders?:

    QFileSystemModel

    Well, I don't get this line:

    filename = source_index.data(Qt.DisplayRole)
    

    You are getting the data for the "Qt.DisplayRole" which obviously is going to return nothing. The QFileSystemModel provides the following roles: https://doc.qt.io/qt-5/qfilesystemmodel.html#Roles-enum.
    So for your case:

    filename = source_index.data(Qt.FileNameRole)
    


  • @Jackkkkk in general it is best to keep this acronym in mind when coding K.I.S.S. (Keep It Simple and Smart) I prefer the positive spin on this acronym versus its negative cousin. Basically if you are thinking about doing something complicated perhaps a cleaner simpler solution might be better. Also keep in mind that a lot of folks out on the internet are what I would call wannabe programmers and actually are not any more clued in on programmer do's and dont's than you are. Still I am not the be all and end all when it comes to programming either, I just have a lot real world experience under my belt and tend to shoot straight from the hip when giving advice. Ultimately its your decision on what you use and do not use.

    That said no I am not talking about making a custom QFileSystemModel unless that happens to be the Simplest Smartest method to solving this issue for you. However I would think unless you really need that level of complexity for this fairly simple sounding issue I would not go that route. If you want tutoring ( for free although I do accept donations ;) ) then I have a Discord message server where I am tutoring various folks in programming and you invited if you want to join in. Discord btw is a free download kind of a cross between a forum and live chat where you can drop files instead of copy/paste the files contents makes it very easy to share code and such.

    Okay back to your issue -- you want a custom way to display a list of data -- the Simplest method to do this is to "read" (not when I quote something it means general concept as for instance read could mean a lot of things but ultimately it just means getting the data from some place and pulling into your application via whatever means is best for that data) that data in and put it into a List-like object then once you have that data in your own List-like object you manipulate however you want to before displaying it.

    MVC - or Model View Controller is a programming methodology which you can google if you are unfamiliar with it. Further when I say MVC I am meaning "MVC" basically anything that resembles the basic methodology as there a quite a few spin offs that are a similar but slightly different in implementation and personally I do not think I even do straight MVC I just do a conceptual version of it. But in short it can be viewed like this M<>C<>V (which I am not sure why they did not use that acronym) where the Model talks to the Controller that talks to the View and the View and the Model know nothing about each other and are often developed autonomously. So in your case the Model is your source for that list of folders and your View is obviously the Gui front-end the Controller/Handler in this case is your Code that takes that Source Data and manipulates it into a structure that the Gui needs it to be in for displaying. It also works in reverse, if you have data in the Gui that needs to go to your source it would pass through the Controller/Handler and be converted into a format that is friendly for the Model (typically a database in this case) to handle.

    I hope that helps you some -- and pm me if you want to join Discord be more than happy to lend you a hand.



  • @daljit97 AttributeError: type object 'PySide2.QtGui.Qt' has no attribute 'FileNameRole'
    i think you might mean QFileSystemModel.FileNameRole, it doesn't work.



  • @daljit97 after retesting, QFileSystemModel.FileNameRole and Qt.DisplayRole get similar results. they all get the name of the folders or disk. so that's not the problem.



  • @denni-0 I want it to be as simple as possible. but the more i learned, the harder it became for me to achieve this "simple" goal.

    there are two ways to filter the folder list, one in the model and the other in the view. neither is as easy as i thought. maybe i should change the way to my question.



  • @Jackkkkk what is the output of print(filename)?



  • @daljit97
    Qt.DisplayRole result:
    DISK NAME (F:)
    DISK NAME (C:)
    DISK NAME (D:)
    DISK NAME (F:)
    ...

    QFileSystemModel.FileNameRole result (no disk name):
    F:
    C:
    D:
    F:
    ...

    the result shows a list of all disks (which is the top level of the file system), indicating QSortFilterProxyModel.filterAcceptsRow() used an invalid index. look at the last test in my code.


Log in to reply