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

目录导读

    • 简述
      • [使用 QTimer 实现 QGraphicsSvgItem 边框动画效果](#使用 QTimer 实现 QGraphicsSvgItem 边框动画效果)

简述

在了解学习WPS的流程图的时候,发现它这个选择图元有个动态边框效果,而且连接线还会根据线生成点从头移动到尾的动画。像这种:

在QML中实现这种动画属性很简单,现成的动画属性,但是在QGraphicsView 中实现这种效果就值得思考一下,

在QT中SVG的动画属性只支持animateTransform 元素,其他动画元素是不支持。即使我把QT版本升级成 6.7.0版本 也不支持,

例如 animate 动画元素:

SVG文件:

xml 复制代码
<?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 自带的选中显示边框效果中的边框是外扩了一定像素,
    参考源码:
cpp 复制代码
//! 这是 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;

只需要以下内容:

cpp 复制代码
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)的大小递增递减才能看出完整效果。

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

实际效果:

完整代码:

TGraphicsSvgItem.h

cpp 复制代码
#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

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);
}
相关推荐
尘中远13 小时前
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
qt·数据可视化·qcustomplot·qwt·工业软件·科学绘图
sycmancia15 小时前
Qt——多线程间的互斥
开发语言·qt
尘中远21 小时前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远21 小时前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia21 小时前
Qt——进程与线程的概念
qt
郝学胜-神的一滴1 天前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
开发语言·c++·qt·程序人生·软件开发·用户界面
尘中远1 天前
【Qwt 7.0 系列】3D 数据可视化 —— OpenGL 高性能三维绘图
qt·3d·qcustomplot·qwt·科学绘图·高性能绘图
满天星83035771 天前
【Qt】控件(二) (geometry及与frameGeometry的区别)
开发语言·qt
大气的小蜜蜂1 天前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·qt·sqlite
尘中远1 天前
【Qwt 7.0 系列】总体架构解析 —— 从单体到三库模块化的演进
qt·matplotlib·绘图·qwt·科学绘图