手机免费建站平台下载,淘宝做个网站多少钱,网站底部留言代码,WordPress查看主题源代码文章目录 效果图引言玩法 拖拽概念基本概念如何在Qt中使用拖放注意事项 游戏关键问题总结 效果图 
开始一个拖动操作通常是当用户在一个可拖动的组件上按下鼠标按钮并移动一定距离时。在Qt中你需要创建一个QDrag对象并指定要拖动的数据。
放下 (Drop)
放下操作发生在拖动过程的最后当用户释放鼠标按钮时。如果释放位置是一个可以接受放下的组件一个设置为接受放下的QWidget或者QGraphicsItem那么会发生放下操作。
MIME 数据
拖动和放下的数据是通过MIMEMultipurpose 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-NBif (!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就行不过它那个有几个明显的问题我这都优化了。知识理应共享大家相互学习源码在此哦。