![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c6dd66befd314442adf07e1dec0d550c.png
QDrag
对象,并指定要拖动的数据。QWidget
或者QGraphicsItem
),那么会发生放下操作。QMimeData
对象来处理拖放的数据。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
,用来处理拖动物体离开组件时的事件。QListView
设置了继承于QAbstractListModel
的代理模型,右边为一个QWidget
。 // 计算新的图像大小,取原始图片宽高的最小值作为新的尺寸size
int 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;
}
};
void PuzzleWidget::addInPlace(Piece piece)
{
if (piece.location == piece.rect.topLeft() / pieceSize())
{
inPlace++;
if (inPlace == MacroDf::getCloum() * MacroDf::getCloum())
emit puzzleCompleted();
}
}
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);
}
}
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++;
}
}
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);
// 如果拼图块放置在正确的位piece
addInPlace(rPic);
addInPlace(piece);
}
}
else
{
highlightedRect = QRect();
// 不是我们支持的数据格式,保留默认行为
event->ignore();
}
}
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();
else
endRow = 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;
}