这个例子展示使用拖放的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); // 设置中心部件 }