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


      本博的示例来自与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();

    七、程序效果

  • 相关阅读:
    Codeforces Round #535 (Div. 3)
    2019 CCPC-Wannafly Winter Camp Day4(Div2, onsite)
    Codeforces Round #534 (Div. 2)
    2019 CCPC-Wannafly Winter Camp Day3(Div2, onsite)
    2019 CCPC-Wannafly Winter Camp Day2(Div2, onsite)
    2019 CCPC-Wannafly Winter Camp Day1 (Div2, onsite)
    codeforces1097D Makoto and a Blackboard 数学+期望dp
    【NOIP2016】换教室
    ICPC2019徐州站游记
    【Codeforces】Orz Panda Cup
  • 原文地址:https://www.cnblogs.com/xiaobingqianrui/p/9552917.html
Copyright © 2020-2023  润新知