Qt图形视图体系结构示例解析(视图、拖拽、动画)

  本博的示例来自与QT Example:C:\Qt\Qt5.9.3\Examples\Qt-5.9.3\widgets\graphicsview\dragdroprobothtml

  将经过分析示例完成主要功能:安全

  (1)颜色图元绘制ide

  (2)机器人图元绘制函数

  (3)颜色图元的鼠标事件oop

  (4)机器人图元的DragDrop事件post

  (5)图元动画效果动画

1、颜色图元类实现

  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)

(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!"));

2、机器人头像图元类实现

  颜色图元的实现中已经了解了基本实现方法,机器人图元的实现也不例外,因为机器人包括不少图元部分(头、身体等),咱们能够采用面对对象继承的方式来实现。

  定义全部机器人图元的基类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);
    }
}

3、视图、场景类实现

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

4、颜色图元鼠标事件实现

  颜色图元的鼠标事件包括鼠标按下,鼠标移动和鼠标释放,要了解更详细的事件机制可阅读前面的博客: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);
}

5、拖拽事件实现

  在介绍如何实现拖拽事件以前先来了解两个类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);
    }
}

6、图元动画实现

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

7、程序效果