个人实现的QT拼图游戏(开源),QT拖拽事件详解

文章目录

      • 效果图
      • 引言
        • 玩法
      • 拖拽概念
        • 基本概念
        • 如何在Qt中使用拖放
        • 注意事项
      • 游戏关键问题
      • 总结

效果图

请添加图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c6dd66befd314442adf07e1dec0d550c.png
在这里插入图片描述
在这里插入图片描述

引言

  • 在学习QT demo时,发现有一个拼图demo,介绍拖拽事件的。以此为蓝本加了亿点修饰,就诞生了这个游戏。
玩法
  • 游戏为拼图游戏,分为俩种模式(闯关与休闲)。
  • 闯关模式:在规定的时间内完成拼图,共有四关,有三种难度,每种难度所需的时间不一致。
  • 休闲模式:玩家可以自定义图片与难度,没有时间限制。

拖拽概念

基本概念
  • 在Qt中,拖放(Drag and Drop)是一种非常直观的方式来处理对象的移动或复制。拖放可以在单个应用程序内进行,也可以在不同应用程序之间进行。Qt为此提供了一组丰富的API来支持拖放操作。
  1. 拖动 (Drag)
  • 开始一个拖动操作,通常是当用户在一个可拖动的组件上按下鼠标按钮,并移动一定距离时。在Qt中,你需要创建一个QDrag对象,并指定要拖动的数据。
  1. 放下 (Drop)
  • 放下操作发生在拖动过程的最后,当用户释放鼠标按钮时。如果释放位置是一个可以接受放下的组件(一个设置为接受放下的QWidget或者QGraphicsItem),那么会发生放下操作。
  1. MIME 数据
  • 拖动和放下的数据是通过MIME(Multipurpose Internet Mail Extensions)类型封装的。在Qt中通常使用QMimeData对象来处理拖放的数据。
如何在Qt中使用拖放
  1. 启用组件的拖放
  • 首先,确保你的QWidget派生类允许拖放。使用setDragEnabled(true)可以使得组件可以被拖动,使用setAcceptDrops(true)使得组件可以接收放下。
  1. 处理拖动事件
  • 在源组件中,你需要重写mousePressEventmouseMoveEvent。这些本是处理鼠标事件的函数在此也被用来发起拖动。在鼠标移动事件中,你可以使用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);
}
  1. 处理放下事件
  • 在目标组件中,你需要重写几个事件处理函数以处理放下事件:dragEnterEventdragMoveEvent(可选)和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
  • 游戏维护了一个全局的结构体指针中,该结构体用于保存游戏的信息,如模式,难度,当前关卡等信息。
  • 游戏实现的主要难点就是拖拽的实现
  1. 如何将一张图片分割为指定的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));}}
  1. 如何判断拼图是否完成
  • 在切割图片的时候,我们已经将将图片正确位置存放到图片中,只需要全局维护一个计数器,当计数器等于拼图数量时,即是完成。
    // 图片资源结构体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();}
}
  1. 图片是如何出现在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);}
}
  1. 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++;}
}
  1. 图片是如何拖入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();}
}
  1. 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;
}
  1. 如何将widget拖回list
  • 上述中我们在widget的点击事件中直接创建了拖拽数据,那么我们只需要在listdropMimeData实现存放的逻辑就行
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;
}
  1. 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就行,不过它那个有几个明显的问题,我这都优化了。
  • 知识理应共享,大家相互学习,源码在此哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/241641.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES框架详解

ES框架详解 1、全文检索的介绍 ​ 那么对于一般的公司&#xff0c;初期是没有那么多数据的&#xff0c;所以很多公司更倾向于使用传统的数据库&#xff1a;mysql&#xff1b;比如我们要查找关键字”传智播客“&#xff0c;那么查询的方式大概就是:select * from table where …

【AI接口】语音版、文心一言大模型和AI绘图、图片检测API

文章目录 一、语音版大模型AI1、接口2、请求参数3、请求参数示例4、接口返回示例 二、AI图片鉴黄合规检测API1、接口2、请求参数3、请求参数示例4、接口返回示例5、报错说明6、代码开源 三、人工智能AI绘画API1、接口2、请求参数3、请求参数示例4、接口返回示例5、AI绘画成果展…

RDMA编程实践-SEND-RECEICVE原语应用

RDMA编程实践 本文描述了RDMA编程过程中的SEND-RECEIVE双边原语的代码实现。包含多个版本&#xff0c;1、client向server发送消息&#xff0c;server回复client收到消息(ACK)&#xff0c;然后两边断开连接。2、server端循环等待客户端建立连接&#xff0c;client发送一次消息后…

Linux 【C编程】 引入线程,线程相关函数

1.线程的引入 1.1使用线程同时读取键盘和鼠标 代码演示&#xff1a; #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <termios.h> #include <fcntl.h> #include <string.h> // 读取…

鸿蒙HarmonyOS实战-ArkTS语言(基本语法)

&#x1f680;一、ArkTS语言基本语法 &#x1f50e;1.简介 HarmonyOS的ArkTS语言是一种基于TypeScript开发的语言&#xff0c;它专为HarmonyOS系统开发而设计。ArkTS语言结合了JavaScript的灵活性和TypeScript的严谨性&#xff0c;使得开发者能够快速、高效地开发出高质量的Har…

如何从命令行运行testng.xml?

目录 创建一个新的java项目并从命令行运行testng.xml 使用命令行运行XML文件 从命令行运行现有maven项目的XML文件 在这篇文章中&#xff0c;我们将使用命令行运行testng.xml。有多种场景需要使用命令行工具运行testng.xml。也许您已经创建了一个maven项目&#xff0c;现在想…

git提交代码到远端仓库的方法详解

一、何为git git就是版本控制器&#xff0c;就比如说你新建了一个git文件夹&#xff0c;里面用于存放你的C语言实习报告&#xff0c;现在要用git对该文件夹进行接管。当你修改了你的C语言实习报告点击保存之后&#xff0c;就用git的相关命令&#xff0c;提交给git&#xff0c;让…

Flask 3.x log全域配置(包含pytest)

最近使用到flask3.x&#xff0c;配置了全域的log&#xff0c;这边记录下 首先需要创建logging的配置文件&#xff0c;我是放在项目根目录的&#xff0c; Logging 配置 logging.json {"version": 1, # 配置文件版本号"formatters": {"default&qu…

git中合并分支时出现了代码冲突怎么办

目录 第一章、Git代码冲突介绍1.1&#xff09;什么是Git代码冲突①git merge命令介绍②代码冲突原因 1.2&#xff09;提示代码冲突的两种情况①本地不同分支的文件有差异时&#xff1a;②本地仓库和git远程仓库的文件有差异时&#xff1a; 1.3&#xff09;解决合并时的代码冲突…

音乐人声分离工具:极简的人声和背景音乐分离工具

这是一个极简的人声和背景音乐分离工具&#xff0c;本地化网页操作&#xff0c;无需连接外网&#xff0c;使用 2stems/4stems/5stems 模型。 将一首歌曲或者含有背景音乐的音视频文件&#xff0c;拖拽到本地网页中&#xff0c;即可将其中的人声和音乐声分离为单独的音频wav文件…

04 SpringBoot整合Druid/MyBatis/事务/AOP+打包项目

整合Druid 项目结构&#xff1a; 引入依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaL…

rust跟我学六:虚拟机检测

图为RUST吉祥物 大家好,我是get_local_info作者带剑书生,这里用一篇文章讲解get_local_info是怎么检测是否在虚拟机里运行的。 首先,先要了解get_local_info是什么? get_local_info是一个获取linux系统信息的rust三方库,并提供一些常用功能,目前版本0.2.4。详细介绍地址:…

00-Rust前言

问&#xff1a;为什么要近期想学习Rust? 答&#xff1a; Rust出来也是有一段时间了&#xff0c;从Microsoft吵着要重构他们的C"祖传代码"开始&#xff0c;Rust就披着“高效&#xff0c;安全”的头衔。而自己决定要学习Rust&#xff0c;是因为近期发现&#xff1a;与…

TDengine 企业级功能:存储引擎对多表低频场景优化工作分享

在去年 8 月份发布的 3.1.0.0 版本中&#xff0c;TDengine 进行了一系列重要的企业级功能更新&#xff0c;其中包括对多表低频场景写入性能的大幅优化。这一优化工作为有此需求的用户提供了更大的便捷性和易用性。在本文中&#xff0c;TDengine 的资深研发将对此次优化工作进行…

Java项目:10 Springboot的电商书城管理系统

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 该系统分为前台展示和后台管理两大模块&#xff0c;前台主要是为消费者服务。该子系统实现了注册&#xff0c;登录&#xff0c;以及从浏览、下…

强化学习(二)多臂老虎机 “Multi-armed Bandits”——1

将强化学习与机器学习、深度学习区分开的最重要的特征为&#xff1a;它通过训练中信息来评估所采取的动作&#xff0c;而不是给出正确的动作进行指导&#xff0c;这极大地促进了寻找更优动作的需求。 1、多臂老虎机&#xff08;Multi-armed Bandits&#xff09;问题 赌场的老虎…

JS遍历对象的方法及特点

1、定义一个对象 let obj {name: Tom,age: 20,sex: 男,};obj.weight 70kg;// obj的原型上定义属性Object.prototype.height 180cm;Object.prototype.major function() {console.log(专业&#xff1a;计算机应用技术);};console.log(obj, obj); 控制台输出的obj中&#xff…

c++:基于c语言基础上的语法不同(1)

前言&#xff1a;此篇文章适合学完c语言基础概念的同学&#xff0c;是帮助c向c语言的同学快速掌握基本语法。 基础格式 #include<iostream>using namespace std; int main() {system("pause");return 0; } 输入&#xff1a; cin>>a;//a是输入内容 输出…

最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作

详情点击链接&#xff1a;最新ChatGPT/GPT4科研应用与AI绘图及论文高效写作 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#xff0c;AI画图&#xff0c;图像识别&#xff0c;文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Clau…

如何手写一个RPC?

在学习 RPC 框架之前&#xff0c;我们先来手写一个RPC。 我们在学习的过程中&#xff0c;一定要做到知其然&#xff0c;还要知其所以然。 架构演进 单体架构 要知道&#xff0c;在以前单体架构的时候&#xff0c;会将所有的应用功能都集中在一个服务当中。 单体架构初始开发…