3

So i have a tree viewas shown below;

    #QTreeView widget
    #Shows files in set directory
    self.treeView = QtWidgets.QTreeView(self.centralWidget)
    self.treeView.setSortingEnabled(True)
    self.treeView.setObjectName("treeView")
    self.horizontalLayout_4.addWidget(self.treeView)
    self.file_model=QtWidgets.QFileSystemModel()
    self.file_model.setRootPath('C:\My Stuff\Movies')
    self.treeView.setModel(self.file_model)
    self.treeView.setRootIndex(self.file_model.index('C:\My Stuff\Movies'))
    self.treeView.setColumnWidth(0,275)
    self.file_model.setNameFilters(self.filterList)
    self.file_model.setNameFilterDisables(0)

As you can see i have a filter that hides items that dont pass the filter (e.g. *.mkv) however i have folders in my directory that contain a file that does not fit the filter requirements. The folder remains in my treeview even though it is empty, how do i remove these empty folders (Keep in mind i need to be able to show these folders when i apply a filter that allows for the file in the folder to be shown.

I am running PyQt5, Python 3.5, Windows 7.

L.John.B
  • 120
  • 1
  • 13

1 Answers1

1

We have the same question and this is the way I tried to solve the problem.

You need to subclass QSortFilterProxyModel and override the hasChildren and filterAcceptsRow.

Please take note that instead of calling the QFileSystemModel's setNameFilters you would need to call the subclassed QSortFilterProxyModel's nameFilters method.

This is the code I ended up basing on the implementation above:

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class DirProxy(QSortFilterProxyModel):
    nameFilters = ''
    def __init__(self):
        super().__init__()
        self.dirModel = QFileSystemModel()
        self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files) # <- added QDir.Files to view all files
        self.setSourceModel(self.dirModel)

    def setNameFilters(self, filters):
        if not isinstance(filters, (tuple, list)):
            filters = [filters]
        self.nameFilters = filters
        self.invalidateFilter()

    def hasChildren(self, parent):
        sourceParent = self.mapToSource(parent)
        if not self.dirModel.hasChildren(sourceParent):
            return False
        qdir = QDir(self.dirModel.filePath(sourceParent))
        return bool(qdir.entryInfoList(qdir.NoDotAndDotDot|qdir.AllEntries|qdir.AllDirs))
    
    def filterAcceptsRow(self, row, parent):
        source = self.dirModel.index(row, 0, parent)
        if source.isValid():
            if self.dirModel.isDir(source):
                qdir = QDir(self.dirModel.filePath(source))
                if self.nameFilters:
                    qdir.setNameFilters(self.nameFilters)
                return bool(qdir.entryInfoList(qdir.NoDotAndDotDot|qdir.AllEntries|qdir.AllDirs))

            elif self.nameFilters:  # <- index refers to a file
                qdir = QDir(self.dirModel.filePath(source))
                return qdir.match(self.nameFilters, self.dirModel.fileName(source)) # <- returns true if the file matches the nameFilters
        return True

class Test(QWidget):
    def __init__(self):
        super().__init__()
        
        self.dirProxy = DirProxy()
        self.dirProxy.dirModel.directoryLoaded.connect(lambda : self.treeView.expandAll())
        self.dirProxy.setNameFilters(['*.ai'])  # <- filtering all files and folders with "*.ai"
        self.dirProxy.dirModel.setRootPath(r"<Dir>")

        self.treeView = QTreeView()
        self.treeView.setModel(self.dirProxy)

        root_index = self.dirProxy.dirModel.index(r"<Dir>")
        proxy_index = self.dirProxy.mapFromSource(root_index)
        self.treeView.setRootIndex(proxy_index)

        self.treeView.show()

app = QApplication(sys.argv)
ex = Test()
sys.exit(app.exec_())

This is the testing I did and the result looks just fine to me:

Trial 1: enter image description here

Trial 2: enter image description here

Please read this question for more info.

Eliazar
  • 301
  • 3
  • 13