Qt图形视图体系结构示例解析(视图、拖拽、动画)
本博的示例来自与QT Example:C:\Qt\Qt5.9.3\Examples\Qt-5.9.3\widgets\graphicsview\dragdroprobot
将通过分析示例完成主要功能:
(1)颜色图元绘制
(2)机器人图元绘制
(3)颜色图元的鼠标事件
(4)机器人图元的DragDrop事件
(5)图元动画效果
一、颜色图元类实现
QGraphicsItem作为所有图元类的基类,自定义图元类需继承QGraohicsItem类,实现其基类的纯虚函数
virtual QRectF boundingRect() const = ;
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) = ;
boundingRect()设置图元的边界矩形范围,QGraphicsView使用此来确定图元是否需要重绘
paint()实现图元的绘制操作,一种方法是直接在paint中对图元进行绘制。另一种方法可以通过shape返回QPainterPath,然后在paint中依据QPainterPath进行绘制
该示例实现了随机的10中颜色图元,boundRect()为QRectF(-15,-15,30,30),图元的中心坐标为(0,0)
(1)自定义随机颜色
m_pColor(qrand() % , qrand() % , qrand() % )
(2)图元边界矩形设置
QRectF ColorItem::boundingRect() const
{
return QRectF(-,-,,);
}
(3)图元绘制
void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setBrush(m_pColor);
painter->drawEllipse(boundingRect());
}
(4)光标设置
当鼠标进入图元或是拖动图元时设置光标形状,光标形状查看枚举类型:CursorShape
setCursor(Qt::OpenHandCursor);
setAcceptedMouseButtons(Qt::LeftButton);
(5)设置ToolTip
当鼠标进入图元时显示提示内容:

setToolTip(QString("QColor(%1,%2,%3)\n%4").arg(m_pColor.red())
.arg(m_pColor.green()).arg(m_pColor.blue())
.arg("Click and drag this color onto the robot!"));
二、机器人头像图元类实现
颜色图元的实现中已经了解了基本实现方法,机器人图元的实现也不例外,由于机器人包括很多图元部分(头、身体等),我们可以采用面对对象继承的方式来实现。
定义所有机器人图元的基类Robot
class Robot : public QGraphicsObject
{
public:
Robot(QGraphicsItem *parent = Q_NULLPTR); protected:
virtual void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
virtual void dragLeaveEvent(QGraphicsSceneDragDropEvent *event);
//virtual void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
virtual void dropEvent(QGraphicsSceneDragDropEvent *event); QColor m_Color; // 颜色
bool m_bDragOver; // 鼠标是否拖放完毕
};
机器人头部图元:
class QPixmap;
class RobotHand : public Robot
{
public:
RobotHand(QGraphicsItem *parent = Q_NULLPTR); QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = ) override; protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
void dropEvent(QGraphicsSceneDragDropEvent *event) override; private:
QPixmap m_pixmap;
};
(1)边界矩形设置
QRectF RobotHand::boundingRect() const
{
return QRectF(-, -, ,);
}
(2)机器人头部绘制
当m_pixmap.isNull()为真时,使用默认颜色或拖放后的颜色m_Color进行填充,否则使用pixmap绘制
void RobotHand::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (m_pixmap.isNull())
{
painter->setPen(Qt::black);
painter->setBrush(m_bDragOver ? m_Color.light() : m_Color);
//painter->drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt::RelativeSize);
painter->drawRoundedRect(-, -, , , , , Qt::RelativeSize);
painter->setBrush(Qt::white);
painter->drawEllipse(-, -, ,);
painter->drawEllipse(, -, ,);
painter->setBrush(Qt::black);
painter->drawEllipse(-, -, , );
painter->drawEllipse(, -, , );
painter->setPen(QPen(Qt::black, ));
painter->setBrush(Qt::NoBrush);
painter->drawArc(-, -, , , * , * );
}
else
{
painter->scale(., .);
painter->drawPixmap(QPointF(- * 4.4, - * 3.54), m_pixmap);
}
}
三、视图、场景类实现
(1)场景设置
QGraphicsScene* m_pScene;
m_pScene = new QGraphicsScene(QRectF(-,-,,));
添加图元:
for (int i = ; i < ; i ++)
{
ColorItem *item = new ColorItem; item->setPos(qCos((i / 10.0) *6.28) * ,qSin((i / 10.0) *6.28) * );
if(i == )
{
item->setData(ColorItem::COLOR_TYPE,"pixmap");
}
m_pScene->addItem(item);
} Robot* pRobot = new RobotHand;
pRobot->setPos(-,-);
m_pScene->addItem(pRobot);
(2)视图设置
自定义视图:
class GraphicsView : public QGraphicsView
{
public:
GraphicsView(QGraphicsScene *scene, QWidget *parent = Q_NULLPTR)
:QGraphicsView(scene, parent)
{ } void resizeEvent(QResizeEvent *event)
{
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
};
这里重点提下resizeEvent虚函数,设置场景虽视图的变化情况,以下来自QT官方文档:

视图设置和添加场景:
GraphicsView* m_pView;
m_pView = new GraphicsView(m_pScene);
m_pView->setBackgroundBrush(QColor(, , ));
m_pView->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); setCentralWidget(m_pView);
四、颜色图元鼠标事件实现
颜色图元的鼠标事件包括鼠标按下,鼠标移动和鼠标释放,要了解更详细的事件机制可阅读前面的博客:Qt之事件处理机制
重载事件虚函数:
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void ColorItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
} void ColorItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "drag instance:" << QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
.length();
qDebug() << "startDragDistance:" << QApplication::startDragDistance();
if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
.length() < QApplication::startDragDistance())
{
return;
} QDrag *drag = new QDrag(event->widget());
QMimeData *mime = new QMimeData;
drag->setMimeData(mime);
if (data(COLOR_TYPE) == "pixmap")
{
mime->setImageData(QPixmap(":/images/head.png"));
}
else
{
mime->setColorData(m_pColor);
} QPixmap pixMap(,);
pixMap.fill(Qt::white);
QPainter painter(&pixMap);
painter.translate(, );
paint(&painter, , );
painter.end(); drag->setPixmap(pixMap);
drag->setHotSpot(QPoint(, )); drag->exec();
setCursor(Qt::OpenHandCursor);
} void ColorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
}
五、拖拽事件实现
在介绍如何实现拖拽事件之前先来了解两个类QDrag和QMimeData
(1)QMimeData类
QMimeData类为数据提供一个容器,用来记录关于MIME类型数据的信息
QMimeData常用来描述保存在剪切板里信息,或者拖拽原理
QMimeData对象把它所保存的信息和正确的MIME类型连接起来来保证信息可以被安全的在应用程序之间转移,或者在同一个应用程序之间拷贝
QMimeData对象通产雇佣new来创建,并且支持QDrag和QClipboard对象,这可以使QT管理他们所使用的内存
单一的QMimeData对象可以同时用好几种不同的格式来存储同一个数据,formats()函数返回可以用的数据格式的list,data()函数可以返回和MIME类型相连的数据类型,setData()用来为MIME类型设置数据
对于大多数MIME类型,QMimeData提供方便的函数来获取数据

QMiMeData数据的设置:
QMimeData *mime = new QMimeData;
if (data(COLOR_TYPE) == "pixmap")
{
mime->setImageData(QPixmap(":/images/head.png"));
}
else
{
mime->setColorData(m_pColor);
}
QMiMeData数据的获取:
const QMimeData* mime = event->mimeData();
if (mime->hasImage())
{
m_pixmap = qvariant_cast<QPixmap>(mime->imageData());
update();
}
(2)QDrag类
QDrag类提供了MIME基础数据类型的拖动和释放,拖放是用户在应用程序中复制和移动数据的一种直观方式,在许多桌面环境中被用作在应用程序之间复制数据的机制,qt中的拖放支持以处理拖放操作的大部分细节的QDrag类为中心。
QDrag类常用函数:
void setMimeData(QMimeData *data);
QMimeData *mimeData() const; void setPixmap(const QPixmap &);
QPixmap pixmap() const; void setHotSpot(const QPoint &hotspot); // 设置热点
QPoint hotSpot() const; QObject *source() const;
QObject *target() const; Qt::DropAction start(Qt::DropActions supportedActions = Qt::CopyAction);
Qt::DropAction exec(Qt::DropActions supportedActions = Qt::MoveAction);
Qt::DropAction exec(Qt::DropActions supportedActions, Qt::DropAction defaultAction); void setDragCursor(const QPixmap &cursor, Qt::DropAction action);
QPixmap dragCursor(Qt::DropAction action) const; Qt::DropActions supportedActions() const;
Qt::DropAction defaultAction() const; static void cancel();
void setMimeData(QMimeData *data); // 设置MimeData
void setHotSpot(const QPoint &hotspot); // 设置热点,即鼠标在拖动图片的显示位置
void setPixmap(const QPixmap &); // 设置跟随鼠标拖动的位图
exec()开始drag事件循环
QDrag对象的初始化在源窗口的mouseMoveEvent中进行:
QMimeData *mime = new QMimeData;
drag->setMimeData(mime);
if (data(COLOR_TYPE) == "pixmap")
{
mime->setImageData(QPixmap(":/images/head.png"));
}
else
{
mime->setColorData(m_pColor);
} QPixmap pixMap(,);
pixMap.fill(Qt::white);
QPainter painter(&pixMap);
painter.translate(, );
paint(&painter, , );
painter.end(); drag->setPixmap(pixMap);
drag->setHotSpot(QPoint(, )); drag->exec();
setCursor(Qt::OpenHandCursor);
}
(2)拖拽事件实现
在源窗口中的事件响应:
void ColorItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
} void ColorItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "drag instance:" << QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
.length();
qDebug() << "startDragDistance:" << QApplication::startDragDistance();
if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
.length() < QApplication::startDragDistance())
{
return;
} QDrag *drag = new QDrag(event->widget());
QMimeData *mime = new QMimeData;
drag->setMimeData(mime);
if (data(COLOR_TYPE) == "pixmap")
{
mime->setImageData(QPixmap(":/images/head.png"));
}
else
{
mime->setColorData(m_pColor);
} QPixmap pixMap(,);
pixMap.fill(Qt::white);
QPainter painter(&pixMap);
painter.translate(, );
paint(&painter, , );
painter.end(); drag->setPixmap(pixMap);
drag->setHotSpot(QPoint(, )); drag->exec();
setCursor(Qt::OpenHandCursor);
} void ColorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
}
目标窗口中的事件响应:
setAcceptDrops(true); 设置窗口的接收事件
void RobotHand::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
if (event->mimeData()->hasImage())
{
event->setAccepted(true);
m_bDragOver = true;
update();
}
else
{
Robot::dragEnterEvent(event);
}
} void RobotHand::dropEvent(QGraphicsSceneDragDropEvent *event)
{
m_bDragOver = false; const QMimeData* mime = event->mimeData();
if (mime->hasImage())
{
m_pixmap = qvariant_cast<QPixmap>(mime->imageData());
update();
}
else
{
Robot::dropEvent(event);
}
}
六、图元动画实现
(1)QPropertyAnimation
QPropertyAnimation类定义了Qt的属性动画,QPropertyAnimation以Qt属性做差值,作为属性值存储在QVariants中,该类继承自QVariantAnimation,并支持基类相同的元类型动画。声明属性的类必须是一个QObject,为了能够让属性可以用做动画效果,必须提供一个setter(这样,QPropertyAnimation才可以设置属性的值)。注意:这能够使它让许多Qt控件产生动画效果。
QPropertyAnimation类介绍:
class QPropertyAnimationPrivate;
class Q_CORE_EXPORT QPropertyAnimation : public QVariantAnimation
{
Q_OBJECT
Q_PROPERTY(QByteArray propertyName READ propertyName WRITE setPropertyName)
Q_PROPERTY(QObject* targetObject READ targetObject WRITE setTargetObject) public:
QPropertyAnimation(QObject *parent = Q_NULLPTR);
QPropertyAnimation(QObject *target, const QByteArray &propertyName, QObject *parent = Q_NULLPTR); // 对象指针、属性名
~QPropertyAnimation(); QObject *targetObject() const;
void setTargetObject(QObject *target); QByteArray propertyName() const;
void setPropertyName(const QByteArray &propertyName); protected:
bool event(QEvent *event) Q_DECL_OVERRIDE;
void updateCurrentValue(const QVariant &value) Q_DECL_OVERRIDE;
void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) Q_DECL_OVERRIDE; private:
Q_DISABLE_COPY(QPropertyAnimation)
Q_DECLARE_PRIVATE(QPropertyAnimation)
};
QVariantAnimation类属性:起始值、结束值、当前值、时间间隔
Q_PROPERTY(QVariant startValue READ startValue WRITE setStartValue)
Q_PROPERTY(QVariant endValue READ endValue WRITE setEndValue)
Q_PROPERTY(QVariant currentValue READ currentValue NOTIFY valueChanged)
Q_PROPERTY(int duration READ duration WRITE setDuration)
Q_PROPERTY(QEasingCurve easingCurve READ easingCurve WRITE setEasingCurve)
示例:实现图元的放大、缩小和旋转
QPropertyAnimation *headAnimation = new QPropertyAnimation(this, "rotation"); // 旋转属性
headAnimation->setStartValue();
headAnimation->setEndValue(-);
headAnimation->setDuration(2000);
QPropertyAnimation *headScaleAnimation = new QPropertyAnimation(this, "scale"); // 比例属性
headScaleAnimation->setEndValue(0.5);
headAnimation->setDuration(2000);
(2)QParallelAnimationGroup
QParallelAnimationGroup类提供动画的并行组。
QParallelAnimationGroup - 一个动画容器,当它启动的时候它里面的所有动画也启动,即:并行运行所有动画,当持续时间最长的动画完成时动画组也随之完成。
QParallelAnimationGroup *animation = new QParallelAnimationGroup(this);
animation->addAnimation(headAnimation);
animation->addAnimation(headScaleAnimation); for (int i = ; i < animation->animationCount(); ++i) {
QPropertyAnimation *anim = qobject_cast<QPropertyAnimation *>(animation->animationAt(i));
anim->setEasingCurve(QEasingCurve::SineCurve);
anim->setDuration();
} headAnimation->setLoopCount(-); // 无限循环
headAnimation->start();
七、程序效果



Qt图形视图体系结构示例解析(视图、拖拽、动画)的更多相关文章
- Qt之股票组件-自选股--列表可以拖拽、右键常用菜单
目录 一.开头嘴一嘴 二.效果展示 三.自选股列表 1.列表初始化 2.添加Item 3.右键菜单 4.拖拽Item 5.刷新数据 四.相关文章 原文链接:Qt之股票组件-自选股--列表可以拖拽.右键 ...
- Qt无边框窗体-最大化时支持拖拽还原
目录 一.概述 二.效果展示 三.demo制作 1.设计窗体 2.双击放大 四.拖拽 五.相关文章 原文链接:Markdown模板 一.概述 用Qt进行开发界面时,既想要实现友好的用户交互又想界面漂亮 ...
- ios-将代码创建的视图控件放入拖拽控件的下面
如图所示 图片是拖拽上去的imageView,橘黄色控件是在代码中创建的添加上去的,此时黄色view在imageView 上方 调用方法bringSubviewToFront:试图将imageView ...
- Qt::QWidget 无默认标题栏边框的拖拽修改大小方式
开发环境:win10+vs2015+qt5.9.1 背景:开发过程中,一般很少会使用系统提供的标题栏和边框:往往都是自定义一个自己设计的方案.这时候在QWidget中需要加上flag:Qt::Fram ...
- React Editor 应用编辑器(1) - 拖拽功能剖析
这是可视化编辑器 Gaea-Editor 的第一篇连载分析文章,希望我能在有限的篇幅讲清楚制作这个网页编辑器的动机,以及可能带来的美好使用前景(画大饼).它会具有如下几个特征: 运行在网页 文档流布局 ...
- Vue拖拽组件列表实现动态页面配置
需求描述 最近在做一个后台系统,有一个功能产品需求是页面分为左右两部分,通过右边的组件列表来动态配置左边的页面视图,并且左边由组件拼装起来的视图,可以实现上下拖拽改变顺序,也可以删除. 根据这个需求我 ...
- JS—实现拖拽
JS中的拖拽示例: 1)实现拖拽思路:当鼠标按下和拖拽过程中,鼠标与拖拽物体之间的相对距离保持不变 2)实现拖拽遇到的问题: 问题1:当鼠标按下移动过快时,离开了拖拽的物体时 ...
- Android Launcher拖拽事件详解【android4.0--Launcher系列二】
AndroidICS4.0版本的launcher拖 拽的流程,基本和2.3的相似.就是比2.3写的封装的接口多了一些,比如删除类的写法就多了个类.等等.4.0的改变有一些,但是不是特别大.这个月一 直 ...
- 开源自己写的一个拖拽库,兼容到IE8+
github地址:https://github.com/qiangzi7723/draggable 目前这个库的兼容做到了兼容IE8,所以如果需要兼容IE8的朋友不妨试试.库里面写了很多的注释,对于想 ...
随机推荐
- Mybatis之基础应用小结以及IntelliJ IDEA目录结构的一些小问题
IntelliJ IDEA 目录结构的一些小问题 [Mybatis 之基础应用小结] 1.不管怎么样,先建立一个简单的MySQL数据表,如下所示 2.接下来要做的事情就是通过Mybatis对数据表进行 ...
- systemverilog interface杂记
随着IC设计复杂度的提高,模块间互联变得复杂,SV引入接口,代表一捆连线的结构. Systemverilog语法标准,新引入一个重要的数据类型:interface. interface主要作用有两个: ...
- 20145201《Java程序设计》第五次实验报告
实验五 Java网络编程及安全 实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 我负责客户端 组队队员:鄢曼君20145227负责服务器 博客地址:htt ...
- 20145235李涛《网络对抗》Exp5 MSF基础应用
基础问答 用自己的话解释什么是exploit,payload,encode? exploit:相当于搬运工,把攻击代码传送到靶机中. payload:相当于shellcode. encode:相当于包 ...
- 线程的sleep()方法和yield()方法区别
1.sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会 2.yield()方法只会给相同优先级或更高优先级的线程以运行的机会 3.线程执行sleep()方法后 ...
- Sharding-Jdbc实现分表分库
Sharding-Jdbc分表分库LogicTable数据分片的逻辑表,对于水平拆分的数据库(表),同一类表的总称.订单信息表拆分为2张表,分别是t_order_0.t_order_1,他们的逻辑表名 ...
- 智能穿戴设备移动APP端与外设数据传输协议功能模块CMD&ACK表
Notification Module Function CMD ACK Notification History Count [0x0301] [0x0000] [0x01] [0x0301] [0 ...
- java中Vector类的常用方法
Vector类是实现List接口,所以继承的方法就不在这里讲了 https://www.cnblogs.com/xiaostudy/p/9503199.html public void add(int ...
- mybatis报错 Error instantiating interface com.atguigu.mybatis.dao.DepartmentMapper with invalid types () or values ()
mybatis报错 Error instantiating interface com.atguigu.mybatis.dao.DepartmentMapper with invalid types ...
- 报错cannot be cast to javassist.util.proxy.Proxy
出现这种错误还有一个原因:hibernate和struts中都有javassit的jar包,把其中的低版本删除就行了.