• 24. [Python GUI] PyQt5中拖放的基本原理


    PyQt5的拖放

    一、拖放相关的类

    拖放涉及到的主要的一些类如下所示:

    20221128012307

    二、拖放的基本原理

    2.1 拖放的动作

    拖放操作包括两个动作:

    • 拖动(drag)
    • 放下(drop 或称为放置)。

    当被拖动时拖动的数据会被存储为 MIME 类型的对象, MIME 类型使用 QMimeData 类来描述。 MIME 类型通常由剪贴板和拖放系统使用,以识别不同类型的数据。

    • 拖动点(drag site):
      拖动的起始位置。
    • 放下点(drop site):
      被拖动的对象放下的位置,若部件不能接受拖动的对象, Qt 会改变光标的形状(一个禁用形状)来向用户进行说明。

    2.2 拖动的启动和结束

    • 启动拖放:
      拖放通过调用 QDrag::exec()函数而启动,该函数是一个阻塞函数(但不会阻塞主事件循环),这意味着在拖放操作结束之前,不会返回该函数,调用 QDrag::exec()函数后, Qt 拥有对拖动对象的所有权,并会在必要时将其删除。

    • 结束拖放:
      当用户放下拖动或取消拖动操作时结束拖放。

    2.3 拖放产生的过程和事件

    启动拖放后,会使数据被拖动,这时需要按住鼠标按键才能拖动需要拖动的数据,松开鼠标按键时意味着拖动结束。

    默认情况下,部件不接受放下事件。使用 QWidget::setAcceptDrops()函数可设置部件是否接受放下事件(即,拖放完成时发送的事件)。只有在部件接受放下事件的情形下,才会产生以下事件。

    • QDragEnterEvent:拖动进入事件。
      当拖动操作进入部件时,该事件被发送到部件,忽略该事件,将会导至后续的拖放事件不能被发送。 通常在该部件上光标会在外观上显示为禁用的图形。

    • QDragMoveEvnet:拖动移动事件。
      当拖动操作正在进行时,以及当具有焦点时按下键盘的修饰键(比如 Ctrl)时, 发送该事件, 要使部件能接收到该事件,则该部件必须接受 QDragEnterEvent 事件。

    • QDropEvent:放下事件。
      在完成拖放操作时发送该事件,即当用户在部件上放下一个对象时,发送此事件。要使部件能接收到该事件,则该部件必须接受 QDragEnterEvent事件,且不能忽略 QDragMoveEvnt 事件。

    • QLeaveEvent:当拖放操作离开部件时发送该事件
      注意:要使部件能接收到该事件,必须要使拖动先进入该部件(即产生 QDragEnterEvent 事件),然后再离开该部件,才会产生 QLeaveEvent 事件。因很少使用该事件,因此本文不做重点介绍。

    上文中提到的必须接受是指必须重新实现该事件的处理函数并接受该事件,不能忽略是指在处件事理函数中不明确调用 ignore()函数忽略该事件,这意味着可以不必重新实现该事件的处理函数。

    以上事件产生的顺序为: QDragEnterEvent、 QDragMoveEvnet、 QDropEvent

    20221128013420

    2.4 编写拖放程序的步骤

    1. 在需要接受放下数据的部件上调用 QWidget::setAcceptDrops()函数以使该部件能接受拖放事件。

    2. 启动拖放: 通常在 mousePressEvent()或 mouseMoveEvent()函数中启动拖放,记住启动拖放就是调用 QDrag 对象的 exec()函数,因此也可以在 keyPressEvent()等函数中启动拖放(因很少这样做,所以本文不介绍这种情况下的拖放)。 在此步把需要拖动的数据保存在 QMimeData 对象中。

    3. 重新实现需要接受放下数据的部件的 dragEnterEvent()事件处理函数。

    4. 根据需要重新实现 dragMoveEvent 或 dropEvent()函数

    2.5 简单的拖放示例代码

    本示例程序示范了如何把数据从按钮A拖至按钮B:

    from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout
    from PyQt5.QtCore import QMimeData, qDebug
    from PyQt5 import QtGui
    from PyQt5.QtGui import QDrag
    import sys
    
    class MyButton(QPushButton):
        def __init__(self, text:str) -> None:
            super().__init__(text)
            
        def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
            '''
            在该事件中启动拖放
            '''
            # 将需要拖动的数据放入QMimeData对象中,该对象用于保存需要传递的数据
            # 数据的内容完全由程序员自行设定。通常为界面上所选择的内容。
            my_mime_data = QMimeData()
            
            # 这是QMimeData中存储的内容,即拖放的数据
            my_mime_data.setText(self.text())
            
            # 设置拖动的数据,该函数会获得QMimeData的所有权
            my_drag = QDrag(self)
            my_drag.setMimeData(my_mime_data)
            
            # 启动拖放
            my_drag.exec_()
            
        def dragEnterEvent(self, e: QtGui.QDragEnterEvent) -> None:
            '''
            处理是否接受拖动事件
            '''
            # 接受拖动进入事件
            e.accept()
            
            # 若忽略该事件,则不会再发送之后的事件,拖放至此结束,这会导致鼠标光标显示为禁用的图形
            # e.ignore()
            
        def dropEvent(self, e: QtGui.QDropEvent) -> None:
            '''
            处理拖动的数据(当然了,也可以不做任何处理)
            '''
            # 设置此部件的文本为拖动对象中的文本
            self.setText(e.mimeData().text())
            
            # 此事件不影响后续事件,可接受也可忽略
            # e.accept()
            # e.ignore()
            
    class MyWidget(QWidget):
        def __init__(self, parent=None) -> 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_())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    运行效果如下:

    原始状态:
    20221128025023

    在按钮AAA上按下鼠标左键不动并拖动到图示位置,由于主窗口不接受放下事件,因此光标显示为禁用的状态
    20221128025214

    拖动AAA到按钮BBB上时,会发送QDragEnterEvent事件,同时光标改变形状,表示BBB按钮可以接受拖动的数据,
    在按钮BBB上释放鼠标时,此时发送QDropEvent事件,按钮BBB的文本被修改为拖动对象中保存的数据。
    20221128025250

    至此,拖动结束。

  • 相关阅读:
    layui table合并相同的列
    137.【SpringCloud-快速搭建】
    Linux常见指令:从基础到理论
    Java多线程(5):CAS
    Flask 数据库 连接池、DBUtils、http 连接池
    java基于微信小程序的大学生个人家庭理财产品 uniapp小程序
    自动拟人对话机器人在客户服务方面起了什么作用?
    操作系统中文件系统的实现和分配方式探析(上)
    并发编程的12种业务场景
    前端新宠 Svelte,呜呜,卷不动了
  • 原文地址:https://blog.csdn.net/hubing_hust/article/details/128072839