现代Qt开发教程新手篇2.5——拖放系统基础相关仓库仍然已经开源正在积极火热的建设之中欢迎各位大佬提Issue和PR链接地址https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeQt1. 前言 / 为什么需要拖放说实话拖放这个功能在很多应用里看起来像是锦上添花但真正做项目的时候你会发现它几乎是刚需。想想看文件管理器里把文件从一个文件夹拖到另一个文件夹、图片编辑器里拖入一张图片直接打开、看板工具里拖动任务卡片改变状态——这些操作如果没有拖放支持用户体验会瞬间回到上个世纪。Qt 的拖放系统设计得相当完整但完整也意味着概念多。你需要理解的东西包括怎么让一个 Widget 接受拖入的数据、怎么从你的 Widget 发起一次拖放操作、怎么用 QMimeData 在拖放过程中携带各种格式的数据。这些东西拼在一起就是一套完整的拖放交互链路。OK咱们那就开始这次的学习吧2. 环境说明本篇代码适用于 Qt 6.5 版本CMake 3.26C17 或更高标准。拖放相关的类分布在 QtGuiQDrag、QMimeData和 QtWidgetsQWidget 的 dragEnterEvent 等两个模块中所以示例代码需要同时链接这两个模块。桌面平台上均可正常编译运行但需要注意的是 Qt 的拖放功能依赖于底层窗口系统的支持在无窗口系统的嵌入式环境下行为可能不同。3. 核心概念讲解3.1 启用拖放接收setAcceptDrops默认情况下QWidget 是不接受拖放操作的。如果你不做任何设置当用户把东西拖到你的 Widget 上方时光标会显示一个禁止图标表示这里不允许放下。要让一个 Widget 变成合法的拖放目标第一步就是在构造函数里调用setAcceptDrops(true)。classDropWidget:publicQWidget{Q_OBJECTpublic:explicitDropWidget(QWidget*parentnullptr):QWidget(parent){setAcceptDrops(true);// 这是第一步没有这一步后面全白搭setMinimumSize(300,200);}};这个设置的含义很直白告诉 Qt 的事件系统这个 Widget 对拖进来的东西感兴趣请把相关的拖放事件分发给我。如果你忘了这一步后面不管怎么重写dragEnterEvent和dropEvent它们都不会被调用——因为 Qt 在事件分发的早期阶段就帮你把这个事件忽略掉了。3.2 dragEnterEvent 和 dropEvent 处理拖放接收setAcceptDrops(true)只是开了门真正的逻辑需要我们通过重写两个事件处理函数来实现。第一个是dragEnterEvent(QDragEnterEvent *event)。这个事件在拖拽操作进入 Widget 区域时触发你需要在这里决定这个拖进来的东西我收不收判断的依据通常是检查拖放数据中包含的 MIME 类型。如果决定接受就调用event-acceptProposedAction()如果不接受什么都不做就行Qt 会自动显示禁止图标。voiddragEnterEvent(QDragEnterEvent*event)override{// 检查拖入的数据是否包含我们想要的格式if(event-mimeData()-hasText()){event-acceptProposedAction();// 告诉 Qt这个我要了}// 如果不调用 acceptProposedAction()Qt 会认为你拒绝了这次拖放}第二个是dropEvent(QDropEvent *event)。这个事件在用户松开鼠标、真正把东西放下时触发。到这一步说明dragEnterEvent已经放行了所以你可以放心地从event-mimeData()里提取数据。voiddropEvent(QDropEvent*event)override{QString textevent-mimeData()-text();qDebug()收到拖放文本:text;// 把文本存起来然后触发重绘或更新 UIm_droppedTexttext;update();event-acceptProposedAction();// 确认接收}你可能会注意到这两个函数里都调用了acceptProposedAction()。这个函数的作用是告诉 Qt我接受了这次拖放并且同意执行拖放源提议的动作复制、移动还是链接。如果你想在接收端改变动作类型可以用event-setDropAction(Qt::CopyAction)先覆盖再 accept。还有一个事件你可能偶尔会用到dragMoveEvent(QDragMoveEvent *event)。它在拖拽过程中鼠标在 Widget 内移动时持续触发。大多数简单场景你不需要重写它但在某些情况下——比如你想根据鼠标位置高亮显示某个区域——这个事件就派上用场了。voiddragMoveEvent(QDragMoveEvent*event)override{// 可以根据 event-pos() 判断鼠标在哪个区域// 用于实现拖放时的视觉反馈event-acceptProposedAction();}3.3 用 QDrag 发起拖放操作上面讲的是接收端的逻辑现在我们看看发送端——怎么让你的 Widget 成为一个拖放源让用户可以从这里拖走数据。发起拖放的标准做法是在mousePressEvent或mouseMoveEvent中创建一个QDrag对象。通常我们会选择在mouseMoveEvent里做这件事因为这样可以加入一个移动阈值避免用户只是想点击结果触发了拖拽。voidmouseMoveEvent(QMouseEvent*event)override{// 检查移动距离是否超过了拖拽阈值if(!(event-buttons()Qt::LeftButton)){return;}if((event-pos()-m_dragStartPos).manhattanLength()QApplication::startDragDistance()){return;// 移动距离太短不算拖拽}// 创建拖放对象QDrag*dragnewQDrag(this);// 创建 MIME 数据并设置内容QMimeData*mimeDatanewQMimeData;mimeData-setText(m_textToDrag);drag-setMimeData(mimeData);// 可选设置拖拽时跟随鼠标的缩略图// drag-setPixmap(QPixmap::fromImage(someImage));// 执行拖放操作这个调用会阻塞直到拖放结束Qt::DropAction resultdrag-exec(Qt::CopyAction|Qt::MoveAction);qDebug()拖放结果:result;// result 是 Qt::CopyAction / Qt::MoveAction / Qt::IgnoreAction 之一}这里有几个要点需要理清。首先QDrag和QMimeData的父子关系是通过setMimeData自动建立的——QMimeData的所有权会转移给QDrag所以你不需要手动 delete。QDrag::exec()是一个阻塞调用它会启动一个局部的嵌套事件循环来处理整个拖拽过程直到用户松开鼠标或取消操作才返回。返回值告诉你最终执行了什么动作。另外别忘了在mousePressEvent里记录起始位置这是判断拖拽阈值的基础voidmousePressEvent(QMouseEvent*event)override{if(event-button()Qt::LeftButton){m_dragStartPosevent-pos();}QWidget::mousePressEvent(event);}3.4 QMimeData拖放数据的载体QMimeData 是拖放系统中的数据容器它用 MIME 类型来标识数据格式。Qt 内置支持了几种常见的 MIME 类型你可以通过对应的便利函数直接访问hasText()/text()处理纯文本、hasUrls()/urls()处理文件路径列表、hasHtml()/html()处理 HTML 富文本、hasImage()/imageData()处理图片数据。处理纯文本拖放是最简单的场景比如我们把一段文字从一个 Widget 拖到另一个 Widget// 发送端QMimeData*mimeDatanewQMimeData;mimeData-setText(Hello from drag source!);drag-setMimeData(mimeData);// 接收端voiddropEvent(QDropEvent*event)override{if(event-mimeData()-hasText()){QString receivedevent-mimeData()-text();// 处理接收到的文本...}}文件拖放稍微特殊一些。当用户从系统文件管理器拖入文件时MIME 类型是text/uri-listQt 把它封装成了QListQUrl。你需要在dragEnterEvent里检查hasUrls()然后在dropEvent里通过toLocalFile()把 URL 转成本地路径voiddragEnterEvent(QDragEnterEvent*event)override{if(event-mimeData()-hasUrls()){event-acceptProposedAction();}}voiddropEvent(QDropEvent*event)override{QListQUrlurlsevent-mimeData()-urls();for(constQUrlurl:urls){if(url.isLocalFile()){QString filePathurl.toLocalFile();qDebug()收到文件:filePath;}}event-acceptProposedAction();}当你需要传递自定义数据时可以使用setData(const QString mimeType, const QByteArray data)来设置任意 MIME 类型的数据。比如你想传递一个自定义的应用内数据格式// 发送端自定义 MIME 类型QMimeData*mimeDatanewQMimeData;mimeData-setData(application/x-myapp-task,taskData.toByteArray());drag-setMimeData(mimeData);// 接收端检查自定义 MIME 类型voiddragEnterEvent(QDragEnterEvent*event)override{if(event-mimeData()-hasFormat(application/x-myapp-task)){event-acceptProposedAction();}}按照惯例自定义 MIME 类型应该使用application/x-前缀加上你的应用名和具体数据类型这样不会和标准 MIME 类型冲突。到这里你可以停下来想一想一次完整的拖放操作中发送端和接收端各自需要做什么QMimeData 在两者之间扮演什么角色把这三件事——启用接收、处理事件、携带数据——在脑子里串成一条链路后面的实战练习就会顺很多。4. 踩坑预防第一个坑是忘了调setAcceptDrops(true)。这个太常见了你把dragEnterEvent和dropEvent都重写好了拖拽的时候就是不触发调试半天才发现构造函数里少了这一行。Qt 的设计是如果一个 Widget 没有声明自己接受拖放系统就不会把拖放事件分发给它你的重写函数根本不会被调用。第二个坑是QDrag::exec()的阻塞行为。前面说了这个函数会启动一个嵌套事件循环。这意味着在你的exec()返回之前外层函数不会继续往下走。如果你把它放在一个不合适的时机调用——比如在一个正在执行的动画回调里——可能会产生意料之外的递归事件处理。绝大多数简单场景下这不是问题但如果你有复杂的事件逻辑这一点需要留意。第三个坑跟文件拖放有关从文件管理器拖入的 URL 是用QUrl表示的而不是直接的文件路径字符串。如果你直接用event-mimeData()-text()去读取文件路径大多数情况下你会得到一个空字符串或者一堆 URL 编码后的路径。正确的做法是用urls()获取QUrl列表然后对每个 URL 调用toLocalFile()转成本地文件路径。第四个坑是拖拽阈值。如果你直接在mousePressEvent里发起QDrag用户每次点击都会触发拖拽——连正常的点击选中都做不到。标准做法是在mouseMoveEvent里判断移动距离是否超过QApplication::startDragDistance()通常 4 像素只有超过阈值才算真正的拖拽。接下来做一个练习假设你要实现一个简单的看板卡片可以从待办列拖到完成列。思考一下卡片 Widget 的发送端需要做什么列 Widget 的接收端需要检查什么拖放的数据应该用什么格式携带5. 练习项目我们来做一个实战练习创建一个包含两个文本框的窗口支持从一个文本框拖拽文字到另一个文本框。这听起来简单但它涵盖了拖放系统的完整流程——发起拖拽、携带数据、接收判断、提取数据。完成标准是创建一个DragSourceWidget作为拖拽源继承 QWidget在构造函数中设置一段默认文本并显示支持鼠标拖拽发起操作拖拽时携带文本数据创建一个DropTargetWidget作为拖放目标同样继承 QWidget构造函数中调用setAcceptDrops(true)重写dragEnterEvent接受文本类型的拖放重写dropEvent提取文本并显示在 Widget 上主窗口把这两个 Widget 水平排列中间用标签标注拖拽源和接收目标。几个提示DragSourceWidget需要重写mousePressEvent和mouseMoveEvent前者记录起始位置后者判断阈值并创建QDragDropTargetWidget接收到文本后可以用update()触发paintEvent把文本画出来也可以用 QLabel 显示拖拽源的文本可以用半透明的QPixmap作为拖拽预览图视觉效果会好很多。6. 官方文档参考链接Qt 文档 · Drag and Drop – Qt 拖放系统的完整概述包含拖放操作的完整流程和各平台注意事项Qt 文档 · QDrag Class – 拖放操作发起端的 API 文档包含 exec()、setMimeData()、setPixmap() 等方法说明Qt 文档 · QMimeData Class – MIME 数据容器的完整文档涵盖内置类型便利函数和自定义 MIME 类型的使用Qt 文档 · QDragEnterEvent – 拖拽进入事件文档解释 acceptProposedAction() 的行为Qt 文档 · QDropEvent – 放下事件文档包含 drop action 和 MIME 数据提取的详细说明到这里QtGui 部分的五篇基础教程就全部完成了。拖放系统是 GUI 编程中非常实用的交互模式掌握了接收端和发送端两套流程、理解了 QMimeData 作为数据载体的设计后面不管遇到什么样的拖放需求你都有能力把它拆解成这几个标准步骤来实现。接下来的 QtWidgets 篇章我们会进入布局系统和事件处理那才是真正构建复杂界面的开始。相关阅读现代Qt开发教程新手篇1.15——正则与文本处理 - 相似度 71%通用GUI编程技术——Win32 原生编程实战五十四——Hook 机制 - 相似度 71%通用GUI编程技术——图形渲染实战四十四——D3D12命令列表、队列与围栏GPU同步核心 - 相似度 71%
现代Qt开发教程(新手篇)2.5——拖放系统基础
现代Qt开发教程新手篇2.5——拖放系统基础相关仓库仍然已经开源正在积极火热的建设之中欢迎各位大佬提Issue和PR链接地址https://github.com/Awesome-Embedded-Learning-Studio/Tutorial_AwesomeQt1. 前言 / 为什么需要拖放说实话拖放这个功能在很多应用里看起来像是锦上添花但真正做项目的时候你会发现它几乎是刚需。想想看文件管理器里把文件从一个文件夹拖到另一个文件夹、图片编辑器里拖入一张图片直接打开、看板工具里拖动任务卡片改变状态——这些操作如果没有拖放支持用户体验会瞬间回到上个世纪。Qt 的拖放系统设计得相当完整但完整也意味着概念多。你需要理解的东西包括怎么让一个 Widget 接受拖入的数据、怎么从你的 Widget 发起一次拖放操作、怎么用 QMimeData 在拖放过程中携带各种格式的数据。这些东西拼在一起就是一套完整的拖放交互链路。OK咱们那就开始这次的学习吧2. 环境说明本篇代码适用于 Qt 6.5 版本CMake 3.26C17 或更高标准。拖放相关的类分布在 QtGuiQDrag、QMimeData和 QtWidgetsQWidget 的 dragEnterEvent 等两个模块中所以示例代码需要同时链接这两个模块。桌面平台上均可正常编译运行但需要注意的是 Qt 的拖放功能依赖于底层窗口系统的支持在无窗口系统的嵌入式环境下行为可能不同。3. 核心概念讲解3.1 启用拖放接收setAcceptDrops默认情况下QWidget 是不接受拖放操作的。如果你不做任何设置当用户把东西拖到你的 Widget 上方时光标会显示一个禁止图标表示这里不允许放下。要让一个 Widget 变成合法的拖放目标第一步就是在构造函数里调用setAcceptDrops(true)。classDropWidget:publicQWidget{Q_OBJECTpublic:explicitDropWidget(QWidget*parentnullptr):QWidget(parent){setAcceptDrops(true);// 这是第一步没有这一步后面全白搭setMinimumSize(300,200);}};这个设置的含义很直白告诉 Qt 的事件系统这个 Widget 对拖进来的东西感兴趣请把相关的拖放事件分发给我。如果你忘了这一步后面不管怎么重写dragEnterEvent和dropEvent它们都不会被调用——因为 Qt 在事件分发的早期阶段就帮你把这个事件忽略掉了。3.2 dragEnterEvent 和 dropEvent 处理拖放接收setAcceptDrops(true)只是开了门真正的逻辑需要我们通过重写两个事件处理函数来实现。第一个是dragEnterEvent(QDragEnterEvent *event)。这个事件在拖拽操作进入 Widget 区域时触发你需要在这里决定这个拖进来的东西我收不收判断的依据通常是检查拖放数据中包含的 MIME 类型。如果决定接受就调用event-acceptProposedAction()如果不接受什么都不做就行Qt 会自动显示禁止图标。voiddragEnterEvent(QDragEnterEvent*event)override{// 检查拖入的数据是否包含我们想要的格式if(event-mimeData()-hasText()){event-acceptProposedAction();// 告诉 Qt这个我要了}// 如果不调用 acceptProposedAction()Qt 会认为你拒绝了这次拖放}第二个是dropEvent(QDropEvent *event)。这个事件在用户松开鼠标、真正把东西放下时触发。到这一步说明dragEnterEvent已经放行了所以你可以放心地从event-mimeData()里提取数据。voiddropEvent(QDropEvent*event)override{QString textevent-mimeData()-text();qDebug()收到拖放文本:text;// 把文本存起来然后触发重绘或更新 UIm_droppedTexttext;update();event-acceptProposedAction();// 确认接收}你可能会注意到这两个函数里都调用了acceptProposedAction()。这个函数的作用是告诉 Qt我接受了这次拖放并且同意执行拖放源提议的动作复制、移动还是链接。如果你想在接收端改变动作类型可以用event-setDropAction(Qt::CopyAction)先覆盖再 accept。还有一个事件你可能偶尔会用到dragMoveEvent(QDragMoveEvent *event)。它在拖拽过程中鼠标在 Widget 内移动时持续触发。大多数简单场景你不需要重写它但在某些情况下——比如你想根据鼠标位置高亮显示某个区域——这个事件就派上用场了。voiddragMoveEvent(QDragMoveEvent*event)override{// 可以根据 event-pos() 判断鼠标在哪个区域// 用于实现拖放时的视觉反馈event-acceptProposedAction();}3.3 用 QDrag 发起拖放操作上面讲的是接收端的逻辑现在我们看看发送端——怎么让你的 Widget 成为一个拖放源让用户可以从这里拖走数据。发起拖放的标准做法是在mousePressEvent或mouseMoveEvent中创建一个QDrag对象。通常我们会选择在mouseMoveEvent里做这件事因为这样可以加入一个移动阈值避免用户只是想点击结果触发了拖拽。voidmouseMoveEvent(QMouseEvent*event)override{// 检查移动距离是否超过了拖拽阈值if(!(event-buttons()Qt::LeftButton)){return;}if((event-pos()-m_dragStartPos).manhattanLength()QApplication::startDragDistance()){return;// 移动距离太短不算拖拽}// 创建拖放对象QDrag*dragnewQDrag(this);// 创建 MIME 数据并设置内容QMimeData*mimeDatanewQMimeData;mimeData-setText(m_textToDrag);drag-setMimeData(mimeData);// 可选设置拖拽时跟随鼠标的缩略图// drag-setPixmap(QPixmap::fromImage(someImage));// 执行拖放操作这个调用会阻塞直到拖放结束Qt::DropAction resultdrag-exec(Qt::CopyAction|Qt::MoveAction);qDebug()拖放结果:result;// result 是 Qt::CopyAction / Qt::MoveAction / Qt::IgnoreAction 之一}这里有几个要点需要理清。首先QDrag和QMimeData的父子关系是通过setMimeData自动建立的——QMimeData的所有权会转移给QDrag所以你不需要手动 delete。QDrag::exec()是一个阻塞调用它会启动一个局部的嵌套事件循环来处理整个拖拽过程直到用户松开鼠标或取消操作才返回。返回值告诉你最终执行了什么动作。另外别忘了在mousePressEvent里记录起始位置这是判断拖拽阈值的基础voidmousePressEvent(QMouseEvent*event)override{if(event-button()Qt::LeftButton){m_dragStartPosevent-pos();}QWidget::mousePressEvent(event);}3.4 QMimeData拖放数据的载体QMimeData 是拖放系统中的数据容器它用 MIME 类型来标识数据格式。Qt 内置支持了几种常见的 MIME 类型你可以通过对应的便利函数直接访问hasText()/text()处理纯文本、hasUrls()/urls()处理文件路径列表、hasHtml()/html()处理 HTML 富文本、hasImage()/imageData()处理图片数据。处理纯文本拖放是最简单的场景比如我们把一段文字从一个 Widget 拖到另一个 Widget// 发送端QMimeData*mimeDatanewQMimeData;mimeData-setText(Hello from drag source!);drag-setMimeData(mimeData);// 接收端voiddropEvent(QDropEvent*event)override{if(event-mimeData()-hasText()){QString receivedevent-mimeData()-text();// 处理接收到的文本...}}文件拖放稍微特殊一些。当用户从系统文件管理器拖入文件时MIME 类型是text/uri-listQt 把它封装成了QListQUrl。你需要在dragEnterEvent里检查hasUrls()然后在dropEvent里通过toLocalFile()把 URL 转成本地路径voiddragEnterEvent(QDragEnterEvent*event)override{if(event-mimeData()-hasUrls()){event-acceptProposedAction();}}voiddropEvent(QDropEvent*event)override{QListQUrlurlsevent-mimeData()-urls();for(constQUrlurl:urls){if(url.isLocalFile()){QString filePathurl.toLocalFile();qDebug()收到文件:filePath;}}event-acceptProposedAction();}当你需要传递自定义数据时可以使用setData(const QString mimeType, const QByteArray data)来设置任意 MIME 类型的数据。比如你想传递一个自定义的应用内数据格式// 发送端自定义 MIME 类型QMimeData*mimeDatanewQMimeData;mimeData-setData(application/x-myapp-task,taskData.toByteArray());drag-setMimeData(mimeData);// 接收端检查自定义 MIME 类型voiddragEnterEvent(QDragEnterEvent*event)override{if(event-mimeData()-hasFormat(application/x-myapp-task)){event-acceptProposedAction();}}按照惯例自定义 MIME 类型应该使用application/x-前缀加上你的应用名和具体数据类型这样不会和标准 MIME 类型冲突。到这里你可以停下来想一想一次完整的拖放操作中发送端和接收端各自需要做什么QMimeData 在两者之间扮演什么角色把这三件事——启用接收、处理事件、携带数据——在脑子里串成一条链路后面的实战练习就会顺很多。4. 踩坑预防第一个坑是忘了调setAcceptDrops(true)。这个太常见了你把dragEnterEvent和dropEvent都重写好了拖拽的时候就是不触发调试半天才发现构造函数里少了这一行。Qt 的设计是如果一个 Widget 没有声明自己接受拖放系统就不会把拖放事件分发给它你的重写函数根本不会被调用。第二个坑是QDrag::exec()的阻塞行为。前面说了这个函数会启动一个嵌套事件循环。这意味着在你的exec()返回之前外层函数不会继续往下走。如果你把它放在一个不合适的时机调用——比如在一个正在执行的动画回调里——可能会产生意料之外的递归事件处理。绝大多数简单场景下这不是问题但如果你有复杂的事件逻辑这一点需要留意。第三个坑跟文件拖放有关从文件管理器拖入的 URL 是用QUrl表示的而不是直接的文件路径字符串。如果你直接用event-mimeData()-text()去读取文件路径大多数情况下你会得到一个空字符串或者一堆 URL 编码后的路径。正确的做法是用urls()获取QUrl列表然后对每个 URL 调用toLocalFile()转成本地文件路径。第四个坑是拖拽阈值。如果你直接在mousePressEvent里发起QDrag用户每次点击都会触发拖拽——连正常的点击选中都做不到。标准做法是在mouseMoveEvent里判断移动距离是否超过QApplication::startDragDistance()通常 4 像素只有超过阈值才算真正的拖拽。接下来做一个练习假设你要实现一个简单的看板卡片可以从待办列拖到完成列。思考一下卡片 Widget 的发送端需要做什么列 Widget 的接收端需要检查什么拖放的数据应该用什么格式携带5. 练习项目我们来做一个实战练习创建一个包含两个文本框的窗口支持从一个文本框拖拽文字到另一个文本框。这听起来简单但它涵盖了拖放系统的完整流程——发起拖拽、携带数据、接收判断、提取数据。完成标准是创建一个DragSourceWidget作为拖拽源继承 QWidget在构造函数中设置一段默认文本并显示支持鼠标拖拽发起操作拖拽时携带文本数据创建一个DropTargetWidget作为拖放目标同样继承 QWidget构造函数中调用setAcceptDrops(true)重写dragEnterEvent接受文本类型的拖放重写dropEvent提取文本并显示在 Widget 上主窗口把这两个 Widget 水平排列中间用标签标注拖拽源和接收目标。几个提示DragSourceWidget需要重写mousePressEvent和mouseMoveEvent前者记录起始位置后者判断阈值并创建QDragDropTargetWidget接收到文本后可以用update()触发paintEvent把文本画出来也可以用 QLabel 显示拖拽源的文本可以用半透明的QPixmap作为拖拽预览图视觉效果会好很多。6. 官方文档参考链接Qt 文档 · Drag and Drop – Qt 拖放系统的完整概述包含拖放操作的完整流程和各平台注意事项Qt 文档 · QDrag Class – 拖放操作发起端的 API 文档包含 exec()、setMimeData()、setPixmap() 等方法说明Qt 文档 · QMimeData Class – MIME 数据容器的完整文档涵盖内置类型便利函数和自定义 MIME 类型的使用Qt 文档 · QDragEnterEvent – 拖拽进入事件文档解释 acceptProposedAction() 的行为Qt 文档 · QDropEvent – 放下事件文档包含 drop action 和 MIME 数据提取的详细说明到这里QtGui 部分的五篇基础教程就全部完成了。拖放系统是 GUI 编程中非常实用的交互模式掌握了接收端和发送端两套流程、理解了 QMimeData 作为数据载体的设计后面不管遇到什么样的拖放需求你都有能力把它拆解成这几个标准步骤来实现。接下来的 QtWidgets 篇章我们会进入布局系统和事件处理那才是真正构建复杂界面的开始。相关阅读现代Qt开发教程新手篇1.15——正则与文本处理 - 相似度 71%通用GUI编程技术——Win32 原生编程实战五十四——Hook 机制 - 相似度 71%通用GUI编程技术——图形渲染实战四十四——D3D12命令列表、队列与围栏GPU同步核心 - 相似度 71%