Почему QTreeView.scrollTo () не работает изначально - PullRequest
0 голосов
/ 18 января 2019

Код ниже просто отображает древовидную структуру компьютерных дисков. Каждый раз, когда выбирается новый файл / папка, представление прокручивается, чтобы сделать этот новый выбор видимым.

Вопрос 1 : хотя это работает, первоначальный выбор после запуска приложения не запускает прокрутку. Почему?

Вопрос 2 : Если в инструкции:

self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)

инвертированы:

self.my_view.resizeColumnToContents(0)
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)

размер первого столбца не корректируется ни на начальном экране, а только после. Зачем?

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()

`


Редактировать : После использования хорошего решения, предоставленного @ekhumoro, мой пример кода выше работал. Однако этот другой фрагмент кода все еще не:

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()

1 Ответ

0 голосов
/ 18 января 2019

Первая проблема может возникнуть, если по умолчанию модель инициализирует свой текущий индекс в текущем каталоге. Это будет означать, что если вы установите снова на тот же индекс, сигнал изменения строки не будет излучаться (потому что ничего не изменилось). Это можно исправить, вызвав обработчик изменения строки напрямую:

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)
        ...

Что касается второй проблемы: при вызове scrollTo может потребоваться развернуть несколько каталогов, чтобы выбрать нужный индекс. Очевидно, это может изменить ширину первого столбца, поэтому вы всегда должны вызывать resizeColumnToContents после , чтобы получить правильную ширину.

UPDATE

Я думаю, что есть и другая проблема, вызванная проблемами со временем. QFileSystemModel должен работать в некоторой степени асинхронно, поскольку он должен запрашивать ресурсы у операционной системы, а затем ждать ответа. Кроме того, прежде чем он получит ответ, он не может заранее точно знать, сколько данных он собирается получить, поскольку файловая система, возможно, была обновлена ​​во время ожидания. Потенциально, ответ может включать данные из огромного каталога, содержащего тысячи файлов. Таким образом, чтобы обеспечить отзывчивость графического интерфейса, данные обрабатываются партиями, которые имеют достаточный размер для заполнения текущего представления. Если текущий индекс установлен до того, как окно было показано, и все его виджеты полностью размечены , нет никакой гарантии, что представление сможет корректно изменять размеры своих столбцов.

Это может быть исправлено путем явного повторного вызова обработчика изменения строки через однократный таймер с небольшой задержкой. Это должно позволить представлению правильно пересчитать ширину столбцов:

    ...    
    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)
...