用Qt的图形视图框架(Graphics View Framework)做了一个拼图游戏DEMO,演示了:

  • QGraphicsView、QGraphicsScene、QGraphicsItem的基本用法
  • drag && drop
  • 自定义QGraphicsItem

先来看看效果吧:

现在,来看下代码了。

项目说明

如上图所示,项目名称为qPuzzle,三个源文件,main.cpp是入口,imageitem.h和imageitem.cpp实现了:

  • PuzzleImageItem,就是界面左上侧那两个可以拖动的碎片,支持拖动
  • PuzzlePart,用于接受拖放的item
  • PuzzlePartManager,管理可拖放的PuzzleImageItem,拖放到位后从QGraphicsScene中移除PuzzleImageItem

项目还有几个图片资源,model.png是带有拼图区域的房子图片,mode_1.png和model_2.png是房子上扣出来的小图片。

源码说明

分开来说吧,main()、PuzzleImageItem和PuzzlePartItem。

入口函数main

先看main.cpp吧:

class GraphicsView : public QGraphicsView
{
public:
GraphicsView(QGraphicsScene *scene) : QGraphicsView(scene)
{
} protected:
virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE
{
}
}; int main(int argc, char *argv[])
{
QApplication a(argc, argv); QGraphicsScene scene(0, 0, 480, 360);
PuzzleImageItem *image1 = new PuzzleImageItem(":/model_1.png", 60, 60, 1);
image1->setPos(4, 4);
scene.addItem(image1);
PuzzleImageItem *image2 = new PuzzleImageItem(":/model_2.png", 60, 60, 2);
image2->setPos(4, 70);
scene.addItem(image2); QGraphicsPixmapItem *model = new QGraphicsPixmapItem(QPixmap(":/model.png"));
scene.addItem(model);
model->setPos(100, 100); PuzzlePartManager mgr(&scene);
mgr.addSourceItems(1, image1);
mgr.addSourceItems(2, image2); PuzzlePart *part1 = new PuzzlePart(&mgr, 60, 60, 1);
part1->setPos(261, 149);
part1->setZValue(2);
scene.addItem(part1); PuzzlePart *part2 = new PuzzlePart(&mgr, 60, 60, 2);
part2->setPos(231, 199);
part2->setZValue(2);
scene.addItem(part2); GraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
view.setBackgroundBrush(QColor(230, 200, 167));
view.setWindowTitle("House Puzzle");
view.show(); return a.exec();
}

main()方法创建了QGraphicsScene实例,构造各种item并添加到场景中,将QGraphicsView与QGraphicsScene关联起来,代码很直接,不多说了。

PuzzleImageItem

再来看看支持拖动的PuzzleImageItem的实现:

class PuzzleImageItem : public QGraphicsObject
{
public:
PuzzleImageItem(const QString & imagePath, int w, int h, int partId);
QRectF boundingRect() const Q_DECL_OVERRIDE;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE; protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE; protected:
QImage m_image;
int m_width;
int m_height;
int m_partId;
};

PuzzleImageItem代表完整图片的一部分,它从QGraphicsObject继承而来,持有一个图片、宽、高以及图片的id(m_partId)。其中m_partId是碎片索引,在拖放到位后,PuzzlePartManager通过它来将界面左上角的碎片从视图中移除。

重写了boundingRect和paint方法,这是自定义QGraphicsItem时通常都需要做的。

重写了mousePressEvent,在它里面将鼠标形状修改为抓紧的小手:

void PuzzleImageItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::ClosedHandCursor);
}

重写了mouseMoveEvent,在它里面组装QDrag和QMimeData:

void PuzzleImageItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
.length() < QApplication::startDragDistance()) {
return;
} QDrag *drag = new QDrag((QObject*)event->widget());
QMimeData *mime = new QMimeData;
mime->setImageData(m_image);
mime->setData(QString(QMetaType::typeName(QMetaType::Int)), QString("%1").arg(m_partId).toLatin1());
drag->setMimeData(mime); drag->setPixmap(QPixmap::fromImage(m_image));
drag->setHotSpot(QPoint(15, 30)); drag->exec();
setCursor(Qt::OpenHandCursor);
}

注意我在这里用QMimeData传递m_partId给接受拖放的PuzzlePartItem,这样可以区分碎片该放到哪个目标区域。在PuzzlePartItem的dragEnterEvent方法中有用到,代码如下:

void PuzzlePart::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
const QMimeData *mime = event->mimeData();
int partId = mime->data(QMetaType::typeName(QMetaType::Int)).toInt();
if(mime->hasImage() && partId == m_partId)
{
event->setAccepted(true);
m_dragOver = true;
update();
}
else
{
event->setAccepted(false);
}
}

这里可以留意一下使用QMimeData传递非典型类型数据的做法。

重写了mouseReleaseEvent,在它里面重新设置鼠标形状为打开的小手:

void PuzzleImageItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
}

PuzzlePartItem

PuzzlePartItem定义了一个区域,用于接受拖放。它同样从QGraphicsObject继承,声明如下:

class PuzzlePart : public QGraphicsObject
{
public:
PuzzlePart(PuzzlePartManager *mgr, int w, int h, int partId, QGraphicsItem *parent = 0); QRectF boundingRect() const Q_DECL_OVERRIDE;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) Q_DECL_OVERRIDE; protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
void dropEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE; PuzzlePartManager *m_sourcePartManager;
int m_width;
int m_height;
int m_partId;
QImage m_image;
bool m_dragOver;
};

这个类也保留了一个id(m_partId),可以用来决定接受哪个PuzzleImageItem。前面贴出来的dragEnterEvent方法的代码里就用到了。dragEnterEvent方法在PuzzleImageItem被拖到PuzzlePartItem所在区域时触发。

当释放鼠标时,会触发dropEvent,代码如下:

void PuzzlePart::dropEvent(QGraphicsSceneDragDropEvent *event)
{
m_dragOver = false;
const QMimeData *mime = event->mimeData();
int partId = mime->data(QMetaType::typeName(QMetaType::Int)).toInt();
if(mime->hasImage() && partId == m_partId)
{
m_image = qvariant_cast<QImage>(event->mimeData()->imageData());
m_sourcePartManager->removeItem(partId);
}
update();
}

我们在这里接收QMimeData里的图片,触发重新绘制,还调用PuzzlePartManager的removeItem将源item(PuzzleImageItem)从视图中移除。

重写dragLeaveEvent是为了在鼠标拖着源object离开自己时重绘。


好啦,这个demo基本就这样了。

需要项目源码的,可以关注我的订阅号“程序视界”,回复“qPuzzle”获取下载地址。

用Qt图形视图框架开发拼图游戏的更多相关文章

  1. Qt图形视图框架公开课资料

    接受CSDN学院的邀请,讲一次公开课,主题是Qt图形视图框架,报名链接在这里:http://edu.csdn.net/huiyiCourse/detail/228. 内容有两部分:自定义Item和拖放 ...

  2. QT 图形视图框架

    https://blog.csdn.net/qq769651718/article/details/79357936 使用QPushButton.QLabel.QCheckBox等构成GUI的控件或自 ...

  3. Qt开发技术:图形视图框架(一)基本介绍

    前话   使用到Qt的视图框架.   Qt视图框架介绍 简介   图形视图框架(The Graphic View Framework)用于管理和与大量定制的二维图形项目交互,以及用于可视化项目的视图小 ...

  4. Qt 2D绘图之五:图形视图框架的结构和坐标系统

    一.图形视图框架的结构 在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们.但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动.检测它们的碰撞和叠加:或者我们想让自己绘制的图 ...

  5. Qt-MVC图形视图框架分解

    前面在<Qt-MVC图形视图框架出识>中我们了解了Qt图形视图框架中三个最基本的类,弄清他们的关系,本片小文,我们将对QGraphicsView,QGraphiceScene,QGraph ...

  6. Qt-MVC图形视图框架初识

    使用QPushButton.QLabel.QCheckBox等构成GUI的控件或自定义图形时,开发应用程序会变得很简单.但是如果想在GUI中使用数十个或者数百个图形对象,向用户完美展示控制场景,则会受 ...

  7. Qt开发技术:图形视图框架(二)场景QGraphicsScene、QGraphicsItem与QGraphicsView详解

    前话   Qt的图形视图框架,最核心的三个类为:QGraphicsScene.QGraphicsItem与QGraphicsView.   基于图形框架的高级白板软件Demo QGraphicsSce ...

  8. Qt之图形视图框架

    简述 图形视图(Graphics View)提供了一个平台,用于大量自定义2D图元的管理与交互,并提供了一个视图部件(view widget)来显示可以缩放和旋转的图元. 框架包括一个事件传播架构,支 ...

  9. Qt 2D绘图之六:图形视图框架的事件处理与传播

    一.简介 图形视图框架中的事件都是首先由视图进行接收,然后传递给场景,再由场景传递给相应的图形项.而对于键盘事件,它会传递给获得焦点的图形项,可以使用QGraphicsScene类的setFocusI ...

随机推荐

  1. la----3695 City Game(最大子矩阵)

    Bob is a strategy game programming specialist. In his new city building game the gaming environment ...

  2. (巨坑)改了tpl文件之后,前端效果没反应

    通常前端修改很小的部分代码,并不会立即显示出效果.比如,原来是 ,修改后是 虽然只是加多了一个函数,但是作用很大,这种情况下,可能是由于浏览器缓存的原因,修改后的代码没有被重新加载.这个时候,只需要在 ...

  3. JDE报表开发笔记(数据选择及继承)

    在Section的Event中, Do Custom Section("sectionxxx")自定义加载下一个Section Set Selection Append Flag( ...

  4. EF中的那些批量操作

    在使用EF的过程中,我们经常会遇到需要批量操作数据的场景,批量操作有的时候不仅能提高性能,比如使用SqlBulkCopy进入批量插入的时候,而且比较方便操作,提高效率.那么这篇文章就来总结EF中的那些 ...

  5. JS引用类型之——数组

    前言 数组作为JS中非常常用的引用类型,其功能是非常强大滴,今天小猪就彻底的看了下它.为了防止猪脑子不够用所以记录在案呐 1.数组的创建 var arrayObj = new Array(); //创 ...

  6. 如何在Quagga BGP路由器中设置IPv6的BGP对等体和过滤

    在本教程中,我们会向你演示如何创建IPv6 BGP对等体并通过BGP通告IPv6前缀.同时我们也将演示如何使用前缀列表和路由映射特性来过滤通告的或者获取到的IPv6前缀. 拓扑 服务供应商A和B希望在 ...

  7. struts中的常量,action配置中的默认值

    1.struts中Action的开发方式 继承ActionSupport类,这种方法实现的Action可以进行数据校验: 实现Action接口: 不继承任何类,不实现任何接口: 是否继承类或实现接口, ...

  8. javaweb-四则运算

    这次作业,我们选择的是网页开发,后来我们小组才知道自己这方面的知识还是太匮乏了. 主要代码: public class calcu extends HttpServlet{ public void d ...

  9. windows下安装openssh服务并实现远程登录

    需要准备的工具: winscp 点击下载        openssh 点击下载  步骤: 在远程计算机安装 1.首先安装openssh,双击并安装 2.指定用户的home directory为C:\ ...

  10. ClassLoader相关内容

    1.什么叫做bootstrap?作为形容词有依靠自己力量的:自己做的等意思,在我们计算机世界里,一般指的是自举,引导,引导程序. 那什么是bootstrapClassLoader呢?它是引导加载器,也 ...