【问题标题】:Selecting the top-most item of a QTreeView选择 QTreeView 的最顶层项目
【发布时间】:2021-11-13 13:29:55
【问题描述】:

我有一个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() 方法在第一次选择目录时返回 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_())

【问题讨论】:

    标签: python python-3.x pyqt pyqt5


    【解决方案1】:

    存在三个问题:

    • directoryLoaded 在目录的内容第一次加载时调用,第一次,这在单独的线程上异步发生;如果尚未加载“根”目录的内容,则它还没有子目录,因此任何访问根索引子目录的尝试都将返回 无效索引(这就是您得到 @ 987654324@);
    • 每当目录第一次在树中扩展时,也会发送信号,如果将其连接到set_directory,则会出现问题:如果打开文件夹“home”并然后展开目录“documents”,模型将加载其所有内容并为“documents”文件夹发出directoryLoaded,然后再次调用set_directory
    • 文件系统模型还对项目进行排序(默认情况下按字母顺序排列,目录在前),这意味着需要更多时间才能获得您认为是第一项的内容:出于性能原因,操作系统根据文件系统返回目录的内容,这取决于它的实现:有时它按创建/修改时间排序,其他按块的物理位置/索引等;

    考虑到上述情况,你不能依赖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 在模型排序时总是发出;这不仅在尝试使用标题进行排序时发生,而且在添加文件或目录时也会发生;
    • 信号连接不是独占的,你甚至可以将同一个信号连接到同一个槽/函数不止一次,结果函数被调用的次数与它连接的次数一样多;如果你不是很小心,你可能会冒递归的风险;
    • 该标志也适用于空目录,避免了上述递归风险;如果打开了一个新的根目录并且它是空的,它显然会返回一个无效的顶部项目索引(因为没有),但无论如何都会断开信号:如果另一个目录(有内容)被打开但还没有加载后,信号将不再连接,如果内容已经加载,无论如何都会断开连接;

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-15
      • 2012-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多