重命名文件夹后,QFileSystemModel 和 QTreeView return 路径错误

After rename a folder, QFileSystemModel and QTreeView return wrong path

这是一个小文件管理器,您可以右键单击树项目来重命名它们。重命名一级文件夹后,点击下面的文件夹会得到错误的路径。您可以在 window 的顶部看到它,或者使用此代码进行检查。

index = widget.treeView.currentIndex()
model = index.model()
path = model.fileInfo(index).absoluteFilePath()

同时,如果多次重命名那个一级文件夹中的二级文件夹,会出现奇怪的问题。例如,重命名失败或该文件夹消失。我觉得QFileSystemModel改名后没有刷新!

完整代码如下

import sys
from PySide2.QtWidgets import QApplication, QWidget, QFileSystemModel, QTreeView, QPushButton, QLabel, QVBoxLayout, QHBoxLayout, QGridLayout, QMenu, QInputDialog, QLineEdit
from PySide2.QtCore import Qt, QEvent

class MyWidget(QWidget):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)

        ROOT = "F:/New Folder"

        self.treeModel = QFileSystemModel()
        self.treeModel.setRootPath(ROOT)

        self.treeView = QTreeView()
        self.treeView.setModel(self.treeModel)
        self.treeView.setRootIndex(self.treeModel.index(ROOT))
        self.treeView.setColumnHidden(1,True)
        self.treeView.setColumnHidden(2,True) 
        self.treeView.setColumnHidden(3,True)
        self.treeView.installEventFilter(self) # QEvent.ContextMenu

        # for test -----------------------------------
        self.treeView.clicked.connect(lambda index: self.show_path(index))
        treeSelection = self.treeView.selectionModel()
        treeSelection.currentChanged.connect(lambda index, pre_index: self.tree_selection_slot(index, pre_index))

        labelA = QLabel("model path:")
        self.labelA2 = QLabel()
        labelB = QLabel("treeView clicked:")
        self.labelB2 = QLabel()
        labelC = QLabel("tree selection changed:")
        self.labelC2 = QLabel()

        grid = QGridLayout()
        grid.addWidget(labelA, 0, 0)
        grid.addWidget(self.labelA2, 0, 1)
        grid.addWidget(labelB, 1, 0)
        grid.addWidget(self.labelB2, 1, 1)
        grid.addWidget(labelC, 2, 0)
        grid.addWidget(self.labelC2, 2, 1)
        # for test -------------------------------END.

        layout = QVBoxLayout()
        layout.addLayout(grid)
        layout.addWidget(self.treeView)

        self.setLayout(layout)

    def eventFilter(self, source, event):
        """ mouse right click rename menu """
        if event.type() == QEvent.ContextMenu and source is self.treeView:

            gp = event.globalPos()
            lp = self.treeView.viewport().mapFromGlobal(gp)
            index = self.treeView.indexAt(lp)

            if not index.isValid():
                return

            menu = QMenu()
            rename_act = menu.addAction("rename folder")
            rename_act.triggered.connect(lambda: self.change_name(index))
            menu.exec_(gp)

            return True

        return super(MyWidget, self).eventFilter(source, event)

    def change_name(self, index):
        """ rename """
        if not index.isValid():
            return

        model = index.model()
        old_name = model.fileName(index)
        path = model.fileInfo(index).absoluteFilePath()

        # ask new name
        name, ok = QInputDialog.getText(self, "New Name", "Enter a name", QLineEdit.Normal, old_name)
        if not ok or not name:
            return
        
        # rename
        model = index.model()
        wasReadOnly = model.isReadOnly()
        model.setReadOnly(False)
        model.setData(index, name)
        model.setReadOnly(wasReadOnly)

    def show_path(self, index):
        """ for test """
        if not index.isValid():
            return
        model = index.model()
        path = model.fileInfo(index).absoluteFilePath()
        self.labelB2.setText(path)

    def tree_selection_slot(self, index, pre_index):
        """ for test """
        if not index.isValid():
            return
        model = index.model()
        path = model.fileInfo(index).absoluteFilePath()
        self.labelC2.setText(path)
    
    
if not QApplication.instance():
    app = QApplication(sys.argv)
else:
    app = QApplication.instance()

widget = MyWidget()
widget.show()

sys.exit(app.exec_())


更新 1:新代码失败。

我尝试用新代码“重命名”了几次,但问题仍然存在。 我的操作是:

  1. 将“Fallout4”重命名为“质量效应”。 # 成功
  2. 然后“New Folder-01”到“vol.2”。 # 成功
  3. “新文件夹”到“第 3 卷”。 # 成功
  4. “AB”到“生化奇兵”。 # 成功
  5. “新建文件夹”到“bs2”即可。 # 该文件夹实际上已重命名。但是“bs2”的图标变成了“空白”,不能再重命名了

所有操作间隔 2 到 5 秒。

有两个问题,都与QFileSystemModel的内部实现方式有关,它保留了加载索引和节点函数(包括QFileInfo)的缓存,为每个访问的目录添加了一个QFileSystemWatcher,并且,大多数重要的是,使用一个单独的线程。

路径问题与QFileInfo(由fileInfo(index)返回)有关,其本质上不会自动更新自身:一旦一个QFileInfo实例创建(和缓存,在 QFileSystemModel 的情况下),它不更新它的数据。由于模型可能将索引与 QFileInfo 相关联,因此它 returns 相同(未更新)的对象。这也可能与QTBUG-33720)有关。

要获得实际路径,我建议您改用 filePath()(您可以使用 QFileInfo(model.filePath(index)),这将创建一个新的和更新的实例以获得正确的名称)。


重命名问题取决于线程部分:当设置 `readOnly` 属性 时,"flag to take effect" 需要一些时间,并且在 QInputDialog 没有给 Qt 留下足够的时间之后立即进行重命名。 注意:我不太确定实际发生了什么以及为什么会发生,但您可以自己检查一下:如果您在*显示对话框之前*使用 `setReadOnly`,它会起作用。

如果在 Qt 和 C++ 方面有更多经验的人可能对此有所了解,我将很高兴了解并更新此答案。
因此,作为一种“解决方法”,您可以将实施更改为以下内容:

    def change_name(self, index):
        if not index.isValid():
            return

        model = index.model()
        old_name = model.fileName(index)
        wasReadOnly = model.isReadOnly()
        model.setReadOnly(False)

        name, ok = QInputDialog.getText(self, "New Name", "Enter a name", QLineEdit.Normal, old_name)
        if ok and name and name != old_name:
            model.setData(index, name)
        model.setReadOnly(wasReadOnly)