本博的示例來自與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 = 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)\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; };
(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();