news 2026/10/4 15:12:04

PyQt6 中文教程:拖放(Drag Drop)操作实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyQt6 中文教程:拖放(Drag Drop)操作实战指南
  • 文档
  • 教程

【免费下载链接】PyQt-Chinese-tutorial

PyQt6中文教程

项目地址:https://gitcode.com/gh_mirrors/py/PyQt-Chinese-tutorial
点击查看免费下载

拖放(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

一个典型的拖放流程是:

  1. 拖动源(如启用了拖拽的QLineEdit)把数据封装进QMimeData;
  2. 通过QDrag启动拖放,QDrag负责在拖动期间展示拖拽对象、与目标部件协商;
  3. 目标部件通过重写dragEnterEvent判断是否接受传入的数据(按 MIME 类型过滤);
  4. 若接受,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.LeftButtonQt.MouseButton.RightButton、Qt.MouseButton.LeftButton
放置动作枚举Qt.MoveActionQt.DropAction.MoveAction
鼠标位置取值e.pos()(QPoint)e.position()(QPointF,需toPoint()转换)
启动拖放drag.exec_(Qt.MoveAction)drag.exec(Qt.DropAction.MoveAction)
应用主循环app.exec_()app.exec()

其中两点对移植代码影响最大:

  1. 枚举彻底命名空间化:PyQt6 把所有枚举收纳进对应的作用域类(Qt.MouseButton、Qt.DropAction),旧的裸枚举写法(Qt.RightButton、Qt.MoveAction)在 PyQt6 中不再可用;
  2. 坐标返回类型变化: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中文教程

项目地址:https://gitcode.com/gh_mirrors/py/PyQt-Chinese-tutorial
点击查看免费下载

相关推荐

上一篇:WeMod-Patcher 解锁 Pro:两种补丁方案怎么选,附 3 个坑的绕法
下一篇:FinalBurn Neo 使用指南:3 步让旧电脑变成街机模拟器

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 15:11:57

ESP32选型避坑指南:芯片与模组差异及量产选型流程

1. 从一颗芯片到一张订单:为什么“ESP32”这三个字最容易让人踩坑刚入行那会儿,我在采购系统里搜“ESP32”,跳出来几十个料号,价格从几块钱到几十块钱不等,封装从QFN到邮票孔模组五花八门。当时天真地以为“ESP32”就是…

作者头像 李华
网站建设 2026/10/4 15:11:22

AI工程从零到一:绕开模型理论,先搞懂数据、部署与监控

最近后台总有朋友私信我同一个问题:想转 ai-engineering,但网上资料铺天盖地,反而不知道从哪下手。我特别理解这种焦虑,因为几年前我刚接触这个方向时也一样。但做了几年一线 AI 工程之后,我最深的体会是:多…

作者头像 李华
网站建设 2026/10/4 15:10:22

28 秒压到 1.2 秒:backtesting.py 回测提速实战手册

28 秒压到 1.2 秒:backtesting.py 回测提速实战手册 【免费下载链接】backtesting.py 🔎 📈 🐍 💰 Backtest trading strategies in Python. 项目地址: https://gitcode.com/GitHub_Trending/ba/backtesting.py …

作者头像 李华
网站建设 2026/10/4 15:08:06

Flask+微信小程序实战:课程题库学习小程序开发全攻略

我最近刚把一个典型的“讲师学员”学习类小程序完整跑通了。后端是Python Flask写的数据接口,前端是微信小程序,核心功能就两块:一块是学习视频课程,一块是知识题库。讲师能上传视频、维护题目和看学员数据,学员可以看…

作者头像 李华
网站建设 2026/10/4 15:07:50

Windows 上 ESP32-C3 开发环境搭建:ESP-IDF + VS Code 完整指南

1. 为什么要在 Windows 上折腾 ESP32-C3 这套环境 先说结论:ESP32-C3 是一颗性价比极高的 RISC-V 架构 Wi-Fi/蓝牙双模芯片,单核 160MHz,内置 400KB SRAM,价格常年压在十元出头,非常适合做物联网节点、传感器网关、小型…

作者头像 李华
网站建设 2026/10/4 15:07:06

Claude Code 源码泄露后,5 分钟用 TaoToken 搭建本地离线 AI 程序员

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华