本博的示例來自與QT Example:C:\Qt\Qt5.9.3\Examples\Qt-5.9.3\widgets\graphicsview\dragdroprobothtml
將經過分析示例完成主要功能:安全
(1)顏色圖元繪製ide
(2)機器人圖元繪製函數
(3)顏色圖元的鼠標事件oop
(4)機器人圖元的DragDrop事件post
(5)圖元動畫效果動畫
QGraphicsItem做爲全部圖元類的基類,自定義圖元類需繼承QGraohicsItem類,實現其基類的純虛函數ui
virtual QRectF boundingRect() const = 0; virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) = 0;
boundingRect()設置圖元的邊界矩形範圍,QGraphicsView使用此來肯定圖元是否須要重繪this
paint()實現圖元的繪製操做,一種方法是直接在paint中對圖元進行繪製。另外一種方法能夠經過shape返回QPainterPath,而後在paint中依據QPainterPath進行繪製url
該示例實現了隨機的10中顏色圖元,boundRect()爲QRectF(-15,-15,30,30),圖元的中心座標爲(0,0)
m_pColor(qrand() % 256, qrand() % 256, qrand() % 256)
QRectF ColorItem::boundingRect() const { return QRectF(-15,-15,30,30); }
void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->setBrush(m_pColor); painter->drawEllipse(boundingRect()); }
當鼠標進入圖元或是拖動圖元時設置光標形狀,光標形狀查看枚舉類型:CursorShape
setCursor(Qt::OpenHandCursor);
setAcceptedMouseButtons(Qt::LeftButton);
當鼠標進入圖元時顯示提示內容:
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 = 0) override; protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override; void dropEvent(QGraphicsSceneDragDropEvent *event) override; private: QPixmap m_pixmap; };
QRectF RobotHand::boundingRect() const { return QRectF(-15, -15, 30,30); }
當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); } }
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);
自定義視圖:
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
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(); }
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); }
在源窗口中的事件響應:
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); } }
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);
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();