Я создаю виджет для просмотра и управления файлами внутри моего приложения qt. Чтобы построить это, я использую QFileSystemModel
и QListView
с режимом просмотра IconMode
.
Он должен позволять перемещать файлы (элементы) в папки (другие элементы) с помощью QListView
.
Мой вопрос в том, как мне это реализовать?
Во-первых, я пытаюсь переопределить функции supportedDragActions
и supportedDropActions
из ContentFileSystemModel
, чтобы разрешить действия Move и Copy. Кроме того, я переопределяю функцию flags
, чтобы включить перетаскивание. Наконец, я переопределяю canDropMimeData
и dropMimeData
, чтобы проверить, работают ли они, но похоже, что это не так.
Первая проблема заключается в том, что модель не позволяет поместить файл предмета в область QListView
, если в курсоре отображается запрещенный значок (показан на изображении ниже).
Во-первых, я должен настроить модель так, чтобы предметы в папках могли сбрасываться. После этого я могу реализовать код для переноса перетаскиваемых элементов в папку.
Код готов воспроизвести проблему:
import sys
import os
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *
class ContentFileSystemModel(QFileSystemModel):
def __init__(self):
super(ContentFileSystemModel, self).__init__()
def supportedDragActions(self) -> Qt.DropActions:
print("supportedDragActions")
return Qt.MoveAction | super(ContentFileSystemModel, self).supportedDragActions() | Qt.CopyAction
def supportedDropActions(self) -> Qt.DropActions:
print("supportedDropActions")
return Qt.MoveAction | super(ContentFileSystemModel, self).supportedDropActions() | Qt.CopyAction
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
defaultFlags = super(ContentFileSystemModel, self).flags(index)
if not index.isValid():
return defaultFlags
fileInfo = self.fileInfo(index)
# The target
if fileInfo.isDir():
# Allowed drop
return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
# The source: should be directory( in that case)
elif fileInfo.isFile():
# Allowed drag
return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
return defaultFlags
def canDropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
print("canDropMimeData")
return True
def dropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
print("dropMimeData")
return True
def main(argv):
app = QApplication(sys.argv)
path = "C:\\Users\\Me\\Desktop"
file_system_model = ContentFileSystemModel()
file_system_model.setRootPath(path)
file_system_model.setReadOnly(False)
lv_file_manager = QListView()
lv_file_manager.setModel(file_system_model)
lv_file_manager.setViewMode(QListView.IconMode)
lv_file_manager.setRootIndex(file_system_model.index(path))
lv_file_manager.setResizeMode(QListView.Adjust)
lv_file_manager.setMovement(QListView.Static)
lv_file_manager.setSelectionMode(QAbstractItemView.ExtendedSelection)
lv_file_manager.setWrapping(True)
lv_file_manager.setAcceptDrops(True)
lv_file_manager.setDragEnabled(True)
lv_file_manager.setDropIndicatorShown(True)
lv_file_manager.setUniformItemSizes(True)
lv_file_manager.setDragDropMode(QAbstractItemView.InternalMove)
lv_file_manager.setFlow(QListView.LeftToRight)
lv_file_manager.show()
app.exec_()
if __name__ == "__main__":
main(sys.argv)
Вы устанавливаете неправильное свойство движения , так как вы используете Статическое:
Элементы не могут быть перемещены пользователем.
При использовании IconMode
этому свойству автоматически присваивается значение Free
, поэтому вы можете просто удалить следующую строку:
lv_file_manager.setMovement(QListView.Static)
Другие важные реализации находятся в модели canDropMimeData()
(которая должна возвращать True
, если целью является доступный для записи каталог) и dropMimeData()
(которая фактически перемещает файлы).
Последний шаг — переопределить dragMoveEvent()
, чтобы предотвратить перемещение значков вокруг текущего представления.
Обратите внимание, что также были внесены следующие изменения:
flags()
не должен возвращать ItemIsDragEnabled
, если целью является файл;setAcceptDrops(True)
и setDragEnabled(True)
не требуются, так как они автоматически устанавливаются, когда движение не Static
(как в случае использования IconMode
, как описано выше);setDragDropMode()
также не требуется;class ContentFileSystemModel(QFileSystemModel):
# ...
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
defaultFlags = super(ContentFileSystemModel, self).flags(index)
if not index.isValid():
return defaultFlags
fileInfo = self.fileInfo(index)
if fileInfo.isDir():
return Qt.ItemIsDropEnabled | Qt.ItemIsDragEnabled | defaultFlags
elif fileInfo.isFile():
# files should *not* be drop enabled
return Qt.ItemIsDragEnabled | defaultFlags
return defaultFlags
def canDropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
if row < 0 and column < 0:
target = self.fileInfo(parent)
else:
target = self.fileInfo(self.index(row, column, parent))
return target.isDir() and target.isWritable()
def dropMimeData(self, data: QMimeData, action: Qt.DropAction,
row: int, column: int, parent: QModelIndex) -> bool:
if row < 0 and column < 0:
targetDir = QDir(self.fileInfo(parent).absoluteFilePath())
else:
targetDir = QDir(self.fileInfo(self.index(row, column, parent)).absoluteFilePath())
dataList = []
# first check if the source is writable (so that we can move it)
# and that it doesn't already exist on the target path
for url in data.text().splitlines():
path = QUrl(url).toLocalFile()
fileObject = QFile(path)
if not fileObject.permissions() & QFile.WriteUser:
return False
targetPath = targetDir.absoluteFilePath(QFileInfo(path).fileName())
if targetDir.exists(targetPath):
return False
dataList.append((fileObject, targetPath))
# actually move the objects, you might want to add some feedback
# if movement failed (eg, no space left) and eventually undo the
# whole operation
for fileObject, targetPath in dataList:
if not fileObject.rename(targetPath):
return False
return True
class FileView(QListView):
def dragMoveEvent(self, event):
# accept drag movements only if the target supports drops
if self.model().flags(self.indexAt(event.pos())) & Qt.ItemIsDropEnabled:
super().dragMoveEvent(event)
else:
event.ignore()
def main(argv):
# ...
lv_file_manager = FileView()
Если вы хотите получить больше контроля над целью перетаскивания, вы должны переопределить dropEvent
в представлении. Некоторое время назад я самостоятельно написал небольшой файловый менеджер с Qt, поэтому, если бы вы могли лучше объяснить, как ваша программа должна вести себя в зависимости от ситуации, я могу дать вам несколько предложений и, возможно, отредактировать ответ.
Я хочу разрешить перетаскивание элементов (файлов и папок) только в другие элементы папки. Когда пользователь бросает его куда-то, кроме папки, ничего не должно происходить. Когда пользователь помещает его в папку, он должен быть перемещен туда.
Когда я говорю «перемещено», я имею в виду удалить файлы из текущей папки и поместить их в папку, в которую они были перенесены.
Спасибо, музыкант. Когда я удаляю эту строку, элементы можно свободно перемещать в другие места в
QListView
. Однако, раз я хотел бы разрешить перемещение элементов только в папки, это нежелательная функция. В любом случае, это прогресс, когда модель позволяет сбрасывать предметы и вызывать функциюcanDropMimeData
. Попробую ограничить этот дроп только папками. Я буду сообщать здесь о любом прогрессе.