【问题标题】:Qt: Prevent drag-and-drop on child widgets and show forbidden cursorQt:防止在子小部件上拖放并显示禁止光标
【发布时间】:2020-05-05 14:58:57
【问题描述】:

我想在一些自定义小部件中启用拖放功能,以允许在布局中重新排序和重新排列小部件。基本功能正在运行,但我想防止将小部件放在自身或自身的孩子上。这在dropEvent 方法中很简单,但我找不到在拖动时也显示“禁止”光标的方法,以便用户知道不允许放置。

下面的例子展示了一个测试实现,其中小部件“一”到“五”可以拖放(不会发生重新排列,只会在终端上打印一条消息,您可能需要按Ctrl或其他方式)启动拖动)。问题行是dragEnterEvent 方法中的第一个ev.accept()。通过接受该事件,光标显示为“允许”状态(对我来说是一只手)。例如,尝试将“一”拖放到“三”上似乎是允许的,但不会发生任何事情。但是,忽略事件会导致事件传播到父级,因此在这种情况下,将“三”拖放到“三”上会导致“一”得到下降。设置WA_NoMousePropagation 属性似乎没有任何区别。

所以,我需要一种“接受”事件的方法,这样它就不会传播到父级,但仍然显示“禁止”光标,就好像没有人接受事件一样。有什么想法吗?

#!/usr/bin/env python3

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


class WidgetMimeData(QtCore.QMimeData):
    def __init__(self, *args, **kwargs):
      super().__init__(*args, **kwargs)
      self.itemObject = None

    def hasFormat(self, mime):
        if (self.itemObject and (mime == 'widgetitem')):
            return True
        return super().hasFormat(mime)

    def setItem(self, obj):
        self.itemObject = obj

    def item(self):
        return self.itemObject


class DraggableWidget(QGroupBox):
    def __init__(self):
        QWidget.__init__(self)
        layout = QVBoxLayout()
        self.setLayout(layout)
        self.setAcceptDrops(True)

    def addWidget(self, widget):
        return self.layout().addWidget(widget)

    def mouseMoveEvent(self, ev):
        pixmap = QPixmap(self.size())
        pixmap.fill(QtCore.Qt.transparent)
        painter = QPainter()
        painter.begin(pixmap)
        painter.setOpacity(0.8)
        painter.drawPixmap(0, 0, self.grab())
        painter.end()
        drag = QDrag(self)
        mimedata = WidgetMimeData()
        mimedata.setItem(self)
        drag.setMimeData(mimedata)
        drag.setPixmap(pixmap)
        drag.setHotSpot(ev.pos())
        drag.exec_(QtCore.Qt.MoveAction)

    def dragEnterEvent(self, ev):
        item = ev.mimeData().item()
        if item.isAncestorOf(self):
          #ev.ignore()
          ev.accept()
        else:
          ev.accept()

    def dropEvent(self, ev):
        item = ev.mimeData().item()
        if not item.isAncestorOf(self):
          print('dropped on', self.layout().itemAt(0).widget().text())
        ev.accept()


class HelloWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        w1 = DraggableWidget()
        w1.addWidget(QLabel('One'))
        w2 = DraggableWidget()
        w2.addWidget(QLabel('Two'))
        w3 = DraggableWidget()
        w3.addWidget(QLabel('Three'))
        w4 = DraggableWidget()
        w4.addWidget(QLabel('Four'))
        w5 = DraggableWidget()
        w5.addWidget(QLabel('Five'))

        w1.addWidget(w3)
        w1.addWidget(w4)
        w2.addWidget(w5)

        layout = QVBoxLayout()
        layout.addWidget(w1)
        layout.addWidget(w2)
        layout.addStretch(1)

        centralWidget = QWidget(self)          
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)   


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    mainWin = HelloWindow()
    mainWin.show()
    sys.exit( app.exec_() )

【问题讨论】:

    标签: python pyqt drag-and-drop pyqt5


    【解决方案1】:

    可能最简单的方法是始终接受dragEnterEvent 中的事件,而dragMoveEvent 中的事件源是self 的祖先时忽略它,即

    def dragEnterEvent(self, ev):
        ev.accept()
    
    def dragMoveEvent(self, ev):
        item = ev.source()
        if item.isAncestorOf(self):
            ev.ignore()
    

    通过忽略dragMoveEvent 中的事件,您也不需要在dropEvent 中进行检查,并且可以简单地进行

    def dropEvent(self, ev):
        print('Dropped of', self.layout().itemAt(0).widget().text())
        ev.accept()
    

    【讨论】:

    • 差不多了,但看起来dragMoveEvent只有在鼠标移动之后 dragEnterEvent时才会执行,我的意思是可以放在子小部件上如果刚进入孩子时鼠标停止(并且出现手形光标)。保持检查dropEvent 可防止掉落,但在输入子小部件时手形光标仍会短暂闪烁。不过,总比没有好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 2019-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多