重命名文件夹后,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:新代码失败。
我尝试用新代码“重命名”了几次,但问题仍然存在。
我的操作是:
- 将“Fallout4”重命名为“质量效应”。 # 成功
- 然后“New Folder-01”到“vol.2”。 # 成功
- “新文件夹”到“第 3 卷”。 # 成功
- “AB”到“生化奇兵”。 # 成功
- “新建文件夹”到“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)
这是一个小文件管理器,您可以右键单击树项目来重命名它们。重命名一级文件夹后,点击下面的文件夹会得到错误的路径。您可以在 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:新代码失败。
我尝试用新代码“重命名”了几次,但问题仍然存在。 我的操作是:
- 将“Fallout4”重命名为“质量效应”。 # 成功
- 然后“New Folder-01”到“vol.2”。 # 成功
- “新文件夹”到“第 3 卷”。 # 成功
- “AB”到“生化奇兵”。 # 成功
- “新建文件夹”到“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)