QFileSystemModel rowCount() 在 setNameFilters() 之后出错

QFileSystemModel rowCount() wrong after setNameFilters()

我正在尝试添加标签以显示文件夹中的文件数。看起来很容易,但最近我注意到数字不正确(可能永远错了,我从来没有注意到)。我在 linux.

上的 pyqt5 和 pyqt6 中试过这个

出于演示目的,我创建了一个包含 20 个文件的文件夹,但过滤后仅显示 10 个文件。然后我通过计时器设置过滤器以仅显示 2 个文件,然后再次显示所有文件。您将从下面的输出中看到更新计数需要一秒钟。

import os.path
import sys
import tempfile
# from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets


def test(path):
    view = QtWidgets.QTreeView()
    # model = QtGui.QFileSystemModel(view)  # pyqt6
    model = QtWidgets.QFileSystemModel(view)  # pyqt5
    view.setModel(model)
    model.setRootPath(path)
    index = model.index(model.rootPath())
    view.setRootIndex(index)
    model.setNameFilterDisables(False)  # True shows filtered items disabled...False removes them
    model.setNameFilters(['*.foo'])

    def loaded():
        print(f'loaded {model.rowCount(index)=}')

    def update_filter2():
        model.setNameFilters(['1.*'])
        print(f'update_filter {model.rowCount(index)=} expected=2')  # TODO: how to get this to report 2

    def update_filter20():
        model.setNameFilters([])
        print(f'update_filter {model.rowCount(index)=} expected=20')  # TODO: how to get this to report 20

    model.directoryLoaded.connect(loaded)
    QtCore.QTimer.singleShot(2000, update_filter2)
    QtCore.QTimer.singleShot(3000, update_filter2)
    QtCore.QTimer.singleShot(4000, update_filter20)
    QtCore.QTimer.singleShot(5000, update_filter20)
    return view


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    # create test data and clean up afterwards
    with tempfile.TemporaryDirectory() as temp_dir_name:
        print(temp_dir_name)
        for i in range(10):
            with open(os.path.join(temp_dir_name, f'{i}.foo'), 'w') as file:
                print(i, file=file)
            with open(os.path.join(temp_dir_name, f'{i}.bar'), 'w') as file:
                print(i, file=file)
        w = test(temp_dir_name)
        w.show()
    sys.exit(app.exec())

如果我等一下,我会看到预期的行数。所以我想问题是,缺少计时器逻辑。你怎么知道什么时候调用 rowCount() 是安全的?我希望看到 directoryLoaded 再次发出信号。还有其他信号吗?

/tmp/tmplwv8nxi4
loaded model.rowCount(index)=10
loaded model.rowCount(index)=10
update_filter model.rowCount(index)=10 expected=2
update_filter model.rowCount(index)=2 expected=2
update_filter model.rowCount(index)=2 expected=20
update_filter model.rowCount(index)=20 expected=20

作为 documentation explains:

QFileSystemModel uses a separate thread to populate itself so it will not cause the main thread to hang as the file system is being queried. Calls to rowCount() will return 0 until the model populates a directory.

默认情况下,模型具有最大默认数量的项目获取,并且 QFileSystemModel 使用相同的线程方面在排序和过滤方面延迟 fetchMore()。这使得瞬时行计数不可靠。

由于您只需要显示可见项目的“静态”计数,解决方案是正确连接到实际改变模型布局的信号:

    def updateCount():
        print(model.rowCount(view.rootIndex()))
    model.layoutChanged.connect(updateCount)
    model.rowsRemoved.connect(updateCount)
    model.rowsInserted.connect(updateCount)

layoutChanged 对于基本的 sorting/filtering 通常足够了,但是由于您可能还对创建或删除(包括重命名)项目时的目录更改感兴趣,因此应连接其他两个信号也是。

出于性能原因,通常最好创建一个带有 singleShot 标志集和非常低间隔(大于 0,但小于 10-20 毫秒)的 QTimer 实例,然后将这些信号连接到计时器的 start 插槽。这样,如果在短时间内触发了多个信号,计时器最终会重新启动。
不要为此使用静态QTimer.singleShot

    timer = QTimer(interval=10, timeout=updateCount)
    model.layoutChanged.connect(timer.start)
    model.rowsRemoved.connect(timer.start)
    model.rowsInserted.connect(timer.start)