QT实现QGraphicsView绘图 重写QGraphicsSvgItem类实现边框动画

简述

在了解学习WPS的流程图的时候,发现它这个选择图元有个动态边框效果,而且连接线还会根据线生成点从头移动到尾的动画。像这种:
请添加图片描述
在QML中实现这种动画属性很简单,现成的动画属性,但是在QGraphicsView中实现这种效果就值得思考一下,
在QT中SVG的动画属性只支持animateTransform元素,其他动画元素是不支持。即使我把QT版本升级成 6.7.0版本 也不支持,
例如 animate 动画元素:
请添加图片描述
SVG文件:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">

<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink/" baseProfile="tiny" version="1.2">
  <title>Spheres</title>
  <path id="dashedSquare" d="M10,10 L190,10 L190,190 L10,190 Z M10,10" stroke="black" stroke-width="2" fill="none" stroke-dasharray="10,5" stroke-dashoffset="0" stroke-linecap="round">
    <animate  id="dashAnimate" attributeName="stroke-dashoffset" from="200" to="0" dur="2s" begin="0s"  repeatCount="indefinite" />
    <animate  id="resetAnimate" attributeName="stroke-dashoffset" from="0" to="200" dur="2s"  begin="dashAnimate.end" fill="freeze" />
</path>
</svg>

这个SVG是实现了边框的动态效果;
但是在QGraphicsViewQGraphicsSvgItem中是不会有动画效果的,包括QSvgWidget等其他Svg相关的控件也没有,只能尝试其他办法。。

  • 使用 QTimer 实现 QGraphicsSvgItem 边框动画效果

使用QT的 QTimer 类 实现 QGraphicsSvgItem 边框动画,
通过研究上面的SVG可以发现,边框的动画效果实际是stroke-dashoffset 属性的变动,也可以通过
QPen 类的 setDashOffset(qreal offset) 变动,
所以在
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
绘制图元时,只要使用 QTimer 周期性将画笔的DashOffset值来回的修改就可以了,

值得注意的是

  1. 通过研究 QGraphicsSvgItem类源码中的 paint 事件发现 QGraphicsItem自带的选中显示边框效果中的边框是外扩了一定像素,
    参考源码:
//! 这是 QGraphicsItem 自带选中边框效果的绘制边框
//! 源码参考路径:  D:\Qt\Qt5.13.1\5.13.1\Src\qtsvg\src\svg\qgraphicssvgitem.cpp
//! \internal
//! Highlights \a item as selected.
//! NOTE: This function is a duplicate of qt_graphicsItem_highlightSelected() in qgraphicsitem.cpp!

static void qt_graphicsItem_highlightSelected(
    QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option)
{
    const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1));
    if (qFuzzyIsNull(qMax(murect.width(), murect.height())))
        return;

    const QRectF mbrect = painter->transform().mapRect(item->boundingRect());
    if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0))
        return;

    qreal itemPenWidth;
    switch (item->type()) {
        case QGraphicsEllipseItem::Type:
            itemPenWidth = static_cast<QGraphicsEllipseItem *>(item)->pen().widthF();
            break;
        case QGraphicsPathItem::Type:
            itemPenWidth = static_cast<QGraphicsPathItem *>(item)->pen().widthF();
            break;
        case QGraphicsPolygonItem::Type:
            itemPenWidth = static_cast<QGraphicsPolygonItem *>(item)->pen().widthF();
            break;
        case QGraphicsRectItem::Type:
            itemPenWidth = static_cast<QGraphicsRectItem *>(item)->pen().widthF();
            break;
        case QGraphicsSimpleTextItem::Type:
            itemPenWidth = static_cast<QGraphicsSimpleTextItem *>(item)->pen().widthF();
            break;
        case QGraphicsLineItem::Type:
            itemPenWidth = static_cast<QGraphicsLineItem *>(item)->pen().widthF();
            break;
        default:
            itemPenWidth = 1.0;
    }
    const qreal pad = itemPenWidth / 2;

    const qreal penWidth = 0; // cosmetic pen

    const QColor fgcolor = option->palette.windowText().color();
    const QColor bgcolor( // ensure good contrast against fgcolor
        fgcolor.red()   > 127 ? 0 : 255,
        fgcolor.green() > 127 ? 0 : 255,
        fgcolor.blue()  > 127 ? 0 : 255);

    painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine));
    painter->setBrush(Qt::NoBrush);
    painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad));

    painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine));
    painter->setBrush(Qt::NoBrush);
    painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad));
}

所以在重写
void QGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
事件的时候,不能继承原有paint;
只需要以下内容:

void TGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
   if (!renderer()->isValid())
         return;
     if (elementId().isEmpty())
         renderer()->render(painter, boundingRect());
     else
         renderer()->render(painter, elementId(), boundingRect());
         
	 if(isSelected())
	  {
	       QPen pen2(QColor("#067BEF"), 2);
	       pen2.setDashPattern(QVector<qreal>{2,2}); // 设置虚线模式
	       pen2.setDashOffset(dashoffset);
	       painter->setPen(pen2);
	       painter->drawRect(shape().boundingRect()); // 绘制正方形
	   }
}

2.不使用QPropertyAnimation类而是用QTimer 类实现DashOffset值周期性变化,
QPropertyAnimation类 变化的差值是线性的,看不出边框虚线效果。
需要设置按指定步长(10)的大小递增递减才能看出完整效果。

timer = new QTimer();
    QObject::connect(timer, &QTimer::timeout,[&](){
        dashoffset-=10;
        if(dashoffset<=0)
        {
            dashoffset=100;
        }
        this->update();
    });

实际效果:
请添加图片描述
完整代码:

TGraphicsSvgItem.h

#ifndef TGRAPHICSSVGITEM_H
#define TGRAPHICSSVGITEM_H

#include <QGraphicsItem>
#include <QGraphicsSvgItem>
#include <QTimer>
#include <QPainter>
#include <QSvgRenderer>

#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QSequentialAnimationGroup>
#include "ParseSvg/lib_csvgelementpath.h"
#include "tgraphicspointitem.h"


//! D:\Qt\Qt5.13.1\5.13.1\Src\qtsvg\src\svg\qgraphicssvgitem.cpp
//!
class TGraphicsSvgItem:public QGraphicsSvgItem
{
    Q_OBJECT
public:
    TGraphicsSvgItem(QGraphicsItem *parentItem = nullptr);
    TGraphicsSvgItem(const QString &fileName, QGraphicsItem *parentItem = nullptr);

public slots:

    //! 初始化计时器
    void Init_timer();
protected:
//    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:

    qreal dashoffset=100;
   
    //! 虚边框线
    QTimer* timer;
};

#endif // TGRAPHICSSVGITEM_H

TGraphicsSvgItem.cpp

#include "tgraphicssvgitem.h"
#include <QDebug>
#include <QPropertyAnimation>
#include <QTimeLine>

TGraphicsSvgItem::TGraphicsSvgItem(QGraphicsItem *parentItem)
    :QGraphicsSvgItem(parentItem)
{
    this->setAcceptHoverEvents(true);
    Init_timer();
}

TGraphicsSvgItem::TGraphicsSvgItem(const QString &fileName, QGraphicsItem *parentItem)
    :QGraphicsSvgItem(fileName,parentItem)
{
    this->setAcceptHoverEvents(true);
    Init_timer();


}

void TGraphicsSvgItem::Init_timer()
{

    this->setFlag(TGraphicsSvgItem::ItemIsMovable, true);
    this->setFlag(TGraphicsSvgItem::ItemIsSelectable, true);
    this->setFlag(TGraphicsSvgItem::ItemClipsToShape,true);
    timer = new QTimer();
    QObject::connect(timer, &QTimer::timeout,[&](){
        dashoffset-=10;
        if(dashoffset<=0)
        {
            dashoffset=100;
        }
        this->update();
    });
}

//QRectF TGraphicsSvgItem::boundingRect() const {
//    return shape().boundingRect();
//    return this->renderer()->boundsOnElement("shadow");
//}

void TGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
        if (!renderer()->isValid())
            return;

        if (elementId().isEmpty())
            renderer()->render(painter, boundingRect());
        else
            renderer()->render(painter, elementId(), boundingRect());


        if(isSelected())
        {
            QPen pen2(QColor("#067BEF"), 2);
            pen2.setDashPattern(QVector<qreal>{2,2}); // 设置虚线模式
            pen2.setDashOffset(dashoffset);
            painter->setPen(pen2);
            painter->drawRect(shape().boundingRect()); // 绘制正方形
        }

}

QVariant TGraphicsSvgItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if(change==QGraphicsSvgItem::ItemSelectedChange)
    {
        if(value.toUInt()==1)
        {
            dashoffset=100;
            if(timer!=nullptr && timer!=NULL)
                timer->start(100);
        }
        else
        {
            dashoffset=100;
            if(timer!=nullptr && timer!=NULL)
                timer->stop();
        }
    }
    return QGraphicsSvgItem::itemChange(change,value);
}
  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Qt中,QGraphicsView是用来显示和处理大型场景的视图。要实现鼠标绘图,可以通过以下步骤进行: 1. 创建QGraphicsView实例和一个场景(QGraphicsScene)对象,并将场景设置给视图。 2. 创建一个继承自QGraphicsItem的自定义图形项,用于表示绘图的形状。 3. 在自定义图形项中,重写mousePressEvent和mouseMoveEvent两个事件处理函数,用于捕捉鼠标按下和移动的事件。 4. 在mousePressEvent中,记录鼠标按下的位置,并创建一个新的图形项对象。 5. 在mouseMoveEvent中,根据鼠标移动的位置,更新图形项对象的形状。 6. 将新创建的图形项对象添加到场景中,并使用QGraphicsScene::addItem()函数进行添加。 7. 最后,将场景设置给QGraphicsView,并调用QGraphicsView::show()函数显示视图。 以下是一个简单示例代码: ```cpp #include <QtWidgets> class CustomGraphicsItem : public QGraphicsItem { public: CustomGraphicsItem() : QGraphicsItem() { setFlag(ItemIsMovable); setAcceptHoverEvents(true); } QRectF boundingRect() const override { return QRectF(0, 0, 100, 100); } void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { painter->drawEllipse(boundingRect()); } void mousePressEvent(QGraphicsSceneMouseEvent* event) override { if (event->button() == Qt::LeftButton) { QPointF position = event->pos(); qDebug() << "Mouse press at:" << position; } } void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override { setPos(event->pos()); } }; int main(int argc, char* argv[]) { QApplication app(argc, argv); QGraphicsScene scene; QGraphicsView view(&scene); CustomGraphicsItem* item = new CustomGraphicsItem(); scene.addItem(item); view.show(); return app.exec(); } ``` 这是一个简单的绘图程序,可以在视图中点击鼠标左键,然后拖动鼠标绘制一个圆形图形项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

得鹿梦鱼、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值