这个例子展示使用拖放的API来完成一个拼图的解密游戏。
如图,将左边的拼图块拖放到右边,并完成恢复原图的样子即完成了游戏。
例子中用到了一个QSizePolicy的类,它是用来描述横向和纵向大小策略的布局属性的。
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
它会影响窗体的布局引擎,每个窗体在放置前都会返回一个QSizePolicy的东西,我们可以使用QWidget::sizePolicy来改变这个策略。QSizePolicy包含两个独立的QSizePolicy::Policy值和两个stretch因子,分别描述横向和纵向的策略。
可用horizontalPolicy(), verticalPolicy(), horizontalStretch(), verticalStretch()函数就可以返回;
可用setHorizontalPolicy(), setVerticalPolicy(), setHorizontalStretch(), setVerticalStretch()进行设置。
例子的原理是,通过将一张图缩放为正方形后,将此图划分为5*5的大小的拼图,在右方的部件(PuzzleWidget)中,为部件也同样划分为5*5的方块,保存其大小和拼图大小相同并保持不变。MainWindow将图设置好,并为拼图设置编号,并存入懂啊左边部件(PiecesList)中。当我们将左边部件往右变拖放时,每放置一块就保存放置的位置,并检查是否完成了拼图,放置前检查是否此位置可放(当前位置没有被保存)。最后如果检查完成了拼图就释放信号,让MainWindow接收并弹出提示对话框。
这个例子是个不错的例子。通过本例子不仅可以学到如何自定义拖放内容,处理拖放等,还可以学到如何自定义窗体部件。
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(puzzle);
QApplication app(argc, argv);
MainWindow window;
window.openImage(":/images/example.jpg"); // 打开资源图片
#ifdef Q_OS_SYMBIAN
window.showMaximized();
#else
window.show();
#endif
return app.exec();
}
// PiecesList继承自QListWidget,支持拖放
class PiecesList : public QListWidget
{
Q_OBJECT
public:
PiecesList(int pieceSize, QWidget *parent = 0);
void addPiece(QPixmap pixmap, QPoint location); // 加入拼图块
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void startDrag(Qt::DropActions supportedActions);
int m_PieceSize; // 保存拼图块的大小
};
PiecesList::PiecesList(int pieceSize, QWidget *parent)
: QListWidget(parent), m_PieceSize(pieceSize)
{
setDragEnabled(true); // 设置视图中的项可拖动
// 设置视图模型,默认对于ListMode是不可拖放的,IconMode可拖放
setViewMode(QListView::IconMode);
setIconSize(QSize(m_PieceSize, m_PieceSize));
setSpacing(10); // 设置空白,一个项周围空10像素
setAcceptDrops(true); // 设置可接收放置操作
setDropIndicatorShown(true); // 放置指示器显示
}
// 拖动进入
void PiecesList::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("image/x-puzzle-piece")) // 格式允许
event->accept();
else
event->ignore();
}
// 拖动移动
void PiecesList::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("image/x-puzzle-piece")) { // 是目标格式
event->setDropAction(Qt::MoveAction); // 设置为移动动作
event->accept(); // 接收
} else
event->ignore();
}
// 放置
void PiecesList::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("image/x-puzzle-piece")) {
QByteArray pieceData = event->mimeData()->data("image/x-puzzle-piece");
QDataStream dataStream(&pieceData, QIODevice::ReadOnly);
QPixmap pixmap;
QPoint location;
dataStream >> pixmap >> location; // 获取MIME文件信息中的数据
addPiece(pixmap, location); // 将数据(拼图)加入到PiecesList
event->setDropAction(Qt::MoveAction);
event->accept();
} else
event->ignore();
}
// 添加一块拼图项
void PiecesList::addPiece(QPixmap pixmap, QPoint location)
{
QListWidgetItem *pieceItem = new QListWidgetItem(this);
pieceItem->setIcon(QIcon(pixmap)); // 项图标
pieceItem->setData(Qt::UserRole, QVariant(pixmap)); // 项数据
pieceItem->setData(Qt::UserRole+1, location);
pieceItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable // 设置标记
| Qt::ItemIsDragEnabled);
}
// 开始拖动
void PiecesList::startDrag(Qt::DropActions /*supportedActions*/)
{
QListWidgetItem *item = currentItem(); // 获取选中的项
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly); // 将信息写入itemData
QPixmap pixmap = qvariant_cast<QPixmap>(item->data(Qt::UserRole)); // 提取项的Qt::UserRole数据
QPoint location = item->data(Qt::UserRole+1).toPoint(); // 获取原缩放后图排列中的位置
dataStream << pixmap << location; // 存入数据流
QMimeData *mimeData = new QMimeData;
mimeData->setData("image/x-puzzle-piece", itemData); // 设置MIME文件数据信息
QDrag *drag = new QDrag(this); // 定义拖动
drag->setMimeData(mimeData); // 设置MIME文件
drag->setHotSpot(QPoint(pixmap.width()/2, pixmap.height()/2)); // 设置热点
drag->setPixmap(pixmap); // 设置拖动时的图片
if (drag->exec(Qt::MoveAction) == Qt::MoveAction) // 如果返回移动消息就删除本项
delete takeItem(row(item));
}
class PuzzleWidget : public QWidget
{
Q_OBJECT
public:
PuzzleWidget(int imageSize, QWidget *parent = 0); // 初始化固定的大小构造器
void clear(); // 清空
int pieceSize() const; // 拼图块大小
int imageSize() const; // 图大小
signals:
void puzzleCompleted(); // 完成拼图
protected: // 提供拖放的重新实现事件处理器
void dragEnterEvent(QDragEnterEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event); // 绘制事件
private:
int findPiece(const QRect &pieceRect) const;
const QRect targetSquare(const QPoint &position) const;
QList<QPixmap> piecePixmaps; // 存储拼图块
QList<QRect> pieceRects; // 拼图块矩形表,保存放置进来的对应位置的拼图
QList<QPoint> pieceLocations; // 拼图位置
QRect highlightedRect; // 高亮矩形
int inPlace; //
int m_ImageSize; // 窗体大小数据
};
PuzzleWidget::PuzzleWidget(int imageSize, QWidget *parent)
: QWidget(parent), m_ImageSize(imageSize)
{
setAcceptDrops(true); // 设置可接收放置操作
setMinimumSize(m_ImageSize, m_ImageSize); // 设置窗体最小最大,使之大小固定不变
setMaximumSize(m_ImageSize, m_ImageSize);
}
// 清空
void PuzzleWidget::clear()
{
pieceLocations.clear(); // 将存储的那些数据都清空掉
piecePixmaps.clear();
pieceRects.clear();
highlightedRect = QRect();
inPlace = 0;
update(); // 更新
}
// 拖动进入
void PuzzleWidget::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("image/x-puzzle-piece"))
event->accept();
else
event->ignore();
}
// 拖动离开
void PuzzleWidget::dragLeaveEvent(QDragLeaveEvent *event)
{
QRect updateRect = highlightedRect;
highlightedRect = QRect();
update(updateRect);
event->accept();
}
// 拖动移动
void PuzzleWidget::dragMoveEvent(QDragMoveEvent *event)
{
QRect updateRect = highlightedRect.unite(targetSquare(event->pos()));
if (event->mimeData()->hasFormat("image/x-puzzle-piece")
&& findPiece(targetSquare(event->pos())) == -1) {
highlightedRect = targetSquare(event->pos());
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
highlightedRect = QRect();
event->ignore();
}
update(updateRect);
}
// 放置
void PuzzleWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("image/x-puzzle-piece") // MIME文件格式
&& findPiece(targetSquare(event->pos())) == -1) { // 没有放置过拼图
QByteArray pieceData = event->mimeData()->data("image/x-puzzle-piece"); // 获取MIME文件信息
QDataStream dataStream(&pieceData, QIODevice::ReadOnly); // 读取信息
QRect square = targetSquare(event->pos()); // 获取放置位置的矩形块
QPixmap pixmap;
QPoint location;
dataStream >> pixmap >> location; // 数据赋值到pixmap,location
pieceLocations.append(location); // 将location存入pieceLocation
piecePixmaps.append(pixmap); // 将拼图存入到piecePixmap
pieceRects.append(square); // 将矩形块加入到pieceRects,表示该位置矩形已占用
highlightedRect = QRect();
update(square);
event->setDropAction(Qt::MoveAction); // 设置为移动动作
event->accept();
if (location == QPoint(square.x()/pieceSize(), square.y()/pieceSize())) {
inPlace++;
if (inPlace == 25)
emit puzzleCompleted(); // 当拼完并排序正确,激发此信号
}
} else {
highlightedRect = QRect();
event->ignore();
}
}
// 查找对应位置
int PuzzleWidget::findPiece(const QRect &pieceRect) const
{
for (int i = 0; i < pieceRects.size(); ++i) {
if (pieceRect == pieceRects[i]) {
return i;
}
}
return -1;
}
// 鼠标按下
void PuzzleWidget::mousePressEvent(QMouseEvent *event)
{
QRect square = targetSquare(event->pos()); // 根据按下的位置,返回对应位置的矩形
int found = findPiece(square);
if (found == -1) // 对应位置没有放置过拼图
return;
QPoint location = pieceLocations[found]; //
QPixmap pixmap = piecePixmaps[found];
pieceLocations.removeAt(found);
piecePixmaps.removeAt(found);
pieceRects.removeAt(found);
if (location == QPoint(square.x()/pieceSize(), square.y()/pieceSize()))
inPlace--;
update(square);
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << pixmap << location;
QMimeData *mimeData = new QMimeData;
mimeData->setData("image/x-puzzle-piece", itemData);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setHotSpot(event->pos() - square.topLeft());
drag->setPixmap(pixmap);
if (!(drag->exec(Qt::MoveAction) == Qt::MoveAction)) {
pieceLocations.insert(found, location);
piecePixmaps.insert(found, pixmap);
pieceRects.insert(found, square);
update(targetSquare(event->pos()));
if (location == QPoint(square.x()/pieceSize(), square.y()/pieceSize()))
inPlace++;
}
}
// 绘制事件
void PuzzleWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.fillRect(event->rect(), Qt::white); // 用白色填充本窗体的区域
if (highlightedRect.isValid()) { // 如果有高亮矩形块要显示
painter.setBrush(QColor("#ffcccc")); // 设置画刷为粉色
painter.setPen(Qt::NoPen); // 设置画笔:Qt::NoPen
painter.drawRect(highlightedRect.adjusted(0, 0, -1, -1)); // 将方块画出
}
for (int i = 0; i < pieceRects.size(); ++i) { // 绘制已经放置好了的拼图块
painter.drawPixmap(pieceRects[i], piecePixmaps[i]);
}
painter.end();
}
// 返回对应位置的矩形
const QRect PuzzleWidget::targetSquare(const QPoint &position) const
{
return QRect(position.x()/pieceSize() * pieceSize(), position.y()/pieceSize() * pieceSize(), pieceSize(), pieceSize());
}
// 返回拼图块大小(原图片被分为了5行5列)
int PuzzleWidget::pieceSize() const
{
return m_ImageSize / 5;
}
// 返回原图大小
int PuzzleWidget::imageSize() const
{
return m_ImageSize;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
public slots:
void openImage(const QString &path = QString()); // 载入图片
void setupPuzzle(); // 建立拼图槽
private slots:
void setCompleted(); // 拼图摆放完成提示槽
private:
void setupMenus(); // 设置菜单
void setupWidgets(); // 建立两个窗体部件
QPixmap puzzleImage; // 图片
PiecesList *piecesList; // 存放拼图的ListWidget
PuzzleWidget *puzzleWidget; // 摆放拼图的Widget
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupMenus(); // 建立菜单
setupWidgets(); // 建立窗体部件
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); // 大小策略
setWindowTitle(tr("Puzzle")); // 标题
}
void MainWindow::openImage(const QString &path) // 打开一个图片
{
QString fileName = path;
if (fileName.isNull())
fileName = QFileDialog::getOpenFileName(this,
tr("Open Image"), "", "Image Files (*.png *.jpg *.bmp)");
if (!fileName.isEmpty()) { // 载入图片
QPixmap newImage;
if (!newImage.load(fileName)) { // 未成功载入
QMessageBox::warning(this, tr("Open Image"),
tr("The image file could not be loaded."),
QMessageBox::Cancel);
return;
}
puzzleImage = newImage; // 将成功载入的图复制给puzleImage
setupPuzzle(); // 建立游戏
}
}
// 设置完成消息,并重新开始建立游戏
void MainWindow::setCompleted()
{
QMessageBox::information(this, tr("Puzzle Completed"),
tr("Congratulations! You have completed the puzzle!\n"
"Click OK to start again."),
QMessageBox::Ok);
setupPuzzle();
}
// 建立游戏
void MainWindow::setupPuzzle()
{
int size = qMin(puzzleImage.width(), puzzleImage.height()); // 获取长宽中的较小者
puzzleImage = puzzleImage.copy((puzzleImage.width() - size)/2,
(puzzleImage.height() - size)/2, size, size).scaled(puzzleWidget->width(),
puzzleWidget->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
piecesList->clear();
for (int y = 0; y < 5; ++y) { // 初始化拼图列表,将缩放后的图划分为5*5的拼图,QPoint指示几行几列编号
for (int x = 0; x < 5; ++x) {
int pieceSize = puzzleWidget->pieceSize();
QPixmap pieceImage = puzzleImage.copy(x * pieceSize, y * pieceSize, pieceSize, pieceSize);
piecesList->addPiece(pieceImage, QPoint(x, y));
}
}
qsrand(QCursor::pos().x() ^ QCursor::pos().y());
for (int i = 0; i < piecesList->count(); ++i) { // 将每个拼图块作为piecesList的项
if (int(2.0*qrand()/(RAND_MAX+1.0)) == 1) {
QListWidgetItem *item = piecesList->takeItem(i); // 移除并返回
piecesList->insertItem(0, item); // 插入
}
}
puzzleWidget->clear();
}
// 创建菜单
void MainWindow::setupMenus()
{
QMenu *fileMenu = menuBar()->addMenu(tr("&File")); // file菜单栏
QAction *openAction = fileMenu->addAction(tr("&Open...")); // open菜单项
openAction->setShortcuts(QKeySequence::Open);
QAction *exitAction = fileMenu->addAction(tr("E&xit")); // exit菜单项
exitAction->setShortcuts(QKeySequence::Quit);
QMenu *gameMenu = menuBar()->addMenu(tr("&Game")); // Game菜单栏
QAction *restartAction = gameMenu->addAction(tr("&Restart")); // restart菜单项
// 连接信号槽
connect(openAction, SIGNAL(triggered()), this, SLOT(openImage()));
connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
connect(restartAction, SIGNAL(triggered()), this, SLOT(setupPuzzle()));
}
// 创建窗体部件并布局
void MainWindow::setupWidgets()
{
QFrame *frame = new QFrame; // 创建一个QFrame来包含两个拼图部件
QHBoxLayout *frameLayout = new QHBoxLayout(frame);
#if defined(Q_OS_SYMBIAN) || defined(Q_WS_SIMULATOR)
puzzleWidget = new PuzzleWidget(260);
#else
puzzleWidget = new PuzzleWidget(400); // 放置拼图的部件大小为固定的
#endif
piecesList = new PiecesList(puzzleWidget->pieceSize(), this); // 返回puzzleWidget拼图片大小
connect(puzzleWidget, SIGNAL(puzzleCompleted()), // puzzleWidget的完成后执行setCompleted
this, SLOT(setCompleted()), Qt::QueuedConnection);
frameLayout->addWidget(piecesList); // 添加部件
frameLayout->addWidget(puzzleWidget);
setCentralWidget(frame); // 设置中心部件
}