Issue
I wish to create a widget for an app created with PyQt5. I want the user to be able to select any subset of the files within a filesystem hierarchy below a specified directory. I have extended the QFileSystemModel to allow checking the elements in the model roughly following this
I wish the user to be able to modify the checked state of the contents of a directory when the directory is checked - even before the subdirectory has been expanded.
So that this:
...does this 'under the hood' of the collapsed node in the tree view:
The problem I am facing is that the QTreeView - and seemingly the QFileSystemModel - are each, or together optimizing performance by only reporting model items which have been viewed already. Until I manually expand the subdirectories in the View, I cannot traverse the data in the Model.
To illustrate, I have added a print callback, and passed it to my (recursive) tree-traversal routine - to print how many children any index has. (See attached code.) Before I expand a subdir in the Tree View by double-clicking on it, it reports no children: Image 1 reports the following when I click on 'one':
tree clicked: /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one
|children|: 0
...but if I expand the view - as in Image 2, Then I see all the children, like this:
tree clicked: /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one
|children|: 7
child[0]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_f
|children|: 0
child[1]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_e
|children|: 0
child[2]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_d
|children|: 0
child[3]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_c
|children|: 0
child[4]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_b
|children|: 0
child[5]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a
|children|: 6
child[0]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a/tfd
|children|: 0
child[1]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a/sgl
|children|: 0
child[2]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a/kjh
|children|: 0
child[3]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a/jyk
|children|: 0
child[4]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a/dgj
|children|: 0
child[5]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/one_a/..
|children|: 0
child[6]: recursing
traverseDirectory():
model printIndex(): /Users/caleb/dev/ML/cloudburst-ml/data/test_dir/one/..
|children|: 0
How may I induce the loading of the subdirectories, or the expansion of the model's reflection of the actual filesystem? How do I traverse the root path of the QFileSystemModel before the user clicks on the view widget?
Here is my code:
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
class FileTreeSelectorModel(QtWidgets.QFileSystemModel):
def __init__(self, parent=None, rootpath='/'):
QtWidgets.QFileSystemModel.__init__(self, None)
self.root_path = rootpath
self.checks = {}
self.nodestack = []
self.parent_index = self.setRootPath(self.root_path)
self.root_index = self.index(self.root_path)
self.setFilter(QtCore.QDir.AllEntries | QtCore.QDir.Hidden | QtCore.QDir.NoDot)
self.directoryLoaded.connect(self._loaded)
def _loaded(self, path):
print('_loaded', self.root_path, self.rowCount(self.parent_index))
def data(self, index, role=QtCore.Qt.DisplayRole):
if role != QtCore.Qt.CheckStateRole:
return QtWidgets.QFileSystemModel.data(self, index, role)
else:
if index.column() == 0:
return self.checkState(index)
def flags(self, index):
return QtWidgets.QFileSystemModel.flags(self, index) | QtCore.Qt.ItemIsUserCheckable
def checkState(self, index):
if index in self.checks:
return self.checks[index]
else:
return QtCore.Qt.Checked
def setData(self, index, value, role):
if (role == QtCore.Qt.CheckStateRole and index.column() == 0):
self.checks[index] = value
print('setData(): {}'.format(value))
return True
return QtWidgets.QFileSystemModel.setData(self, index, value, role)
def traverseDirectory(self, parentindex, callback=None):
print('traverseDirectory():')
callback(parentindex)
if self.hasChildren(parentindex):
print('|children|: {}'.format(self.rowCount(parentindex)))
for childRow in range(self.rowCount(parentindex)):
childIndex = parentindex.child(childRow, 0)
print('child[{}]: recursing'.format(childRow))
self.traverseDirectory(childIndex, callback=callback)
else:
print('no children')
def printIndex(self, index):
print('model printIndex(): {}'.format(self.filePath(index)))
class FileTreeSelectorDialog(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.root_path = '/Users/caleb/dev/ML/cloudburst-ml/data/test_dir/'
# Widget
self.title = "Application Window"
self.left = 10
self.top = 10
self.width = 1080
self.height = 640
self.setWindowTitle(self.title) #TODO: Whilch title?
self.setGeometry(self.left, self.top, self.width, self.height)
# Model
self.model = FileTreeSelectorModel(rootpath=self.root_path)
# self.model = QtWidgets.QFileSystemModel()
# View
self.view = QtWidgets.QTreeView()
self.view.setObjectName('treeView_fileTreeSelector')
self.view.setWindowTitle("Dir View") #TODO: Which title?
self.view.setAnimated(False)
self.view.setIndentation(20)
self.view.setSortingEnabled(True)
self.view.setColumnWidth(0,150)
self.view.resize(1080, 640)
# Attach Model to View
self.view.setModel(self.model)
self.view.setRootIndex(self.model.parent_index)
# Misc
self.node_stack = []
# GUI
windowlayout = QtWidgets.QVBoxLayout()
windowlayout.addWidget(self.view)
self.setLayout(windowlayout)
QtCore.QMetaObject.connectSlotsByName(self)
self.show()
@QtCore.pyqtSlot(QtCore.QModelIndex)
def on_treeView_fileTreeSelector_clicked(self, index):
print('tree clicked: {}'.format(self.model.filePath(index)))
self.model.traverseDirectory(index, callback=self.model.printIndex)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = FileTreeSelectorDialog()
sys.exit(app.exec_())
I have looked at several links here
[1] - This did not change the behaviour
[2] - This posed an alternative solution which doesn't work for my purposes
[3] - This dealt with the same classes, but not the same issue
Solution
By design QFileSystemModel
does not load all the items since that task is very heavy, on the other hand hasChildren()
indicates if it has children as subdirectories or files but rowCount()
only returns the children that are visible due to design issues, that is discussed in this report.
So you should not use rowCount()
but do the task of iterating over the directory using QDirIterator
:
def traverseDirectory(self, parentindex, callback=None):
print('traverseDirectory():')
callback(parentindex)
if self.hasChildren(parentindex):
path = self.filePath(parentindex)
it = QtCore.QDirIterator(path, self.filter() | QtCore.QDir.NoDotAndDotDot)
while it.hasNext():
childIndex = self.index(it.next())
self.traverseDirectory(childIndex, callback=callback)
else:
print('no children')
I recommend you implement that task in another thread if your model has many levels because it can freeze the GUI.
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.