- 文档
- 教程
【免费下载链接】PyQt-Chinese-tutorial
PyQt6中文教程
拖放(Drag & Drop)是图形界面中最直观的交互方式之一,本教程来自 PyQt6 中文教程(PyQt-Chinese-tutorial)中的 拖拽操作章节,围绕QDrag、QMimeData以及一组可重写的事件处理器展开。读完本文你将掌握两种典型的 PyQt6 拖放实现:一是从QLineEdit拖拽纯文本到QPushButton并更新按钮标签,二是用鼠标右键拖拽按钮组件本身、在窗口内任意移动它;同时还能了解 PyQt6 相对 PyQt5 在拖放 API 上的关键差异。
拖放操作的基本概念
在计算机图形用户界面中,拖放是点击一个虚拟对象、按住不放并将其拖到另一个位置或另一个虚拟对象之上的动作。一般来说,它可以触发多种类型的动作,或者在两个抽象对象之间建立各种类型的关联。拖放是 GUI 的一部分,它让用户能够以非常直观的方式完成复杂的事情。
通常我们可以拖放两种东西:数据或图形对象。
- 如果把一张图片从一个应用程序拖到另一个应用程序,拖放的是二进制数据;
- 如果在 Firefox 中把一个选项卡拖到另一个位置,拖放的是一个图形组件。
这两类场景在 PyQt6 中对应两条不同的实现路径:数据传输型拖放依赖 MIME 数据封装,而组件拖放则直接操作控件本身的坐标与位置。本文的两个示例恰好分别覆盖了这两种情况。
从 PyQt6 的事件和信号教程可知,PyQt6 程序是事件驱动的:应用进入主循环后,由事件源产生事件,再交由对应的事件处理器处理。拖放相关的dragEnterEvent、dropEvent、mouseMoveEvent等都属此类事件处理器,它们都有默认实现,当我们想自定义拖放行为时,就需要重写这些方法。
QDrag 与基于 MIME 的数据传输
拖放的核心类是QDrag:它提供对基于 MIME 的拖放数据传输的支持,处理拖放操作中的大部分细节。传输的数据包含在QMimeData对象中。
在 PyQt6 中,相关的类分布在三个模块里,从示例代码的导入语句可以清晰看到分工:
from PyQt6.QtCore import Qt, QMimeData # 枚举、MIME 数据容器 from PyQt6.QtGui import QDrag # 拖放传输载体 from PyQt6.QtWidgets import QPushButton, QWidget, QApplication一个典型的拖放流程是:
- 拖动源(如启用了拖拽的
QLineEdit)把数据封装进QMimeData; - 通过
QDrag启动拖放,QDrag负责在拖动期间展示拖拽对象、与目标部件协商; - 目标部件通过重写
dragEnterEvent判断是否接受传入的数据(按 MIME 类型过滤); - 若接受,
dropEvent在释放鼠标时被触发,目标部件从QMimeData中取出数据完成最终处理。
示例一:把纯文本拖放到按钮上(simple.py)
第一个示例中有一个QLineEdit和一个QPushButton。我们把纯文本从行编辑部件拖到按钮部件上,按钮的标签会随之改变。完整代码如下:
# file: simple.py #!/usr/bin/python """ ZetCode PyQt6 tutorial This is a simple drag and drop example. """ import sys from PyQt6.QtWidgets import (QPushButton, QWidget, QLineEdit, QApplication) class Button(QPushButton): def __init__(self, title, parent): super().__init__(title, parent) self.setAcceptDrops(True) def dragEnterEvent(self, e): if e.mimeData().hasFormat('text/plain'): e.accept() else: e.ignore() def dropEvent(self, e): self.setText(e.mimeData().text()) class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): edit = QLineEdit('', self) edit.setDragEnabled(True) edit.move(30, 65) button = Button("Button", self) button.move(190, 65) self.setWindowTitle('Simple drag and drop') self.setGeometry(300, 300, 300, 150) def main(): app = QApplication(sys.argv) ex = Example() ex.show() app.exec() if __name__ == '__main__': main()下面逐步拆解这个示例的关键实现。
自定义 Button 类:成为拖放目标
为了把文本拖到QPushButton上,我们必须重写(reimplement)一些方法,因此创建了一个继承自QPushButton的Button类:
class Button(QPushButton): def __init__(self, title, parent): super().__init__(title, parent) self.setAcceptDrops(True)setAcceptDrops(True)用于开启部件的释放事件(drop events)。只有调用了该方法,部件才会响应后续的拖放事件。
dragEnterEvent:声明接受的数据类型
def dragEnterEvent(self, e): if e.mimeData().hasFormat('text/plain'): e.accept() else: e.ignore()dragEnterEvent方法定义了部件接受的数据类型——本例中只接受纯文本(text/plain)。当拖拽对象进入部件区域时,事件对象e携带mimeData,我们通过hasFormat('text/plain')判断其 MIME 格式:匹配则调用e.accept()放行,否则调用e.ignore()拒绝。这一步是拖放能否落地的“准入检查”。
dropEvent:处理释放动作
def dropEvent(self, e): self.setText(e.mimeData().text())dropEvent方法处理释放事件。当用户在按钮上松开鼠标完成放置时,这里从mimeData中取出文本(text()即text/plain内容的取值方法),并作为按钮的新标签显示出来。
启用 QLineEdit 的拖拽
edit = QLineEdit('', self) edit.setDragEnabled(True)QLineEdit部件内置支持拖放操作,我们只需要调用setDragEnabled(True)激活它,就能从输入框中拖出选中的文本,无需任何额外代码。
运行该程序的效果如下图所示,输入框中输入ZetCode后将其拖到右侧按钮上,按钮标签即被替换:
示例二:拖放按钮组件(drag_button.py)
第二个示例演示如何拖放按钮组件本身。窗口上有一个QPushButton:鼠标左键点击它时,控制台打印press消息;鼠标右键按住拖动时,则对按钮执行拖放操作,把它移动到鼠标释放的位置。完整代码如下:
# file: drag_button.py #!/usr/bin/python """ ZetCode PyQt6 tutorial 在这个程序里,有一个按钮,可以用鼠标左键点击,也可以鼠标右键拖拽 """ import sys from PyQt6.QtCore import Qt, QMimeData from PyQt6.QtGui import QDrag from PyQt6.QtWidgets import QPushButton, QWidget, QApplication class Button(QPushButton): def __init__(self, title, parent): super().__init__(title, parent) def mouseMoveEvent(self, e): if e.buttons() != Qt.MouseButton.RightButton: return mimeData = QMimeData() drag = QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(e.position().toPoint() - self.rect().topLeft()) dropAction = drag.exec(Qt.DropAction.MoveAction) def mousePressEvent(self, e): super().mousePressEvent(e) if e.button() == Qt.MouseButton.LeftButton: print('press') class Example(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setAcceptDrops(True) self.button = Button('Button', self) self.button.move(100, 65) self.setWindowTitle('Click or Move') self.setGeometry(300, 300, 550, 450) def dragEnterEvent(self, e): e.accept() def dropEvent(self, e): position = e.position() self.button.move(position.toPoint()) e.setDropAction(Qt.DropAction.MoveAction) e.accept() def main(): app = QApplication(sys.argv) ex = Example() ex.show() app.exec() if __name__ == '__main__': main()同样逐步拆解。
Button 类:重写鼠标事件
基于QPushButton创建Button类,并重写QPushButton的两个方法:mouseMoveEvent和mousePressEvent。其中mouseMoveEvent是拖放操作开始的地方:
class Button(QPushButton): def __init__(self, title, parent): super().__init__(title, parent)注意:本例中按钮自身并没有调用setAcceptDrops,因为拖放目标(接受方)是外层窗口Example,而不是按钮本身。
限定右键触发拖拽
if e.buttons() != Qt.MouseButton.RightButton: return这里规定只有鼠标右键可以触发拖放操作,左键保留给“点击”使用。注意 PyQt6 中枚举写法为Qt.MouseButton.RightButton(详见下文的 PyQt5 对比),e.buttons()返回当前按下的鼠标按键状态。
创建 QDrag 并启动拖放
mimeData = QMimeData() drag = QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(e.position().toPoint() - self.rect().topLeft()) dropAction = drag.exec(Qt.DropAction.MoveAction)- 创建
QDrag对象,它提供基于 MIME 的拖放数据传输支持; - 把
QMimeData对象设置给drag(此处没有填充具体内容,仅作为携带者); setHotSpot设置拖拽光标的热点位置:用当前鼠标位置(e.position(),注意返回的是QPointF,需经toPoint()转为整数坐标)减去按钮矩形左上角(self.rect().topLeft()),这样拖拽时按钮不会“跳帧”;drag.exec(Qt.DropAction.MoveAction)启动并进入拖放操作的阻塞循环,返回实际执行的放置动作(这里将返回值赋给dropAction便于后续判断,示例中未进一步使用;可以推断在实际应用中可据此检查拖放是否被接受)。
左键点击:打印 press
def mousePressEvent(self, e): super().mousePressEvent(e) if e.button() == Qt.MouseButton.LeftButton: print('press')鼠标左键点击按钮时,控制台打印press消息。注意这里先调用了父级的mousePressEvent,否则按钮被按下的视觉反馈不会呈现——这一点与事件和信号教程中“重写事件处理器需保留默认逻辑”的思路一致。
窗口作为放置目标:移动按钮
Example窗口调用了self.setAcceptDrops(True),成为拖放目标:
def dragEnterEvent(self, e): e.accept()dragEnterEvent无条件接受拖入(示例中没有做 MIME 过滤,接受所有拖放)。
def dropEvent(self, e): position = e.position() self.button.move(position.toPoint()) e.setDropAction(Qt.DropAction.MoveAction) e.accept()dropEvent定义鼠标释放并完成放置后的行为:取出当前鼠标指针位置(e.position(),QPointF,同样需toPoint()转换),把按钮move到该坐标;e.setDropAction(Qt.DropAction.MoveAction)指定本次放置动作的类型——这里是一个移动(Move)动作;- 最后
e.accept()接受该放置事件。
从 PyQt5 到 PyQt6 的拖放 API 差异
本仓库同时收录了 PyQt5 版拖拽教程(对应translated/pyqt5/tuo-zhuai.md)与 PyQt6 版本(即本文依据的 translated/pyqt6/dragdrop.md,英文原文见 original/pyqt6/dragdrop.md)。对照两版源码,可以归纳出 PyQt6 中拖放编程的关键差异:
| 关注点 | PyQt5 写法 | PyQt6 写法 |
|---|---|---|
| 鼠标按键枚举 | Qt.RightButton、Qt.LeftButton | Qt.MouseButton.RightButton、Qt.MouseButton.LeftButton |
| 放置动作枚举 | Qt.MoveAction | Qt.DropAction.MoveAction |
| 鼠标位置取值 | e.pos()(QPoint) | e.position()(QPointF,需toPoint()转换) |
| 启动拖放 | drag.exec_(Qt.MoveAction) | drag.exec(Qt.DropAction.MoveAction) |
| 应用主循环 | app.exec_() | app.exec() |
其中两点对移植代码影响最大:
- 枚举彻底命名空间化:PyQt6 把所有枚举收纳进对应的作用域类(
Qt.MouseButton、Qt.DropAction),旧的裸枚举写法(Qt.RightButton、Qt.MoveAction)在 PyQt6 中不再可用; - 坐标返回类型变化:
e.position()返回浮点坐标的QPointF,拖放相关代码里要记得用toPoint()转换,否则move()等接收整数QPoint的方法会类型不匹配。
拖放操作要点小结
回顾本章,PyQt6 实现拖放的核心套路可以概括为:
- 拖动源:内置支持拖拽的控件(如
QLineEdit)只需setDragEnabled(True);自定义拖动源则在mouseMoveEvent中创建QDrag、设置QMimeData与热点,再调用exec启动; - 放置目标:先
setAcceptDrops(True)开启释放事件,再重写dragEnterEvent(按mimeData().hasFormat(...)过滤可接受的数据类型)与dropEvent(取数据、改状态、必要时setDropAction声明动作类型); - 两种对象:拖放“数据”时通过
QMimeData传递,拖放“图形组件”时直接操作控件坐标,两者共用同一套事件模型; - 事件处理器:
dragEnterEvent、dropEvent、mouseMoveEvent、mousePressEvent均有默认实现,重写时注意保留必要的父级调用(如示例二中的super().mousePressEvent(e))。
掌握这套机制后,你可以进一步扩展出列表重排序、跨窗口拖放、文件拖入(处理text/uri-listMIME 格式)等更丰富的交互,它们都建立在本文所述的QDrag+QMimeData+ 事件重写的基础框架之上。
- 文档
- 教程
【免费下载链接】PyQt-Chinese-tutorial
PyQt6中文教程
相关推荐
抖音下载工具怎么用:从 Cookie 到批量采集的 4 个实操场景
抖音下载工具怎么用:从 Cookie 到批量采集的 4 个实操场景 DouK Downloader(工程名 TikTokDownloader)是一款免费的抖音下
文档教程如何永久保存微信聊天记录:3步实现完整数据备份与智能分析
如何永久保存微信聊天记录:3步实现完整数据备份与智能分析 你是否曾担心手机丢失或更换设备时,那些珍贵的微信聊天记录会永远消失?那些与家人的温馨对话、朋友间的快乐
Wails v3 文件拖放(File Drag & Drop)实战:从操作系统文件拖入到应用内 HTML5 拖拽
Wails v3 文件拖放(File Drag & Drop)实战:从操作系统文件拖入到应用内 HTML5 拖拽 本文以 Wails v3 官方示例 drag
桌面应用跨平台CLI前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考