#python #pyqt5 #pyside2
#python #pyqt5 #pyside2
Вопрос:
Это небольшой файловый менеджер, вы можете щелкнуть правой кнопкой мыши элементы дерева, чтобы переименовать их. после переименования папки первого уровня щелчок по папке под ней приведет к неправильному пути. вы можете увидеть это в верхней части окна или использовать этот код для проверки.
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» в «mass effects». # успех
- затем «Новая папка-01» на «vol.2». # успех
- «Новая папка» на «vol.3». # успех
- «AB» в «BioShock». # успех
- «Новая папка» в «bs2» в. # папка фактически переименована. но значок «bs2» стал «пустым» и больше не может переименовываться.
все операции разделяются на 2-5 секунд.
Комментарии:
1. Моя система — Windows10.
Ответ №1:
Есть две проблемы, обе связаны с тем, как QFileSystemModel реализован внутри, который сохраняет кэш загруженных индексов и функций узла (включая QFileInfo), добавляет QFileSystemWatcher для каждого посещенного каталога и, что наиболее важно, использует отдельный поток.
Проблема с путем связана с QFileInfo
(возвращается fileInfo(index)
), который по своей природе не обновляется автоматически: после создания экземпляра QFileInfo (и кэширования, в случае QFileSystemModel ) он не обновляет свои данные. Поскольку модель предположительно связывает индекс с QFileInfo, она возвращает тот же (не обновленный) объект. Это также, вероятно, связано с QTBUG-33720).
Чтобы получить фактический путь, я предлагаю вам использовать filePath()
вместо этого (вы можете использовать QFileInfo(model.filePath(index))
, который создаст новый и обновленный экземпляр, чтобы получить правильное имя).
Проблема переименования зависит от части потока: при установке свойства `Только для чтения` требуется некоторое время, чтобы «флаг вступил в силу», и выполнение этого сразу после 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)
Комментарии:
1. Я не ожидал, что простое действие переименования будет так сложно выполнить в Qt. Я попытался переименовать его с помощью os.rename() и QDir.rename(), но QFileSystemModel «заблокировал» эту папку. Хотя model.setData() не будет «блокировать», результат очень нестабилен, и я всегда чувствую, что с внутренним индексом что-то не так. (Хотя я не знаю реальной причины).
2. Я полагаю, вы ошибаетесь.
QDir.rename
илиos.rename
действительно не заботятся о QFileSystemModel (который не может «заблокировать» какой-либо файл, да и не должен), и если вы пытаетесь использовать их,fileInfo().absoluteFilePath()
тогда вам действительно следует прочитать то, что я написал, с гораздо большей осторожностью. Управление файловой системой не так просто, как кажется, только потому, что вы к этому привыкли: есть много слоев от вашего «я хочу переименовать» до фактического результата, Qt должен найти правильный баланс между всем этим (что также происходит асинхронно и учитывает кроссплатформенные проблемы). Вы не можете это игнорировать.3. Позже я проведу дополнительные тесты для os.rename. вы читали UPDATE1 выше? вот почему я сказал, что model.setData() очень нестабилен.
4. кстати, как нарисовать «серый фон» для ключевых слов?
5. 1) Из моих тестов, если вы делаете то, что описано выше, проблема не существует (и, опять же, если вы действительно не хотите разрешать редактирование вида, просто установите триггеры редактирования
NoEditTriggers
и установитеreadOnly
для свойства значение False с самого начала вместо того, чтобы менять его каждый раз); 2) опять же,на какой «серый фон» и «ключевые слова» вы ссылаетесь ?!?