本博的示例来自与QT Example:C:QtQt5.9.3ExamplesQt-5.9.3widgetsgraphicsviewdragdroprobot
将通过分析示例完成主要功能:
(1)颜色图元绘制
(2)机器人图元绘制
(3)颜色图元的鼠标事件
(4)机器人图元的DragDrop事件
(5)图元动画效果
一、颜色图元类实现
QGraphicsItem作为所有图元类的基类,自定义图元类需继承QGraohicsItem类,实现其基类的纯虚函数
virtual QRectF boundingRect() const = 0; virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) = 0;
boundingRect()设置图元的边界矩形范围,QGraphicsView使用此来确定图元是否需要重绘
paint()实现图元的绘制操作,一种方法是直接在paint中对图元进行绘制。另一种方法可以通过shape返回QPainterPath,然后在paint中依据QPainterPath进行绘制
该示例实现了随机的10中颜色图元,boundRect()为QRectF(-15,-15,30,30),图元的中心坐标为(0,0)
(1)自定义随机颜色
m_pColor(qrand() % 256, qrand() % 256, qrand() % 256)
(2)图元边界矩形设置
QRectF ColorItem::boundingRect() const { return QRectF(-15,-15,30,30); }
(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) %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 = 0) override; protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent *event) override; private: QPixmap m_pixmap; };
(1)边界矩形设置
QRectF RobotHand::boundingRect() const { return QRectF(-15, -15, 30,30); }
(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(130) : m_Color); //painter->drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt::RelativeSize); painter->drawRoundedRect(-15, -15, 30, 30, 25, 25, Qt::RelativeSize); painter->setBrush(Qt::white); painter->drawEllipse(-7, -12, 7,7); painter->drawEllipse(1, -12, 7,7); painter->setBrush(Qt::black); painter->drawEllipse(-5, -11, 2, 2); painter->drawEllipse(2, -11, 2, 2); painter->setPen(QPen(Qt::black, 2)); painter->setBrush(Qt::NoBrush); painter->drawArc(-6, -9, 12, 15, 190 * 16, 160 * 16); } else { painter->scale(.15, .15); painter->drawPixmap(QPointF(-15 * 4.4, -30 * 3.54), m_pixmap); } }
三、视图、场景类实现
(1)场景设置
QGraphicsScene* m_pScene; m_pScene = new QGraphicsScene(QRectF(-150,-150,300,300));
添加图元:
for (int i = 0; i < 10; i ++) { ColorItem *item = new ColorItem; item->setPos(qCos((i / 10.0) *6.28) * 100,qSin((i / 10.0) *6.28) * 100); if(i == 0) { item->setData(ColorItem::COLOR_TYPE,"pixmap"); } m_pScene->addItem(item); } Robot* pRobot = new RobotHand; pRobot->setPos(-10,-30); 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(230, 200, 167)); 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(30,30); pixMap.fill(Qt::white); QPainter painter(&pixMap); painter.translate(15, 15); paint(&painter, 0, 0); painter.end(); drag->setPixmap(pixMap); drag->setHotSpot(QPoint(15, 15)); 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(30,30); pixMap.fill(Qt::white); QPainter painter(&pixMap); painter.translate(15, 15); paint(&painter, 0, 0); painter.end(); drag->setPixmap(pixMap); drag->setHotSpot(QPoint(15, 15)); 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(30,30); pixMap.fill(Qt::white); QPainter painter(&pixMap); painter.translate(15, 15); paint(&painter, 0, 0); painter.end(); drag->setPixmap(pixMap); drag->setHotSpot(QPoint(15, 15)); 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(30); headAnimation->setEndValue(-30);
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 = 0; i < animation->animationCount(); ++i) { QPropertyAnimation *anim = qobject_cast<QPropertyAnimation *>(animation->animationAt(i)); anim->setEasingCurve(QEasingCurve::SineCurve); anim->setDuration(2000); } headAnimation->setLoopCount(-1); // 无限循环 headAnimation->start();