文件更改时 QFileSystemModel 不更新
QFileSystemModel not updating when files change
我遇到了 QFileSystemModel 不显示文件更改的问题。首次创建文件时,它会立即显示出来。但是当文件本身发生变化时,大小和时间戳不会更新。我多次尝试强制更新模型,但没有取得真正的成功。我取得的最好成绩是完全替换模型。虽然这会导致此错误:
QSortFilterProxyModel: index from wrong model passed to mapToSource
下面的测试代码创建一个空目录的 table 视图。单击左侧按钮会创建一个文件 (foo.txt)。连续单击将数据附加到文件。据我了解,QFileSystemModel 不需要刷新,但第二个按钮是我的尝试。
任何关于我做错的帮助将不胜感激!
# Testing with python3.6.3 and pip installed pyqt5 5.9.2 in virtualenv on Ubuntu
import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self._view = QtWidgets.QTableView()
layout.addWidget(self._view)
self._modify_button = QtWidgets.QPushButton('Create')
layout.addWidget(self._modify_button)
self._refresh_button = QtWidgets.QPushButton('Refresh')
layout.addWidget(self._refresh_button)
self._modify_button.clicked.connect(self._modify)
self._refresh_button.clicked.connect(self._refresh)
self._model, self._proxy = None, None
self.temp_dir = tempfile.TemporaryDirectory(dir=os.path.dirname(os.path.abspath(__file__)))
self.init_model(self.temp_dir.name)
def init_model(self, path):
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy = QtCore.QSortFilterProxyModel(self)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
# self._view.setModel(self._model)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
def _loaded(self):
path = self._model.rootPath()
source_index = self._model.index(path)
index = self._proxy.mapFromSource(source_index)
self._view.setRootIndex(index)
# self._view.setRootIndex(source_index)
def _modify(self):
"""Create or modify foo.txt..model should see and update"""
self._modify_button.setText('Modify')
file_name = os.path.join(self.temp_dir.name, 'foo.txt')
with open(file_name, 'a') as txt_file:
print('foo', file=txt_file)
# def _refresh(self):
# # This only seems to work once..and its a flawed approach since it requires permission to write
# temp = tempfile.NamedTemporaryFile(dir=self.temp_dir.name)
# def _refresh(self):
# self._model.beginResetModel()
# self._model.endResetModel()
# def _refresh(self):
# self._proxy.setFilterRegExp('foo')
# self._proxy.setFilterRegExp(None)
# self._proxy.invalidate()
# self._proxy.invalidateFilter()
# self._proxy.reset()
#
# root_index = self._model.index(self._model.rootPath())
# rows = self._model.rowCount(root_index)
# proxy_root_index = self._proxy.mapFromSource(root_index)
# topLeft = self._proxy.index(0, 0, proxy_root_index)
# bottomRight = self._proxy.index(rows - 1, self._model.columnCount(proxy_root_index) - 1, proxy_root_index)
# # self._proxy.dataChanged.emit(topLeft, bottomRight)
# self._model.dataChanged.emit(topLeft, bottomRight)
# def _refresh(self):
# # This only seems to work once
# self._model.setRootPath('')
# self._model.setRootPath(self.temp_dir.name)
def _refresh(self):
# This seems heavy handed..but seems to work
# ..though generates "QSortFilterProxyModel: index from wrong model passed to mapToSource" spam in console
self.init_model(self.temp_dir.name)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
更新:
从 Qt-5.9.4 开始,QT_FILESYSTEMMODEL_WATCH_FILES
环境变量可用于打开每个文件的监视(参见 QTBUG-46684). This needs to be set once to a non-empty value before the model starts caching information about files. But note that this will add a file-watcher to every file that is encountered, so this may make it an expensive solution on some systems.
下面留下原答案作为问题的解释。
此问题是由一个长期存在的 Qt 错误引起的:QTBUG-2276。不幸的是,目前看来它不太可能很快得到修复。如错误报告评论中所示,问题的核心似乎是:
It's an OS limitation. A change to a file does not mean the directory
is modified.
唯一真正的解决方法是将 QFileSystemWatcher
附加到 每个文件 ,这显然可以是 prohibitively expensive(在某些平台,无论如何)。
除了这个问题,QFileSystemModel class 目前没有提供 API 来强制刷新,而且,正如您所发现的,似乎没有是任何可靠的解决方法。大多数 "solutions" 在 SO 和其他地方提供,建议一些变体:
root = fsmodel.rootPath()
fsmodel.setRootPath('')
fsmodel.setRootPath(root)
但是如您所知,这似乎只起作用一次 - 可能是由于当前实施文件信息缓存的方式有些怪癖。
目前强制更新的唯一方法似乎是替换整个模型。可以通过像这样重构 init_model
方法来防止当前实现此方法产生的错误消息:
def init_model(self, path):
if self._proxy is None:
self._proxy = QtCore.QSortFilterProxyModel(self)
else:
# remove the current source model
self._proxy.setSourceModel(None)
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
这是一个非常令人不满意的情况,但目前似乎没有任何明显的解决方法。
从 Qt v5.9.4 开始,您可以设置环境变量 QT_FILESYSTEMMODEL_WATCH_FILES
,您可以在 changelog:
中阅读更多相关信息
[QTBUG-46684] It is now possible to enable per-file watching by
setting the environment variable QT_FILESYSTEMMODEL_WATCH_FILES,
allowing to track for example changes in file size.
两件事:
- 暂时需要设置before initializing the model,之后设置到其他文件夹就没有问题了。
- 请注意,此功能是以潜在的重负载为代价的。
我遇到了 QFileSystemModel 不显示文件更改的问题。首次创建文件时,它会立即显示出来。但是当文件本身发生变化时,大小和时间戳不会更新。我多次尝试强制更新模型,但没有取得真正的成功。我取得的最好成绩是完全替换模型。虽然这会导致此错误:
QSortFilterProxyModel: index from wrong model passed to mapToSource
下面的测试代码创建一个空目录的 table 视图。单击左侧按钮会创建一个文件 (foo.txt)。连续单击将数据附加到文件。据我了解,QFileSystemModel 不需要刷新,但第二个按钮是我的尝试。
任何关于我做错的帮助将不胜感激!
# Testing with python3.6.3 and pip installed pyqt5 5.9.2 in virtualenv on Ubuntu
import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self._view = QtWidgets.QTableView()
layout.addWidget(self._view)
self._modify_button = QtWidgets.QPushButton('Create')
layout.addWidget(self._modify_button)
self._refresh_button = QtWidgets.QPushButton('Refresh')
layout.addWidget(self._refresh_button)
self._modify_button.clicked.connect(self._modify)
self._refresh_button.clicked.connect(self._refresh)
self._model, self._proxy = None, None
self.temp_dir = tempfile.TemporaryDirectory(dir=os.path.dirname(os.path.abspath(__file__)))
self.init_model(self.temp_dir.name)
def init_model(self, path):
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy = QtCore.QSortFilterProxyModel(self)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
# self._view.setModel(self._model)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
def _loaded(self):
path = self._model.rootPath()
source_index = self._model.index(path)
index = self._proxy.mapFromSource(source_index)
self._view.setRootIndex(index)
# self._view.setRootIndex(source_index)
def _modify(self):
"""Create or modify foo.txt..model should see and update"""
self._modify_button.setText('Modify')
file_name = os.path.join(self.temp_dir.name, 'foo.txt')
with open(file_name, 'a') as txt_file:
print('foo', file=txt_file)
# def _refresh(self):
# # This only seems to work once..and its a flawed approach since it requires permission to write
# temp = tempfile.NamedTemporaryFile(dir=self.temp_dir.name)
# def _refresh(self):
# self._model.beginResetModel()
# self._model.endResetModel()
# def _refresh(self):
# self._proxy.setFilterRegExp('foo')
# self._proxy.setFilterRegExp(None)
# self._proxy.invalidate()
# self._proxy.invalidateFilter()
# self._proxy.reset()
#
# root_index = self._model.index(self._model.rootPath())
# rows = self._model.rowCount(root_index)
# proxy_root_index = self._proxy.mapFromSource(root_index)
# topLeft = self._proxy.index(0, 0, proxy_root_index)
# bottomRight = self._proxy.index(rows - 1, self._model.columnCount(proxy_root_index) - 1, proxy_root_index)
# # self._proxy.dataChanged.emit(topLeft, bottomRight)
# self._model.dataChanged.emit(topLeft, bottomRight)
# def _refresh(self):
# # This only seems to work once
# self._model.setRootPath('')
# self._model.setRootPath(self.temp_dir.name)
def _refresh(self):
# This seems heavy handed..but seems to work
# ..though generates "QSortFilterProxyModel: index from wrong model passed to mapToSource" spam in console
self.init_model(self.temp_dir.name)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
更新:
从 Qt-5.9.4 开始,QT_FILESYSTEMMODEL_WATCH_FILES
环境变量可用于打开每个文件的监视(参见 QTBUG-46684). This needs to be set once to a non-empty value before the model starts caching information about files. But note that this will add a file-watcher to every file that is encountered, so this may make it an expensive solution on some systems.
下面留下原答案作为问题的解释。
此问题是由一个长期存在的 Qt 错误引起的:QTBUG-2276。不幸的是,目前看来它不太可能很快得到修复。如错误报告评论中所示,问题的核心似乎是:
It's an OS limitation. A change to a file does not mean the directory is modified.
唯一真正的解决方法是将 QFileSystemWatcher
附加到 每个文件 ,这显然可以是 prohibitively expensive(在某些平台,无论如何)。
除了这个问题,QFileSystemModel class 目前没有提供 API 来强制刷新,而且,正如您所发现的,似乎没有是任何可靠的解决方法。大多数 "solutions" 在 SO 和其他地方提供,建议一些变体:
root = fsmodel.rootPath()
fsmodel.setRootPath('')
fsmodel.setRootPath(root)
但是如您所知,这似乎只起作用一次 - 可能是由于当前实施文件信息缓存的方式有些怪癖。
目前强制更新的唯一方法似乎是替换整个模型。可以通过像这样重构 init_model
方法来防止当前实现此方法产生的错误消息:
def init_model(self, path):
if self._proxy is None:
self._proxy = QtCore.QSortFilterProxyModel(self)
else:
# remove the current source model
self._proxy.setSourceModel(None)
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
这是一个非常令人不满意的情况,但目前似乎没有任何明显的解决方法。
从 Qt v5.9.4 开始,您可以设置环境变量 QT_FILESYSTEMMODEL_WATCH_FILES
,您可以在 changelog:
[QTBUG-46684] It is now possible to enable per-file watching by setting the environment variable QT_FILESYSTEMMODEL_WATCH_FILES, allowing to track for example changes in file size.
两件事:
- 暂时需要设置before initializing the model,之后设置到其他文件夹就没有问题了。
- 请注意,此功能是以潜在的重负载为代价的。