Reputation: 7524
The code below just displays a tree view of computer drives. Each time a new file/folder is selected, the view scrolls to make this new selection visible.
Question 1: While this works, the initial selection after the application is launched doesn't trigger the scroll. Why?
Question 2: If the instructions:
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
are inverted:
self.my_view.resizeColumnToContents(0)
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
the first column size is not adjusted either on the initial display, only after. Why?
import sys
from PyQt5.QtCore import Qt, QModelIndex, QDir
from PyQt5.QtWidgets import QApplication, QTreeView, QMainWindow, QFileSystemModel, QAbstractItemView
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
# Instance variables
self.my_view = QTreeView()
self.my_model = QFileSystemModel()
# Init FS model to show all computer drives
model_root_path = str(self.my_model.myComputer())
self.my_model.setRootPath(model_root_path)
# Init tree view
self.my_view.setModel(self.my_model)
self.my_view.setRootIndex(self.my_model.index(model_root_path))
self.my_view.setSelectionMode(QAbstractItemView.SingleSelection)
self.my_view.setSelectionBehavior(QAbstractItemView.SelectRows)
# Connect selection change events to custom slot
select_model = self.my_view.selectionModel()
select_model.currentRowChanged.connect(self.current_row_changed)
# Main window
self.setCentralWidget(self.my_view)
self.setGeometry(200, 200, 800, 600)
# Select initial row on view
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
def current_row_changed(self):
"""Current row of the model has changed"""
# Scroll view to new row
index = self.my_view.selectionModel().currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
# Show path of current row in window title
absolute_path = self.my_model.filePath(index)
self.setWindowTitle(absolute_path)
def main():
a = QApplication(sys.argv)
mw = MyWindow()
mw.show()
sys.exit(a.exec_())
if __name__ == '__main__':
main()
`
Edit: After using the good solution provided by @ekhumoro, my sample code above worked. However this other piece of code still didn't:
import os
import sys
from PyQt5.QtCore import pyqtSignal, QTimer, QDir, Qt
from PyQt5.QtWidgets import QMainWindow, QGridLayout, QWidget, QTreeView, QAbstractItemView, QFileSystemModel, \
QApplication
class AppWindow(QMainWindow):
default_folder_path = "."
def __init__(self):
super().__init__()
self.folder_view = FolderTreeView()
self.folder_view.folder_has_changed.connect(self.folder_changed)
self.build_ui()
self.show()
# Select initial folder
self.select_initial_folder()
def build_ui(self):
main_widget = QWidget()
layout = QGridLayout(main_widget)
layout.addWidget(self.folder_view)
self.setCentralWidget(main_widget)
self.setGeometry(200, 100, 800, 600)
def select_initial_folder(self):
folder_index = self.folder_view.get_index(AppWindow.default_folder_path)
if folder_index.isValid():
self.folder_view.select_folder(folder_index)
def folder_changed(self, folder_path):
if not os.path.isdir(folder_path):
print("Non existing folder:", folder_path)
return
class FolderTreeView(QTreeView):
folder_has_changed = pyqtSignal(str)
def __init__(self):
super().__init__()
self.folder_tree_model = FolderTreeModel()
self.setModel(self.folder_tree_model)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
def select_folder(self, folder_index):
self.setCurrentIndex(folder_index)
def currentChanged(self, current, previous):
super(FolderTreeView, self).currentChanged(current, previous)
# Scroll the view to current item and resize folder name column
QTimer.singleShot(50, lambda: self.delayed_scroll(current))
# Emit signal for other uses
self.folder_has_changed.emit(self.folder_tree_model.filePath(current))
def delayed_scroll(self, index):
self.scrollTo(index, QAbstractItemView.EnsureVisible)
self.resizeColumnToContents(0)
def get_index(self, folder_path):
return self.folder_tree_model.index(folder_path)
class FolderTreeModel(QFileSystemModel):
def __init__(self):
super().__init__()
self.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
self.setRootPath("")
def main():
app = QApplication(sys.argv)
window = AppWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Upvotes: 3
Views: 1549
Reputation: 120798
The first problem may be caused if, by default, the model initialises its current index to the current directory. This would mean that if you set it again to the same index, the row-change signal will not be emitted (because nothing changed). This can be fixed by calling the row-change handler directly:
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
...
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
self.current_row_changed()
def current_row_changed(self):
index = self.my_view.currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
...
As to the second problem: when you call scrollTo
, it may have to expand several directories in order to select the required index. This could obviously change the width of the first column, so you should always call resizeColumnToContents
afterwards in order to get the correct width.
UPDATE:
I think there is also another problem caused by timing issues. The QFileSystemModel
must work asynchronously to some extent, because it has to request resources from the operating system and then wait for the response. Also, before it gets the response, it cannot know in advance exactly how much data it is going to receive, because the file-system may have been updated while it was waiting. Potentially, the response could include data from a huge directory containing thousands of files. So in order to keep the GUI responsive, the data is processed in batches which are of a sufficient size to fill the current view. If the current index is set before the window has been shown and all its widgets fully laid out, there is no guarantee that the view will be able to resize its columns correctly.
This can be fixed by explicitly re-calling the row-change handler via a one-shot timer with a small delay. This should allow the view to recalculate its column widths correctly:
...
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
QTimer.singleShot(50, self.current_row_changed)
def current_row_changed(self):
index = self.my_view.currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
Upvotes: 3