这个例子展示使用拖放的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 )
- {
- 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); // 设置中心部件
- }
FROM: http://blog.csdn.net/xuguangsoft/article/details/8543092