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 = 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();

七、程序效果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM