26. [Python GUI] PyQt5中拖放详解之拖放动作
创始人
2024-02-27 13:51:04
0

PyQt5的拖放动作

1. 什么是拖放动作

拖放动作是指用户希望怎样处理拖放的数据,比如移动、复制、还是创建由目标到源的链
接等。 拖放动作由 Qt::DropAction 枚举描述:
20221128115133

2. 可能的拖放动作,实际的拖放动作,建议的拖放动作

  • 可能的拖放动作:
    是指用户在拖放时可能会执行的拖放动作,用户在拖放时通常可由用户选择,比如可以选择移动、复制或链接等动作中的一种,这些动作都是可能的拖放动作。可能的拖放动作在 QDrag::exe()函数的第 1 个参数中指定,同时该函数的返回值是最终的实际拖放动作。

  • 实际的拖放动作:
    是指拖放到最终放置时实际执行的动作,实际拖放动作在 drogEvent()函数中使用 QDropEvent::setDropAction()函数(还需在之后调用 accept()函数)设置,该函数会影响 QDrag::exec()函数的返回值。

  • 建议的拖放动作:
    是指当用户执行拖动而不使用修饰键时的默认动作, 建议拖放动作可在 QDrag::exec()函数的第 2 个参数中指定,该参数的设置会影响拖动时鼠标光标右下角的外观,另外 QDropEvent::acceptProposedAction()函数表示设置执行操作为建议操作并接受该事件。

以上三种拖放动作常常相互关联,比如用户在拖动时通常可以执行移动、复制或链接等动作(可能的拖放动作)中的一种,然而应用程序在拖放到最终放置时并不知道用户到底需要执行哪种操作,若用户未指定需要执行的可能的拖放动作中的哪一种动作时,应用程序可以使用设置的建议动作,作为需要执行的动作。

3. 各拖放动作之间的关系

  1. QDrag::exec()函数的规则

    • 若 QDrag::exec()未指定建议拖放动作,则依顺序移动、复制、链接进行选择
    • 若 QDrag::exec()函数在第 2 个参数上指定了建议拖放动作,但该动作不在可能的拖放动作组合之中,则使用默认的复制拖放动作。
  2. QDropEvent::setDropAction()函数的规则

    • 使用 setDropAction()函数设置拖放动作之后应使用 accept()函数,而不应使用QDropEvent::acceptProposedAction()函数(因为该函数会重置拖放动作为建议拖放动作)
    • 若 QDropEvent::setDropAction()函数设置的拖放动作不在可能的拖放动作组合之中,则使用建议拖放动作。
  3. dropEvent()函数的规则,该函数是否接受事件直接影响到 QDrag::exec()函数的返回值,其规则如下

    • 若在该函数内调用 ignore(),则 exec()函数返回 Qt::IgnoreAction
    • 若在该函数内调用 accept(),则 exec()函数返回在该函数中使用 setDropAction()函数设置的拖放动作。

4. 拖放动作及拖放程序设计原则

  1. 若在 mouseMoveEvent()函数中启动拖放,则可以编写避免用户因为手握鼠标抖动而产生的拖动,这比在 mousePressEvent()函数中启动拖放效果更好。

  2. 在 QDrag::exec()函数的参数中指定可能的拖放动作, 比如在其中同时指定移动、复制、链接等; 但最终是否接受这些动作,由后续的事件处理函数进行判断,详见后文。 另外需要注意的是 QDrag::exec()函数虽是阻塞函数,但在执行完该函数(比如释放鼠标按钮完成拖放时)后程序会返回该函数,然后接着执行之后的语句, exec()函数返回的是用户实际执行的动作。

  3. dragEnterEvent()函数通常根据该部件或实际使用情况进行筛选, 比如若该部件不接受图片数据,则忽略对该动作的接受,从而阻止事件被进一步传递。

  4. 若重新实现了 dragMoveEvent()函数,则还可以在该函数内进行进一步的设置, 比如默认为复制动作,若用户拖动的同时按下了 Shift 键,则设置为移动动作,若按下了Ctrl 键,则为复制动作,若按下了 Alt 键则为链接动作等,在该函数中的设置可以影响鼠标光标在外观上的显示,比如复制会在光标右下角显示一个“+”符号等。另外,在该函数内还可以设置用户拖动到该部件中的范围,比如拖动到某矩形范围内时,该部件才接受拖放,否则被忽略等。注意:若在 dragEnterEvent()函数内也设置了拖放动作,同样会改变光标的外观但只会改变进入部件时的外观,光标最终的外观以dragMoveEvent()函数设置的为准(因为该函数位于 dragEnterEvent()之后执行)。

  5. 在 dropEvent()函数内最终决定对拖放的数据的处理,以及用户实际执行的拖放动作,因此该函数决定着 QDrag::exe()函数的返回值,这里要注意的是,对于移动动作,通常原始数据应由源部件(启动拖放的部件)进行删除,因此当 drogEvent()处理完数据之后,应把拖放动作设置为移动, QDrag::exec()函数会返回在 drogEvent()函数中设置的动作,然后源部件根据 QDrag::exe()的返回值是否是移动动作,而作出是否删除原始数据的决定。注: dragEnterEvent()和 dragMoveEvent()对拖放动作的设置不会影响QDrag::exe()的返回值。

  6. 注意:在源部件中创建的 QMimeData 和 QDrag 对象不应由程序员销毁,因为 Qt 会自动销毁,若程序员销毁了,则可能会出现多次 delete 同一个指针的错误。

  7. 以上只是通常在各函数中的做法,当然你也可以不按照这些步骤来实现,从之前的拖放示例可以看到,对拖放的处理完全是任意的。

  8. 注意: Qt 为某些部件提供了一些标准的拖放支持,在继承这些部件实现拖放时需要重新实现 dragEnterEvent()、 dropEvent(),另外还可能需要重新实现 dragMoveEvent()函数,以避免与标准实现的拖放支持相冲突或产生预料之外的结果。

5. 拖放动作示例程序:

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout
from PyQt5 import QtGui
from PyQt5.QtGui import QDrag
from PyQt5.QtCore import QPoint, QMimeData, Qt, qDebug, QRect
import sysclass MyButton(QPushButton):def __init__(self, text, parent=None):super().__init__(text, parent)self.point = QPoint()   # 用于保存第一次点击鼠标的位置def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:self.point = e.pos()def mouseMoveEvent(self, e: QtGui.QMouseEvent) -> None:# 若移动的距离大于5个像素,则启动拖放(避免抖动)if abs(e.pos().x() - self.point.x()) >= 5 or abs(e.pos().y() - self.point.y()) >= 5:# 拖放数据准备my_drag = QDrag(self)my_mime = QMimeData()my_mime.setText('hello')my_drag.setMimeData(my_mime)qDebug('mimedata already set')# 启动拖放,该数据可复制、移动、链接,具体是否接受这些动作,需要由后续程序决定# 若dropEvent函数把拖放动作设置为移动,则需要对原始数据做进一步处理,注意:在完成拖放后会返回exec()函数,然后继续执行其后的语句supportedActions = Qt.DropActions()supportedActions = supportedActions | Qt.DropAction.CopyAction | Qt.DropAction.MoveAction | Qt.DropAction.LinkActionreurn_action = my_drag.exec_(supportedActions)if reurn_action == Qt.DropAction.MoveAction:# 本例中没有可以删除的数据,只简单输出字符串qDebug('move action executed done')else:qDebug(f'my_drag.exec_ return: {reurn_action}')def dragEnterEvent(self, e: QtGui.QDragEnterEvent) -> None:# 若拖动的数据中不包含文本hello,则忽略该事件,否则接受该事件qDebug('dragEnterEvent')if e.mimeData().text() != 'hello':e.ignore()e.accept()def dragMoveEvent(self, e: QtGui.QDragMoveEvent) -> None:qDebug('dragMoveEvent')# 以下设置会改变鼠标光标的外观。若拖动的同时按下了CTRL/ALT/SHIFT键,则把拖放动作设置为复制、移动、链接,否则为复制if e.keyboardModifiers() ==  Qt.KeyboardModifier.ControlModifier:qDebug('set CopyAction')e.setDropAction(Qt.DropAction.CopyAction)elif e.keyboardModifiers() == Qt.KeyboardModifier.ShiftModifier:qDebug('set MoveAction')e.setDropAction(Qt.DropAction.MoveAction)elif e.keyboardModifiers() == Qt.KeyboardModifier.AltModifier:qDebug('set LinkAction')e.setDropAction(Qt.DropAction.LinkAction)else:qDebug('set default CopyAction')e.setDropAction(Qt.DropAction.CopyAction)# 若光标位于button自身矩形r内,则接受该事件,否则忽略该事件r = QRect(0, 0, self.rect().width(), self.rect().height())qDebug(f'r.x={r.x()}, r.y={r.y()}, r.width={r.width()}, r.height={r.height()}')qDebug(f'e.pos: x={e.pos().x()}, y={e.pos().y()}')if r.contains(e.pos()):qDebug('inside rect, accept')e.accept()else:qDebug('outside rect, ignore')e.ignore()def dropEvent(self, e: QtGui.QDropEvent) -> None:qDebug('dropEvent')# 若拖动的源和目标在同一个部件,则什么也不做。注意,应该使用return跳出函数,若使用ignore或accept,程序还会继续执行之后的语句if e.source() == self:returnself.setText(e.mimeData().text())qDebug('dropEvent done. will return move action')# 把拖放动作设为移动e.setDropAction(Qt.DropAction.MoveAction)# 执行accept会把QDrag.exec()函数的返回值置为上面设置的移动动作e.accept()class MyWidget(QWidget):def __init__(self, parent=None):super().__init__(parent)self.init_ui()def init_ui(self):btn_a = MyButton('AAA')btn_b = MyButton('BBB')btn_a.setAcceptDrops(False)btn_b.setAcceptDrops(True)layout = QHBoxLayout()layout.addWidget(btn_a)layout.addWidget(btn_b)self.setLayout(layout)if __name__ == '__main__':app = QApplication(sys.argv)my_widget = MyWidget()my_widget.show()sys.exit(app.exec_())

初始界面
20221128203706

不按任何键直接拖动,或者按住ctrl键进行拖动
20221128203802

按住shift键进行拖动
20221128204751

按住alt键进行拖动
20221128204901

小手一抖,点个赞再走哦

相关内容

热门资讯

原创 戴... 最近,关于前国脚戴琳的欠薪丑闻无疑是引发了球迷的持续关注,从10月25日,媒体人李平康率先爆料,晒出...
思想政治工作条例最新修订内容,... 思想政治工作条例最新修订内容,思想政治工作条例全文下载 思想政治工作条例最新修订,全文下载与深度解读...
CBA潜力赛为何打成“老将赛”... 计时钟归零,双方教练握手致意,观众开始退场,CBA联赛的正赛宣告结束。然而球场并未就此沉寂,替补席上...
“手术钻头断裂遗留患者体内”,... 12月21日,湖南祁阳市卫生健康局发布情况通报称,近日,有媒体报道祁阳市中医医院发生骨科手术钻头断裂...
代驾纠纷 代驾时撞伤行人、车辆发生故障…… 这些都和车主无关,应由代驾赔偿? 观点: 使用代驾服务并非将所有...
公司股东与妻子分居期间出轨女下... 近日据报道,宁夏永宁县人民法院一审查明公司股东李某乙在与妻子李某甲分居期间,与公司女员工马某某存在不...
动物学家、律师和创作者,Thi... 12月21日,以“一起·了不起”为主题的2025 ThinkPad黑FUN礼在京举办。活动现场,律师...
徐奇渊:扩内需与对外政策紧密相... 近日,中国海关总署发布了一组数据令人关注:2025年前11个月,我国货物贸易顺差达到1.08万亿美元...
46岁上海独居女子不幸离世,官... 居住在上海虹口区46岁的蒋女士因突发脑溢血于今年10月入院,远亲吴先生与其公司共同垫付了医药费,但她...