如何使用 QFileSystemModel 和 QListView 将文件项目拖放到文件夹项目中?

How to drag and drop files items inside a folder item using QFileSystemModel and QListView?

我正在创建一个小部件来浏览和管理我的 qt 应用程序中的文件。为了构建它,我使用 QFileSystemModelQListView 以及 IconMode 视图模式。

它应该允许使用 QListView.

将文件(项目)移动到文件夹(其他项目)中

我的问题是如何实现它?

首先,我试图覆盖 ContentFileSystemModel 中的 supportedDragActionssupportedDropActions 函数以允许移动和复制操作。此外,我重写了 flags 函数以启用拖放功能。最后,我重写 canDropMimeDatadropMimeData 以检查它们是否是 运行,但看起来它们不是。

第一个问题是模型在光标显示禁止图标(如下图所示)后,不允许将项目文件放入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)

你设置错了movement property, since you're using Static:

The items cannot be moved by the user.

当使用 IconMode 时,属性 会自动设置为 Free,因此您只需 删除 以下行:

lv_file_manager.setMovement(QListView.Static)

其他重要的实现在模型的 canDropMimeData()(如果目标是可写目录,则必须 return True)和 dropMimeData()(实际上会移动文件)。

最后一步是覆盖 dragMoveEvent() 以防止在当前视图周围移动图标。

请注意,还进行了以下更改:

  • flags() 应该 而不是 return ItemIsDragEnabled 如果目标是一个文件;
  • setAcceptDrops(True)setDragEnabled(True) 不是必需的,因为当移动 not 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()