拖放在“QFileModelSystem”中不起作用

Drag and Drop not working in `QFileModelSystem`

我正在尝试在 QFileSystemModel 中进行拖放操作,但由于我之前没有进行拖放操作的经验,所以我首先在 QTreeView 中进行了尝试。 (我附上了行为的视频)

现在我对我想要的行为没意见了,然后我只是将模型更改为 QFileSystemModel 但遗憾的是它不起作用。所以我尝试阅读 QFileSystemModel, QTreeView, and Drag and Drop from Qt,最后得到以下代码:

我得到的代码:

import os
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class MQTreeView(QTreeView):
    def __init__(self, model):
        super().__init__()
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        # self.setDragDropMode(QAbstractItemView.InternalMove)
        self.setModel(model)
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setRootIndex(model.index(os.path.dirname(os.path.abspath("__file__"))))
        self.setDefaultDropAction(Qt.MoveAction)
        self.viewport().setAcceptDrops(True)

    def dragEnterEvent(self, event):
        m = event.mimeData()
        if m.hasUrls():
            event.accept()
            return
        event.ignore()
        # return super().dragEnterEvent(event)

    def dropEvent(self, event):
        print("[drop event] - dropped")
        if event.source():
            QTreeView.dropEvent(self, event)
        else:
            ix = self.indexAt(event.pos())
            model = self.model()

            if ix.isValid():
                if not model.isDir(ix):
                    ix = ix.parent()      # In case of folder/Dir
                pathDir = model.filePath(ix)
            else:
                # for empty drag and drop
                pathDir = model.rootPath()

            m = event.mimeData()
            if m.hasUrls():
                urlLocals = [url for url in m.urls() if url.isLocalFile()]
                accepted = False
                for urlLocal in urlLocals:
                    path = urlLocal.toLocalFile()
                    info = QFileInfo(path)
                    n_path = QDir(pathDir).filePath(info.fileName())
                    o_path = info.absoluteFilePath()
                    if n_path == o_path:
                        continue
                    if info.isDir():
                        QDir().rename(o_path, n_path)
                    else:
                        qfile = QFile(o_path)
                        if QFile(n_path).exists():
                            n_path += "(copy)" 
                        qfile.rename(n_path)
                        print(f"added -> {info.fileName()}")

                    accepted = True
                if accepted:
                    event.acceptProposedAction()

        # return super().dropEvent(event)

class AppDemo(QWidget):
    def __init__(self):
        super().__init__()
        # -- right -- #
        self.model1 = QFileSystemModel()
        self.model1.setRootPath(os.path.dirname(os.path.abspath("__file__")))

        self.view1 = MQTreeView(self.model1)

        # -- left -- #
        self.model2 = QFileSystemModel()
        self.model2.setRootPath(os.path.dirname(os.path.abspath("__file__")))

        self.view2 = MQTreeView(self.model2)

        # -- layout -- #
        layout = QHBoxLayout(self)
        layout.addWidget(self.view1)
        layout.addWidget(self.view2)

app = QApplication(sys.argv)
main = AppDemo()
main.show()
app.exec_()

上面的代码仍然没有执行我想要的行为,但我很确定其他地方是错误的,它不是覆盖函数(dragEnterEventdropEvent)。我最好的猜测是我没有正确设置 QTreeView 接受掉落的正确方式,尽管我不太确定。

我的问题:我的实现有什么问题?是我接受掉落的方式还是其他原因?

发现问题了!我没有覆盖 dragMoveEvent 方法。我需要覆盖 dragMoveEvent 以确保不会禁止拖动。

我需要接受 dragEnterEvent 中的所有拖动事件:

def dragEnterEvent(self, event):
    event.accept()

然后我需要过滤dragMoveEvent中的事件:

def dragMoveEvent(self, event):
    m = event.mimeData()
    if m.hasUrls():
        event.accept()
        print("[dropEnterEvent] - event accepted")
        return
    event.ignore()

我在下面附上了工作行为的视频和代码。

最终实现:

import os
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class MQTreeView(QTreeView):
    def __init__(self, model, path):
        super().__init__()

        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setModel(model)
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setRootIndex(model.index(path))
        self.setDefaultDropAction(Qt.MoveAction)
        self.viewport().setAcceptDrops(True)

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        m = event.mimeData()
        if m.hasUrls():
            event.accept()
            print("[dropEnterEvent] - event accepted")
            return
        event.ignore()

    def dropEvent(self, event):
        print("[drop event] - dropped")
        if event.source():
            ix = self.indexAt(event.pos())
            model = self.model()

            if ix.isValid():
                if not model.isDir(ix):
                    ix = ix.parent()
                pathDir = model.filePath(ix)
            else:
                # for empty drag and drop
                pathDir = model.rootPath()

            m = event.mimeData()
            if m.hasUrls():
                urlLocals = [url for url in m.urls() if url.isLocalFile()]
                accepted = False
                for urlLocal in urlLocals:
                    path = urlLocal.toLocalFile()
                    info = QFileInfo(path)
                    destination = QDir(pathDir).filePath(info.fileName())
                    source = info.absoluteFilePath()
                    if destination == source:
                        continue  # means they are in the same folder
                    if info.isDir():
                        QDir().rename(source, destination)
                    else:
                        qfile = QFile(source)
                        if QFile(destination).exists():
                            n_info = QFileInfo(destination)
                            
                            destination = n_info.canonicalPath() + QDir.separator() + n_info.completeBaseName() + " (copy)"
                            if n_info.completeSuffix():   # for moving files without suffix
                                destination += "." + n_info.completeSuffix()

                        qfile.rename(destination)
                        print(f"added -> {info.fileName()}")  # for debugging

                    accepted = True
                if accepted:
                    event.acceptProposedAction()

class AppDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setAcceptDrops(True)

        cwd = "test/"
        nw = "test copy/"

        # -- right -- #
        self.model1 = QFileSystemModel()
        self.model1.setRootPath(os.path.dirname(cwd))

        self.view1 = MQTreeView(self.model1, cwd)

        # -- left -- #
        self.model2 = QFileSystemModel()
        self.model2.setRootPath(os.path.dirname(nw))

        self.view2 = MQTreeView(self.model2, nw)

        # -- layout -- #
        layout = QHBoxLayout(self)
        layout.addWidget(self.view1)
        layout.addWidget(self.view2)

app = QApplication(sys.argv)
main = AppDemo()
main.show()
app.exec_()