淘先锋技术网

首页 1 2 3 4 5 6 7

这个例子展示使用拖放的API来完成一个拼图的解密游戏。


如图,将左边的拼图块拖放到右边,并完成恢复原图的样子即完成了游戏。

例子中用到了一个QSizePolicy的类,它是用来描述横向和纵向大小策略的布局属性的。

setSizePolicy(QSizePolicy(QSizePolicy::FixedQSizePolicy::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);                // 设置中心部件
}