选择 QTreeView 的最顶层项目

Selecting the top-most item of a QTreeView

我有一个 Treeview(继承自 QTreeView),型号为 QFileSystemModel。这个 Treeview 对象被添加到另一个带有 QPushButton 的小部件。该按钮将通过 QFileDialog 打开一个目录,并调用 Treeviewset_directory 方法。该方法会将模型的根路径设置为所选目录,并将修改树视图的根索引。然后选择树视图中最顶部的项目。

但是,第一次选择目录时,没有选择最上面的项目。注释掉第 11 行:self.model.directoryLoaded.connect(self.set_directory),问题就解决了。但这意味着 set_directory 方法被调用了两次:

# default directory opened
<class 'NoneType'>
# open new directory, set_directory called twice
<class 'NoneType'>
<class 'PyQt5.QtWidgets.QFileSystemModel'>

如命令行输出所示,首次选择目录时,QModelIndex.model() 方法 returns 为 NoneType。如何在不调用 set_directory 方法两次的情况下将树视图的当前索引设置为最上面的项目?为什么 QModelIndex 模型在目录未被访问时为 NoneType?

主要脚本如下:

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

存在三个问题:

  • directoryLoaded 每当目录的内容第一次被加载时被调用 ,这是在一个单独的线程上异步发生的;如果“root”目录的内容还没有被加载,它还没有 children,所以任何访问根索引的 children 的尝试都会 return 无效索引(这就是为什么你得到None);
  • 每当目录在树中第一次 扩展 时也会发送信号,如果您将它连接到 set_directory 会产生问题:如果您打开“home”文件夹,然后展开“documents”目录,模型将加载其所有内容并为“documents”文件夹发出 directoryLoaded,然后再次依次调用 set_directory ;
  • 文件系统模型还对项目进行排序(默认情况下按字母顺序排列,目录在前),这意味着它需要更多时间才能获得您认为排在第一位的内容item:出于性能原因,OS returns 目录的内容取决于文件系统,这取决于它的实现:有时按 creation/modification 时间排序,其他按物理时间排序position/index 块等;

考虑到以上情况,你不能依赖 directoryLoaded(当然你不应该将它连接到 set_directory),但你可以使用 layoutChanged 信号,因为它总是被发射每当排序完成时(并且 QFileSystemModel 总是在根更改时对模型进行排序);唯一的问题是你必须只在需要时才这样做。

解决方案是创建一个尝试设置顶部项目的函数,如果它无效则它将自身连接到layoutChanged 信号;那时,当模型完成其工作并且顶部索引可用时,将发出信号。使用标志可以帮助我们知道信号是否已连接,然后断开连接,这在您需要支持排序的情况下很重要。

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

请考虑 layoutCheck 标志 非常 重要,原因有很多:

  • 如前所述,layoutChanged 总是 在模型排序时发出;这不仅在尝试使用 headers 进行排序时发生,而且在添加文件或目录时也会发生;
  • 信号连接不是排他性的,您甚至可以将同一个信号多次连接到同一个 slot/function,结果是该函数将被调用多次;如果你不小心,你可能会面临递归的风险;
  • 该标志也适用于空目录,避免了上述递归风险;如果打开一个新的根目录并且它是空的,它显然会 return 一个无效的顶级项目索引(因为有 none),但无论如何信号都会断开:如果另一个目录( with contents) 已打开但尚未加载,则信号不会再次连接,如果内容已加载,则无论如何都会断开连接;

一种可能性是使用 Qt.UniqueConnection 连接类型和 try 块来断开连接,但是,虽然这种方法有效且一致,但有点麻烦:只要连接总是与设置配对,使用基本的布尔标志更简单,更容易阅读和理解。