Reputation: 81
I have a Treeview
(inherits from QTreeView
) with model QFileSystemModel
. This Treeview
object is added to another widget with a QPushButton
. The button will open a directory through a QFileDialog
, and call the Treeview
's set_directory
method. The method will set the root path of the model to the selected directory and will modify the root index of the tree view. The top-most item in the treeview is then selected.
However, when selecting a directory for the first time, the top-most item is not selected. Commenting out line 11: self.model.directoryLoaded.connect(self.set_directory)
, solves the issue. But this means the set_directory
method is called twice:
# default directory opened
<class 'NoneType'>
# open new directory, set_directory called twice
<class 'NoneType'>
<class 'PyQt5.QtWidgets.QFileSystemModel'>
As seen on the command line output, the QModelIndex.model()
method returns a NoneType when selecting a directory for the first time. How do I set the treeview's current index to the top most item without calling the set_directory
method twice? And why is the QModelIndex
model a NoneType when the directory has not been visited?
Main script below:
from PyQt5.QtWidgets import QPushButton, QTreeView, QFileSystemModel, QWidget, QFileDialog, QApplication, QHBoxLayout
import sys
class Treeview(QTreeView):
def __init__(self, parent=None):
super(QTreeView, self).__init__(parent)
self.model = QFileSystemModel()
self.setModel(self.model)
self.set_directory(".")
# self.model.directoryLoaded.connect(self.set_directory)
def set_directory(self, path):
self.setRootIndex(self.model.setRootPath(path))
self.setCurrentIndex(self.model.index(0, 0, self.rootIndex()))
print(type(self.model.index(0, 0, self.rootIndex()).model()))
class MainWidget(QWidget):
def __init__(self, parent=None):
super(QWidget, self).__init__(parent)
self.tview = Treeview(self)
vlayout = QHBoxLayout(self)
vlayout.addWidget(self.tview)
open_button = QPushButton(self)
open_button.clicked.connect(self.open_dir)
vlayout.addWidget(open_button)
def open_dir(self):
filepath = QFileDialog.getExistingDirectory(self)
if filepath:
self.tview.set_directory(filepath)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MainWidget()
widget.show()
sys.exit(app.exec_())
Upvotes: 0
Views: 785
Reputation: 48260
There are three problems:
directoryLoaded
is called whenever the contents of a directory are loaded the first time, which happens asynchronously on a separate thread; if the contents of the "root" directory has never been loaded yet, it will have no children yet, so any attempt to access the children of the root index will return invalid indexes (that's why you get None
);set_directory
: if you open the folder "home" and then expand the directory "documents", the model will load all its contents and emit the directoryLoaded
for the "documents" folder, which will then call set_directory
in turn once again;Considering the above, you cannot rely on directoryLoaded
(and surely you should not connect it to set_directory
), but you can use the layoutChanged
signal, since it's always emitted whenever sorting has completed (and QFileSystemModel always sorts the model when the root changes); the only catch is that you must do it only when needed.
The solution is to create a function that tries to set the top item, if it's not valid then it will connect itself to the layoutChanged
signal; at that point, the signal will be emitted when the model has completed its job, and the top index has become available. Using a flag helps us to know if the signal has been connected, and then disconnect it, which is important in case you need to support sorting.
class Treeview(QTreeView):
layoutCheck = False
def __init__(self, parent=None):
super(QTreeView, self).__init__(parent)
self.model = QFileSystemModel()
self.setModel(self.model)
self.set_directory(QDir.currentPath())
self.setSortingEnabled(True)
def setTopIndex(self):
topIndex = self.model.index(0, 0, self.rootIndex())
print(topIndex.isValid(), topIndex.model())
if topIndex.isValid():
self.setCurrentIndex(topIndex)
if self.layoutCheck:
self.model.layoutChanged.disconnect(self.setTopIndex)
self.layoutCheck = False
else:
if not self.layoutCheck:
self.model.layoutChanged.connect(self.setTopIndex)
self.layoutCheck = True
def set_directory(self, path):
self.setRootIndex(self.model.setRootPath(path))
self.setTopIndex()
Please consider that the layoutCheck
flag is very important, for many reasons:
layoutChanged
is always emitted when the model is sorted; this not only happens when trying to sort using the headers, but also when files or directories are being added;A possibility is to use the Qt.UniqueConnection
connection type and a try
block for the disconnection, but, while this approach works and is consistent, it's a bit cumbersome: as long as the connection is always paired with the setting, using a basic boolean flag is much simpler and easier to read and understand.
Upvotes: 1