个人实现的QT拼图游戏(开源),QT拖拽事件详解
文章目录
- 效果图
- 引言
- 玩法
- 拖拽概念
- 基本概念
- 如何在Qt中使用拖放
- 注意事项
- 游戏关键问题
- 总结
效果图
。
- 闯关模式:在规定的时间内完成拼图,共有四关,有三种难度,每种难度所需的时间不一致。
- 休闲模式:玩家可以自定义图片与难度,没有时间限制。
拖拽概念
基本概念
- 在Qt中,拖放(Drag and Drop)是一种非常直观的方式来处理对象的移动或复制。拖放可以在单个应用程序内进行,也可以在不同应用程序之间进行。Qt为此提供了一组丰富的API来支持拖放操作。
- 拖动 (Drag)
- 开始一个拖动操作,通常是当用户在一个可拖动的组件上按下鼠标按钮,并移动一定距离时。在Qt中,你需要创建一个
QDrag
对象,并指定要拖动的数据。
- 放下 (Drop)
- 放下操作发生在拖动过程的最后,当用户释放鼠标按钮时。如果释放位置是一个可以接受放下的组件(一个设置为接受放下的
QWidget
或者QGraphicsItem
),那么会发生放下操作。
- MIME 数据
- 拖动和放下的数据是通过MIME(Multipurpose Internet Mail Extensions)类型封装的。在Qt中通常使用
QMimeData
对象来处理拖放的数据。
如何在Qt中使用拖放
- 启用组件的拖放
- 首先,确保你的QWidget派生类允许拖放。使用
setDragEnabled(true)
可以使得组件可以被拖动,使用setAcceptDrops(true)
使得组件可以接收放下。
- 处理拖动事件
- 在源组件中,你需要重写
mousePressEvent
和mouseMoveEvent
。这些本是处理鼠标事件的函数在此也被用来发起拖动。在鼠标移动事件中,你可以使用QDrag
来开始拖动操作,并将QMimeData
附加到QDrag
对象。
void SourceWidget::mouseMoveEvent(QMouseEvent *event) {if (!(event->buttons() & Qt::LeftButton)) {return;}QDrag *drag = new QDrag(this);QMimeData *mimeData = new QMimeData;// 设置数据 mimeData->setData(...) 或 mimeData->setText(...)drag->setMimeData(mimeData);// 开始拖动操作Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
}
- 处理放下事件
- 在目标组件中,你需要重写几个事件处理函数以处理放下事件:
dragEnterEvent
、dragMoveEvent
(可选)和dropEvent
。通过这些事件,你可以确定是否接受拖动进来的数据,以及如何处理这些数据。
void TargetWidget::dragEnterEvent(QDragEnterEvent *event) {if (event->mimeData()->hasFormat("custom/format")) {event->acceptProposedAction();}
}
void TargetWidget::dropEvent(QDropEvent *event) {const QMimeData *mimeData = event->mimeData();// 处理放下的数据 mimeData->data(...) 或 mimeData->text()event->acceptProposedAction();
}
注意事项
- 你也许会需要处理
dragLeaveEvent
,用来处理拖动物体离开组件时的事件。 - 拖放事件与标准的鼠标事件是相互独立的,在处理拖放事件时不会影响鼠标事件的处理。
- 拖放操作可以包括图片、文本、HTML等多种数据类型,基本上任何种类的数据都可以通过MIME数据进行传输。
- 要实现跨不同应用程序的拖放,需要确保所有参与的应用程序都能理解相关的MIME类型。
游戏关键问题
- 游戏的总体结构是怎么样的
- 界面主要由俩块组成,左边为一个
QListView
设置了继承于QAbstractListModel
的代理模型,右边为一个QWidget
。 - 游戏维护了一个全局的结构体指针中,该结构体用于保存游戏的信息,如模式,难度,当前关卡等信息。
- 游戏实现的主要难点就是拖拽的实现
- 如何将一张图片分割为指定的x*x的图片
// 计算新的图像大小,取原始图片宽高的最小值作为新的尺寸sizeint size = qMin(pixmap.width(), pixmap.height());// 从原始图片中剪切出一个大小为 sizexsize 的部分作为新的puzzleImage,// 重新调整新的puzzleImage大小为puzzleWidget的imageSize,使用Qt::SmoothTransformation平滑缩放pixmap = pixmap.copy((pixmap.width() - size) / 2, (pixmap.height() - size) / 2, size, size).scaled(puzzleWidget->imageSize(), puzzleWidget->imageSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);// 制作每一片拼图片段。m_PieceSize图片大小,m行列数for (int y = 0; y < m; ++y){for (int x = 0; x < m; ++x){QPixmap pieceImage = pixmap.copy(x * m_PieceSize, y * m_PieceSize, m_PieceSize, m_PieceSize);addPiece(pieceImage, QPoint(x, y));}}
- 如何判断拼图是否完成
- 在切割图片的时候,我们已经将将图片正确位置存放到图片中,只需要全局维护一个计数器,当计数器等于拼图数量时,即是完成。
// 图片资源结构体struct Piece{QPixmap pixmap;QRect rect;QPoint location;Piece() {}Piece(QPixmap Vpixmap, QPoint Vlocation, QRect Vrect) : pixmap(Vpixmap), location(Vlocation), rect(Vrect) {}Piece(const Piece &other){pixmap = other.pixmap;rect = other.rect;location = other.location;}};
- 计数器的增加规则是:若是当前图片所有在矩形与存放的位置相同,计数器+1
void PuzzleWidget::addInPlace(Piece piece)
{if (piece.location == piece.rect.topLeft() / pieceSize()){inPlace++;if (inPlace == MacroDf::getCloum() * MacroDf::getCloum())emit puzzleCompleted();}
}
- 图片是如何出现在widget上的
- 通过绘制实现,
pieces
存放的是保存的图片结构体列表,highlightedRect
为高亮区域。
void PuzzleWidget::paintEvent(QPaintEvent *event)
{QPainter painter(this);painter.fillRect(event->rect(), Qt::white);if (highlightedRect.isValid()){painter.setBrush(QColor("#98FB98"));painter.setPen(Qt::NoPen);painter.drawRect(highlightedRect.adjusted(0, 0, -1, -1));}for (const Piece &piece : pieces){painter.drawPixmap(piece.rect, piece.pixmap);}
}
- widget窗口上图片是如何拖动的
- 在鼠标点击事件中,先判断当前点击的位置是否存在图片,若是有就去存好的图片链表中获取该图片的资源,创建拖动操作的数据对象
void PuzzleWidget::mousePressEvent(QMouseEvent *event)
{// 获取鼠标点击位置的方块QRect square = targetSquareMove(event->pos());// 查找方块是否有图片int found = findPiece(square);if (found == -1)return;// 移除找到的拼图块Piece piece = pieces.takeAt(found);// 如果拼图块的位置与方块的顶点位置一致,表示该拼图块为正确位置,移除时更新完成计数位if (piece.location == square.topLeft() / pieceSize())inPlace--;update(square);// 将拼图块的图像和位置信息存入数据流QByteArray itemData;QDataStream dataStream(&itemData, QIODevice::WriteOnly);dataStream << piece.pixmap << piece.location << piece.rect;// 创建拖动操作的数据对象QMimeData *mimeData = new QMimeData;mimeData->setData("DJ-NB", itemData);// 创建拖动操作QDrag *drag = new QDrag(this);drag->setMimeData(mimeData);drag->setHotSpot(event->pos() - square.topLeft());drag->setPixmap(piece.pixmap);// 判断拖动操作的结果是否为Qt::IgnoreAction,表示拖拽失败,将拼图块放回原位置if (drag->exec(Qt::MoveAction) == Qt::IgnoreAction) // 拖放到其他应用程序。我们使用Qt::IgnoreAction来限制它。{pieces.insert(found, piece);update(targetSquareMove(event->pos()));if (piece.location == QPoint(square.x() / pieceSize(), square.y() / pieceSize()))inPlace++;}
}
- 图片是如何拖入widget以及交换图片的
- 在
dropEvent
事件中,先检查数据格式是否正确,再判断当前要放入的位置是否存在图片,不存在图片直接加入到列表中就行,若是存在,则需要交换俩个图片的信息,同时要判断计数位。
void PuzzleWidget::dropEvent(QDropEvent *event)
{// 检查事件是否含有我们需要的数据格式if (event->mimeData()->hasFormat("DJ-NB")){// 接受事件默认的复制动作event->setDropAction(Qt::MoveAction);event->accept();auto square = targetSquareMove(event->pos()); // 目标位置int existingPieceIndex = findPiece(square); // 寻找目标位置是否有拼图块// 从拖放事件的数据中读取拼图块的信息QByteArray pieceData = event->mimeData()->data("DJ-NB");QDataStream dataStream(&pieceData, QIODevice::ReadOnly);// 将拼图块添加到列表中或与现有拼图块交换if (existingPieceIndex == -1){// 目标位置没有拼图块,直接放置新拼图块Piece piece;piece.rect = targetSquareMove(event->pos());dataStream >> piece.pixmap >> piece.location;// 将拼图块添加到列表中pieces.append(piece);// 清除高亮的区域并更新拼图块的区域highlightedRect = QRect();update(piece.rect);// 如果拼图块放置在正确的位置addInPlace(piece);}else{// 目标位置已有拼图块,和拖入的拼图块互换位置// 起始位置资源Piece piece;dataStream >> piece.pixmap >> piece.location >> piece.rect;// 目标位置资源Piece rPic = pieces[existingPieceIndex];// 删除掉原有的,以便重新写入新值if (rPic.location == rPic.rect.topLeft() / pieceSize())inPlace--;pieces.takeAt(existingPieceIndex);// 数据交互Piece tempPiece = piece;piece.location = rPic.location;piece.pixmap = rPic.pixmap;rPic.location = tempPiece.location;rPic.pixmap = tempPiece.pixmap;// 存放俩组数据pieces.append(piece);pieces.append(rPic);// 重绘涉及的区域highlightedRect = QRect();update(piece.rect);update(rPic.rect);// 如果拼图块放置在正确的位pieceaddInPlace(rPic);addInPlace(piece);}}else{highlightedRect = QRect();// 不是我们支持的数据格式,保留默认行为event->ignore();}
}
- list以拖入widget中的图片如何删除,更新链表视图的
- 在继承与
QAbstractListModel
的代理中的removeRows
函数实现
bool PiecesModel::removeRows(int row, int count, const QModelIndex &parent)
{if (parent.isValid())return false;if (row >= piece.size() || row + count <= 0)return false;// 修剪beginRow和endRow,限制在有效范围内。int beginRow = qMax(0, row);int endRow = qMin(row + count - 1, piece.size() - 1);// 调用beginRemoveRows()告知视图将移除行,开始行beginRow和结束行endRow。beginRemoveRows(parent, beginRow, endRow);// 循环移除while (beginRow <= endRow){piece.removeAt(beginRow);++beginRow;}// 调用endRemoveRows()告知视图完成移除行。endRemoveRows();return true;
}
- 如何将
widget
拖回list
中
- 上述中我们在
widget
的点击事件中直接创建了拖拽数据,那么我们只需要在list
的dropMimeData
实现存放的逻辑就行
bool PiecesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{// 检查mime数据是否包含正确的格式:"DJ-NB"if (!data->hasFormat("DJ-NB"))return false;// 检查拖放操作:if (action == Qt::IgnoreAction)return true;// 只允许插入第一列:if (column > 0)return false;// 判断插入行的尾部位置endRow:int endRow;// 如果是根节点:if (!parent.isValid()){if (row < 0)endRow = piece.size();elseendRow = qMin(row, piece.size());}else // 如果是子节点:{endRow = parent.row();}// 解析mime数据,读取 pixmap 图片和位置 location:QByteArray encodedData = data->data("DJ-NB");QDataStream stream(&encodedData, QIODevice::ReadOnly);// 通过 begin/endInsertRows函数更新模型,插入数据:while (!stream.atEnd()){QPixmap pixmap;QPoint location;QRect rect;// 从数据流中读数据stream >> pixmap >> location >> rect;Piece pie(pixmap, location, rect);// 若是以存在则返回不加入for (auto point : piece){if (point.location == location){return false;}}beginInsertRows(QModelIndex(), endRow, endRow);piece.insert(endRow, pie);endInsertRows();++endRow;}return true;
}
widget
中如何判断当前位置,以及图片中的矩形数据怎么存放
- 通过鼠标的点击获取的点,得到以图片为大小的当前位置左上角坐标,矩形大小也是每张图片的大小
const QRect PuzzleWidget::targetSquareMove(const QPoint &position) const
{// point除以一个数是往前进位,这会导致坐标出现问题,所以要用Int处理int x = position.x() / pieceSize();int y = position.y() / pieceSize();auto pointNew = QPoint(x, y);auto point = pointNew * pieceSize();auto resultRect = QRect(point.x(), point.y(), pieceSize(), pieceSize());return resultRect;
}
总结
- 这个游戏的用了周末俩天时间做完,后面用了一天修了点BUG,细节还是很多的,像计时器如何使用,富文本内容如何显示,弹窗的事件处理等,主要还是用于理解拖拽事件,当然你也可以直接去看QT 的demo,那个没我这么复杂,搜drag就行,不过它那个有几个明显的问题,我这都优化了。
- 知识理应共享,大家相互学习,源码在此哦。
相关文章:

个人实现的QT拼图游戏(开源),QT拖拽事件详解
文章目录 效果图引言玩法 拖拽概念基本概念如何在Qt中使用拖放注意事项 游戏关键问题总结 效果图 
gin渲染篇
1. 各种数据格式的响应 json、结构体、XML、YAML类似于java的properties、ProtoBuf package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample" )// 多种响应方式 func main() {// 1.创建路由// 默认使用了2个中…...

第三方控价服务商怎么选
用对了方法,事半功倍,品牌控价也是如此,品牌方在治理工作中,如果选择自建团队进行处理,需要包含对数据技术的抓取团队,还要有对治理规则熟悉的操作团队,涉及人员和系统,费用成本相应…...

大模型的学习路线图推荐—多维度深度分析【云驻共创】
🐲本文背景 近年来,随着深度学习技术的迅猛发展,大模型已经成为学术界和工业界的热门话题。大模型具有数亿到数十亿的参数,这使得它们在处理复杂任务时表现得更为出色,但同时也对计算资源和数据量提出了更高的要求。 …...

【学习】focal loss 损失函数
focal loss用于解决正负样本的不均衡情况 通常我们需要预测的正样本要少于负样本,正负样本分布不均衡会带来什么影响?主要是两个方面。 样本不均衡的话,训练是低效不充分的。因为困难的正样本数量较少,大部分时间都在学习没有用…...

几个好玩好用的AI站点
本文作者系360奇舞团前端开发工程师 ai能力在去年一年飞速增长,各种AI产品如雨后春笋般冒出来,在各种垂直领域上似乎都有AI的身影出现,今天就总结几款好玩的场景,看大家工作生活中是否会用到。 先说一个比较重要的消息是ÿ…...

Java算法 leetcode简单刷题记录5
Java算法 leetcode简单刷题记录5 老人的数目: https://leetcode.cn/problems/number-of-senior-citizens/ substring(a,b) 前闭后开 统计能整除数字的位数: https://leetcode.cn/problems/count-the-digits-that-divide-a-number/ 并不复杂,…...

计算机网络自顶向下Wireshark labs1-Intro
Wireshark labs1 实验文档:http://www-net.cs.umass.edu/wireshark-labs/Wireshark_Intro_v8.0.pdf 介绍 加深对网络协议的理解通常可以通过观察协议的运行和不断调试协议来大大加深,具体而言,就是观察两个协议实体之间交换的报文序列&…...

CSS实现图片放大缩小的几种方法
参考 方法一: 常用使用img标签,制定width或者height的任意一个,图片会自动等比例缩小 <div><img src"https://avatar.csdn.net/8/5/D/1_u012941315.jpg"/> </div> <!-- CSS--> <style> img {widt…...

时间序列预测 — CNN-LSTM-Attention实现多变量负荷预测(Tensorflow):多变量滚动
专栏链接:https://blog.csdn.net/qq_41921826/category_12495091.html 专栏内容 所有文章提供源代码、数据集、效果可视化 文章多次上领域内容榜、每日必看榜单、全站综合热榜 时间序列预测存在的问题 现有的大量方法没有真正的预测未…...

angular-tree-component组件中实现特定节点自动展开
核心API 都在 expandToNode这个函数中 HTML treeData的数据结构大概如下 [{"key": "3293040275","id": "law_category/3293040275","name": "嘿嘿嘿嘿","rank": 0,"parentKey": "0&q…...

Linux系统下安装Vcpkg,并使用Vcpkg安装、编译OpenSceneGraph
环境:CentOS7 内存:8g(内存过少编译osg时会出现内存不足导致编译失败的情况,内存设置为4G时失败了,我直接加到了8g,所以就以8g为准了) 安装和配置vcpkg cd ~/ git clone https://www.github.com/microsoft/vcpkg cd …...

设计模式二(工厂模式)
本质:实例化对象不用new,用工厂代替,实现了创建者和调用者分离 满足: 开闭原则:对拓展开放,对修改关闭 依赖倒置原则:要针对接口编程 迪米特原则:最少了解原则,只与自己直…...

Maven应用手册
没加载出来就reimport,这个时候clean和install没用,那是编译安装项目的。 reimport干了什么? 结合idea的maven教程 父子模块 子模块不需要groupId ruoyi中父模块还添加了子模块的依赖,,, 先安装父再是子…...

笨蛋学设计模式行为型模式-状态模式【20】
行为型模式-状态模式 8.7状态模式8.7.1概念8.7.2场景8.7.3优势 / 劣势8.7.4状态模式可分为8.7.5状态模式8.7.6实战8.7.6.1题目描述8.7.6.2输入描述8.7.6.3输出描述8.7.6.4代码 8.7.7总结 8.7状态模式 8.7.1概念 状态模式是指对象在运行时可以根据内部状态的不同而改变它们…...

C++从零开始的打怪升级之路(day18)
这是关于一个普通双非本科大一学生的C的学习记录贴 在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料 那么开启正题 今天分享的是关于vector的题目 1.只出现一次的数字1 136. 只出…...

浅谈安科瑞直流电表在新加坡光伏系统中的应用
摘要:本文介绍了安科瑞直流电表在新加坡光伏系统中的应用。主要用于光伏系统中的电流电压电能的计量,配合分流器对发电量进行计量。 Abstract: This article introduces the application of Acrel DC meters in PV system in Indonesia.The device is …...

C++参悟:数值运算相关
数值运算相关 一、概述二、常用数学函数1. 基础运算1. 浮点值的绝对值( |x| )2. 浮点除法运算的余数3. 除法运算的有符号余数4. 除法运算的有符号余数和最后三个二进制位5. 混合的乘加运算6. 两个浮点值的较大者7. 两个浮点值的较小者8. 两个浮点值的正数…...

【Web前端开发基础】CSS的定位和装饰
CSS的定位和装饰 目录 CSS的定位和装饰一、学习目标二、文章内容2.1 定位2.1.1 定位的基本介绍2.1.2 定位的基本使用2.1.3 静态定位2.1.4 相对定位2.1.5 绝对定位2.1.6 子绝父相2.1.7 固定定位2.1.8元素的层级关系 2.2 装饰2.2.1 垂直对齐方式2.2.2 光标类型2.2.3 边框圆角2.2.…...

[pytorch入门] 3. torchvision中的transforms
torchvision中的transforms 是transforms.py工具箱,含有totensor、resize等工具 用于将特定格式的图片转换为想要的图片的结果,即用于图片变换 用法 在transforms中选择一个类创建对象,使用这个对象选择相应方法进行处理 能够选择的类 列…...

WINCC读写EXCEL-VBS
原创 RENHQ WINCC 关于VBS操作EXCEL的文档不管在论坛上还是在网上,相关的脚本已经很多,但是依然有很多人在问这个问题,于是把我以前在论坛上发的一个集合帖子的脚本拿来,重新开个帖子,如果再有人问的话,可…...

Python os模块
简介 Python的os模块是一个标准库模块,用于提供与操作系统相关的功能(相当于接口)。os模块允许Python程序与文件系统、目录结构、进程管理等操作系统级别的功能进行交互。 主要功能 文件和目录操作 创建、删除、重命名文件和目录…...

Elasticsearch:2023 年 Lucene 领域发生了什么?
作者:来自 Elastic Adrien Grand 2023 年刚刚结束,又是 Apache Lucene 开发活跃的一年。 让我们花点时间回顾一下去年的亮点。 社区 2023 年,有: 5 个次要版本(9.5、9.6、9.7、9.8 和 9.9),1 …...

Java算法 leetcode简单刷题记录4
Java算法 leetcode简单刷题记录4 买卖股票的最佳时机: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/ 笨办法: 记录当天的值及之后的最大值,相减得到利润; 所有的天都计算下,比较得到利润最大值&…...

opencv#27模板匹配
图像模板匹配原理 例如给定一张图片,如上图大矩阵所示,然后给定一张模板图像,如上图小矩阵。 我们在大图像中去搜索与小图像中相同的部分或者是最为相似的内容。比如我们在图像中以灰色区域给出一个与模板图像尺寸大小一致的区域,…...

【论文阅读笔记】Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation
1.介绍 Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation Swin-Unet:用于医学图像分割的类Unet纯Transformer 2022年发表在 Computer Vision – ECCV 2022 Workshops Paper Code 2.摘要 在过去的几年里,卷积神经网络ÿ…...

IS-IS:01 ISIS基本配置
这是实验拓扑,下面是基本配置: R1: sys sysname R1 user-interface console 0 idle-timeout 0 0 int loop 0 ip add 1.1.1.1 24 int g0/0/0 ip add 192.168.12.1 24 qR2: sys sysname R2 user-interface console 0 idle-timeout 0 0 int loop 0 ip add …...

基于极限学习机的曲线分类,基于极限学习机的光谱分类,基于极限学习机的分类预测
目录 背影 极限学习机 基于极限学习机的曲线分类,基于极限学习机的光谱分类,基于极限学习机的分类预测 主要参数 MATLAB代码 效果图 结果分析 展望 完整代码下载链接:基于极限学习机的曲线分类,基于极限学习机的光谱分类,基于极限学习机的分类预测的MATLAB代码资源-CSDN…...

miniconda安装
Miniconda是一个小型版的Anaconda,它包含了一个包管理工具conda和Python。Miniconda适用于那些只需要管理Python包和环境,而不需要Anaconda中包含的大部分科学计算工具的用户。 Miniconda的安装过程相对简单。你可以从清华大学开源软件镜像站下载Minico…...

PHP判断访客是否手机端(移动端浏览器)访问的方法总结
PHP判断访客是否手机端、移动端、浏览器访问的方法总结 方法一:使用$_SERVER全局变量方法二:使用PHP的get_browser函数方法三:使用第三方库(Mobile Detect)方法四:使用WURFL设备数据库 方法一:使…...