前言:
如果不瞭解Qt drag-drop 的建議先看一下 Qt 實現拖放內容 drag - drop 【簡單明瞭】
否則看起來會一頭霧水
看一下官方的介紹:
譯文:這個例子是一個簡單的拼圖遊戲的實現,它使用了Qt的模型/視圖框架提供的對拖放的內置支持。拖放拼圖的例子展示了許多相同的特性,但是採用了另一種方法,即在應用程序級別使用Qt的拖放API來處理拖放操作。
這個拼圖的demo 還是能學到東西的
項目叫做 puzzle 大家可以去官方demo 裏找一下 玩一玩
項目的結構如下
main
mainwindow 主界面
piecesmodel 拼圖塊模型
puzzlewidget 拼圖窗口
main :
初始化了資源
實例化了主窗體
加載了一個圖片
圖片就是下面
mainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
public slots:
void openImage();
void loadImage(const QString &path);
void setupPuzzle();
private slots:
void setCompleted();
private:
void setupMenus();
void setupWidgets();
QPixmap puzzleImage;
QListView *piecesList;
PuzzleWidget *puzzleWidget;
PiecesModel *model;
};
類也不復雜
private:
一個 存放 拼圖照片的 pixmap
一個 存放左邊拼圖塊的 listview
右邊的拼圖窗口
拼圖的塊模型
他是自定義了 模型 等會看
整個的結構是這樣的
#include "mainwindow.h"
#include "piecesmodel.h"
#include "puzzlewidget.h"
#include <QDebug>
#include <QtWidgets>
#include <stdlib.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupMenus();
setupWidgets();
model = new PiecesModel(puzzleWidget->pieceSize(), this);
piecesList->setModel(model);
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
setWindowTitle(tr("Puzzle"));
}
void MainWindow::openImage()
{
const QString fileName =
QFileDialog::getOpenFileName(this,
tr("Open Image"), QString(),
tr("Image Files (*.png *.jpg *.bmp)"));
if (!fileName.isEmpty())
loadImage(fileName);
}
void MainWindow::loadImage(const QString &fileName)
{
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;
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->imageSize(),
puzzleWidget->imageSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
qsrand(QCursor::pos().x() ^ QCursor::pos().y());
model->addPieces(puzzleImage);
puzzleWidget->clear();
}
void MainWindow::setupMenus()
{
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QAction *openAction = fileMenu->addAction(tr("&Open..."));
openAction->setShortcuts(QKeySequence::Open);
QAction *exitAction = fileMenu->addAction(tr("E&xit"));
exitAction->setShortcuts(QKeySequence::Quit);
QMenu *gameMenu = menuBar()->addMenu(tr("&Game"));
QAction *restartAction = gameMenu->addAction(tr("&Restart"));
connect(openAction, &QAction::triggered, this, &MainWindow::openImage);
connect(exitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
connect(restartAction, &QAction::triggered, this, &MainWindow::setupPuzzle);
}
void MainWindow::setupWidgets()
{
QFrame *frame = new QFrame;
QHBoxLayout *frameLayout = new QHBoxLayout(frame);
puzzleWidget = new PuzzleWidget(400);
piecesList = new QListView;
piecesList->setDragEnabled(true);
piecesList->setViewMode(QListView::IconMode);
piecesList->setIconSize(QSize(puzzleWidget->pieceSize() - 20, puzzleWidget->pieceSize() - 20));
piecesList->setGridSize(QSize(puzzleWidget->pieceSize(), puzzleWidget->pieceSize()));
piecesList->setSpacing(10);
piecesList->setMovement(QListView::Snap);
piecesList->setAcceptDrops(true);
piecesList->setDropIndicatorShown(true);
PiecesModel *model = new PiecesModel(puzzleWidget->pieceSize(), this);
piecesList->setModel(model);
connect(puzzleWidget, &PuzzleWidget::puzzleCompleted,
this, &MainWindow::setCompleted, Qt::QueuedConnection);
frameLayout->addWidget(piecesList);
frameLayout->addWidget(puzzleWidget);
setCentralWidget(frame);
}
把這個幾個函數的實現都挨個看吧
構造:
setupMenus()
頂部的菜單欄 沒啥說的
setupWidgets()
用了 水平佈局 把 左邊的 listView 和 右邊的 puzzleWidget 合起來
listview 設置了可以拖拽
設置了 view mode 是icon
設置了 icon 的大小 puzzleWidget->pieceSize() 是多少 等會去看這個類
設置 網格的大小
設置間距
設置 item 移動時 吸附到指定的網格上;
設置 item 在拖動和刪除項時是否顯示拖放指示器。
然後給 listview 設置自定義的 model
openImage()
打開圖片
loadImage()
把.h 裏面聲明的 puzzleImage 賦值 新讀進來的圖片
setupPuzzle()
這兩句的 意思是
把圖片 縮放爲 爲 size 的正方形
size 是 取最小的一方 比如 800*600 的圖片 那麼就是取 600
取 600 也不是從 0到600
而是去取 中間的 600
爲什麼?
(puzzleImage.width() - size) / 2
( 800 -600 /2) =100
從 100 的位置 開始取 取600 【100,700】
用筆在紙上畫一下就明白了
然後把縮放好的圖片 給到 model
model 的實現 我們下面看
PiecesModel.h
#include <QAbstractListModel>
#include <QList>
#include <QPixmap>
#include <QPoint>
#include <QStringList>
QT_BEGIN_NAMESPACE
class QMimeData;
QT_END_NAMESPACE
class PiecesModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit PiecesModel(int pieceSize, QObject *parent = 0);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool removeRows(int row, int count, const QModelIndex &parent) override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
int rowCount(const QModelIndex &parent) const override;
Qt::DropActions supportedDropActions() const override;
void addPiece(const QPixmap &pixmap, const QPoint &location);
void addPieces(const QPixmap& pixmap);
private:
QList<QPoint> locations;
QList<QPixmap> pixmaps;
int m_PieceSize;
};
不瞭解 自定義 model 的要去看一下 否則會看不懂
前面的 幾個函數 就是重載 基類的函數
只有這幾個是自己實現的
addPieces(const QPixmap& pixmap)
剛纔 我們處理好的圖片 就是傳遞給了這個函數
beginRemoveRows(QModelIndex(), 0, 24);
endRemoveRows();
只有我們重載了 基類的 removeRows 函數 上面的就必須要寫
for (int y = 0; y < 5; ++y) {
for (int x = 0; x < 5; ++x) {
QPixmap pieceImage = pixmap.copy(x*m_PieceSize, y*m_PieceSize, m_PieceSize, m_PieceSize);
addPiece(pieceImage, QPoint(x, y));
}
}
把傳進來的圖片 分成了 5行5列 的 25個 格子 每個格子的像素是 m_PieceSize
他是多少 等會 puzzleWidget 裏會說
addPiece(const QPixmap &pixmap, const QPoint &location)
然後把這些 圖片格子(拼圖塊) 以隨機的方式 插入到list裏面
有的是在頭部 有的是在尾部插入 打亂了順序
data(const QModelIndex &index, int role) const
獲取模型的數據 根據枚舉的不同 返回的類型不同
有 icon 有 pixmap 有 位置
userRole 是我們自定義的
removeRows(int row, int count, const QModelIndex &parent)
這裏可能有人看不懂了 我給你們弄個 gif
仔細看 邏輯是這樣的
當拖走了一個拼圖塊 左邊部分 那麼 剩餘的圖塊會進行一個排序 從拖走的位置 開始補齊
而不是 空着那個位置
mimeTypes() const
這裏要明白 必須要看我上面發的鏈接 這個就是包裝拖拽數據的類的密碼頭
mimeData(const QModelIndexList &indexes) const
包裝我們的數據 我們的拼圖爲啥能從 左邊的 widget 移動到 一個 widget
是因爲 drag 和 drop 的實現
其實就是把 左邊的 數據 發送到 右邊的窗口 在把他畫出來
這裏數據的封裝 必須用 QMimeData
這塊不明白的去看文章頭部的鏈接 看完就懂了
把 pixmap 和 位置 以數據流的形式寫到了 QbyteArray 然後給到 QMimeData
這個地方就用了 我們說的自定義的用戶的枚舉 來獲取不同的信息
又學到了一點
dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
這個函數 其實是 拖到這裏放下的函數 因爲我們可以把拼圖從右邊拖回到左邊
先判斷 hasFormat mimeTypes 對不對
然後 把 包裝的數據 (拼圖)解包
把數據插入 然後 把每個拼圖 向後移動一個位置 把它塞進去
ok 這邊的整個類就結束了 對這塊不瞭解的 可能看不太懂
我覺得我說的夠詳細了
繼續看 右邊的拼圖類
PuzzleWidget.h
#include <QList>
#include <QPixmap>
#include <QPoint>
#include <QWidget>
QT_BEGIN_NAMESPACE
class QDragEnterEvent;
class QDropEvent;
class QMouseEvent;
QT_END_NAMESPACE
class PuzzleWidget : public QWidget
{
Q_OBJECT
public:
explicit PuzzleWidget(int imageSize, QWidget *parent = 0);
void clear();
int pieceSize() const;
int imageSize() const;
signals:
void puzzleCompleted();
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
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;
};
這邊 還是重載了基類的方法
拖拽進入事件
拖拽離開事件
拖拽移動事件
放下事件
鼠標按壓事件
繪圖事件
一個個看吧
構造
設置 接收拖拽放下事件 這個必須要寫 不寫接收不到拖拽放下事件
設置窗口的固定大小 就是加載的圖片處理完後的的大小
dragEnterEvent(QDragEnterEvent *event)
還是判斷 mimeType
比如 快遞是我們要的東西 才能簽收
dragMoveEvent(QDragMoveEvent *event)
這裏是 拖拽移動時 繪製後面的 高亮矩形塊
painter 繪製背部高亮矩形框和拼圖
下面的 放下事件 和 點擊事件
都是 把數據包裝 刪除拼圖 各種或者 把數據拆開 添加進容器 繪製出來
和上面 model 的實現類似
void PuzzleWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("image/x-puzzle-piece")
&& findPiece(targetSquare(event->pos())) == -1) {
QByteArray pieceData = event->mimeData()->data("image/x-puzzle-piece");
QDataStream stream(&pieceData, QIODevice::ReadOnly);
QRect square = targetSquare(event->pos());
QPixmap pixmap;
QPoint location;
stream >> pixmap >> location;
pieceLocations.append(location);
piecePixmaps.append(pixmap);
pieceRects.append(square);
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->start(Qt::MoveAction) == 0) {
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++;
}
}
寫到這裏 基本的也都說完了
太長了 我也不想寫了 就到這吧
反正就是 你要看懂這個拼圖項目 首先要搞懂 自定義model 看一下 MVD 模型
瞭解 drop 和 drag 的機制
這些在我的其他我文章都有寫 可以看一下 然後在來看這個 就比較清晰了