导读

在学习SVG的动画特效的时候,发现一个开源项目QtSVGEditor ,这个项目实现了SVG文件的修改保存,学习的时候发现这个项目里面的标尺特效写的很不错,很适合在QGraphicsView 控件中使用,所以学习借鉴后写了一个QGraphicsView版的标尺效果。
实际代码思路基本上是照抄的QtSVGEditor的,造轮子是不可能的,这辈子都不可能的 。主要是修改和添加了QGraphicsViewQGraphicsScene之间的坐标转换。

实现效果

请添加图片描述
原理上基本就是重写
void paintEvent(QPaintEvent *event) override;事件,将标尺和刻度线通过QPainter绘制到 viewport();
需要注意的是:
必须先绘制场景QGraphicsView::paintEvent(event) 内容,
再绘制标尺刻度线,要不然会被QGraphicsScene 场景内容直接覆盖掉,这也导致了刻度线会直接显示在QGraphicsItem 图元上方。因为QGraphicsItem 图元的绘制是在QGraphicsView 视窗中实现的,
如果需要 QGraphicsItem 图元位于上方,就得重新实现QGraphicsView::paintEvent原来的绘制。

具体实现

绘制上标尺(横向)

在这里插入图片描述

整体思路是:
计算QGraphicsView视窗控件宽度在QGraphicsScene场景的坐标,在计算一个缩放比例和标尺步长,然后再从0向右递增,从0向左递减的计算场景坐标,再换算成视窗坐标。

void TGraphicsViewRefactor::DrawHorizonRuler(QPainter &painter)
{
    QPoint lefttop= QPoint(0,0);
    QPoint rigthtop=QPoint(this->width(),0);

    //! 转换成 QGraphicsScene 坐标
    QPointF scene_lefttop=mapToScene(lefttop);
    QPointF scene_rigthtop=mapToScene(rigthtop);

	//! 获取缩放比例
    float fscale = (scene_rigthtop.x() - scene_lefttop.x())*1.0 / this->width();
    //! 获取步长
    int  nDistance = 100;
    nDistance = ((1.0 / fscale * 100) / 10) * 10;
    if (nDistance > 50 && nDistance < 150)
    {
        nDistance = 100;
    }
    if (nDistance >= 150 && nDistance < 200)
    {
        nDistance = 200;
    }
//    float fDistance = nDistance * 1.0*fscale;

    //! 抹除 上边框 20*width 内容
    painter.setPen(Qt::white);
    painter.setBrush(Qt::white);
    QRect rc(0, 0, this->width(), 20);
    painter.drawRect(rc);
    //! 画下边框线  --为了不出现上 左 右 三条边
    painter.setPen(Qt::black);
    painter.setBrush(Qt::white);
    painter.drawLine(QPoint(20,20),QPoint(this->width(),20));


    QPolygon Rightpoints;
    int nIndex = 0;
    for (int i =0; i < scene_rigthtop.x(); i += nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nxPt = i + j * (nDistance / 10);
            QPoint viewpointX=mapFromScene(nxPt,0);
            if (viewpointX.x() < 20) //! 为了左上角的符号
                continue;
            int nyPt = 0;
            if (j == 0)
            {
                nyPt = 0;
            }
            else if (j % 5 == 0)
            {
                nyPt = 10;
            }
            else
            {
                nyPt = 15;
            }
            Rightpoints.push_back(QPoint(viewpointX.x(), 20));
            Rightpoints.push_back(QPoint(viewpointX.x(), nyPt));
            Rightpoints.push_back(QPoint(viewpointX.x(), 20));
        }
        if (i >= 0)
        {
            //! 向右偏两像素 避免被遮挡
            QPoint viewpointX=mapFromScene(i,0);
            painter.setBrush(Qt::black);
            painter.drawText(QPoint(viewpointX.x()+2, 14), QString::number(nIndex*nDistance));
        }
        nIndex++;
    }

    //
    QPolygon Leftpoints;
    nIndex = 0;
    for (int i =0; i > scene_lefttop.x(); i -= nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nxPt = i - j * (nDistance / 10);
            QPoint viewpointX=mapFromScene(nxPt,0);
            if (viewpointX.x() < 20) //! 为了左上角的符号
                continue;
            int nyPt = 0;
            if (j == 0)
            {
                nyPt = 0;
            }
            else if (j % 5 == 0)
            {
                nyPt = 10;
            }
            else
            {
                nyPt = 15;
            }
            Leftpoints.push_front(QPoint(viewpointX.x(), 20));
            Leftpoints.push_front(QPoint(viewpointX.x(), nyPt));
            Leftpoints.push_front(QPoint(viewpointX.x(), 20));
        }
        if (i <= 0)
        {
            QPoint viewpointX=mapFromScene(i,0);
            painter.setBrush(Qt::black);
            //! 向右偏两像素 避免负号被遮挡
            painter.drawText(QPoint(viewpointX.x()+2, 14), QString::number(-nIndex * nDistance));
        }
        nIndex++;
    }

    painter.setPen(Qt::black);
    painter.setBrush(Qt::white);
    painter.drawPolyline(Rightpoints);
    painter.drawPolyline(Leftpoints);

}

绘制左标尺(纵向)

绘制左标尺 与绘制上标尺一样,只是X坐标改为Y坐标
在这里插入图片描述

void TGraphicsViewRefactor::DrawVerticalRuler(QPainter &painter)
{
    QPoint lefttop= QPoint(0,0);
    QPoint leftbtn =QPoint(this->width(),this->height());

    //! 转换成 QGraphicsScene 坐标
    QPointF scene_lefttop=mapToScene(lefttop);
    QPointF scene_leftbtn=mapToScene(leftbtn);


    //! 还是以宽 为标准
    float fscale = (scene_leftbtn.x() - scene_lefttop.x())*1.0 / this->width();

    //! 步长
    int  nDistance = 100;
    nDistance = ((1.0 / fscale * 100) / 10) * 10;
    if (nDistance > 50 && nDistance < 150)
    {
        nDistance = 100;
    }
    if (nDistance >= 150 && nDistance < 200)
    {
        nDistance = 200;
    }

    //! 抹除 左边框 20*height 内容
    painter.setPen(Qt::white);
    painter.setBrush(Qt::white);
    QRect rc(0, 0, 20, this->height());
    painter.drawRect(rc);
    //! 画右边框线  --为了不出现上 左 下 三条边
    painter.setPen(Qt::black);
    painter.setBrush(Qt::white);
    painter.drawLine(QPoint(20,0),QPoint(20,this->height()));

    //! 从 0 往下
    QPolygon Bottompoints;
    int nIndex = 0;
    for (int i = 0; i < scene_leftbtn.y(); i += nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nyPt = i + j * (nDistance / 10);
            QPoint viewpointY=mapFromScene(QPointF(0,nyPt));
            if (viewpointY.y() < 20)
                continue;
            int nxPt = 0;
            if (j == 0)
            {
                nxPt = 0;
            }
            else if (j % 5 == 0)
            {
                nxPt = 10;
            }
            else
            {
                nxPt = 15;
            }
            Bottompoints.push_back(QPoint(20, viewpointY.y()));
            Bottompoints.push_back(QPoint(nxPt, viewpointY.y()));
            Bottompoints.push_back(QPoint(20, viewpointY.y()));
        }
        if (i >= 0)
        {
            QPoint viewpointY=mapFromScene(QPointF(0,i));
            painter.setBrush(Qt::black);
            QMatrix mat;
            mat.translate(2, (viewpointY.y() + 2));
            mat.rotate(90);
            mat.translate(2, -(viewpointY.y() + 2));
            painter.setMatrix(mat);
            painter.drawText(QPoint(2, viewpointY.y() + 2), QString::number(nIndex*nDistance));
            painter.resetMatrix();
        }
        nIndex++;
    }


    //
    //! 从 0 向上
    QPolygon Toppoints;
    nIndex = 0;
    for (int i = 0; i >= scene_lefttop.y(); i -= nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nyPt = i - j * (nDistance / 10);
            QPoint viewpointY=mapFromScene(QPointF(0,nyPt));
            if (viewpointY.y() < 20)
                continue;
            int nxPt = 0;
            if (j == 0)
            {
                nxPt = 0;
            }
            else if (j % 5 == 0)
            {
                nxPt = 10;
            }
            else
            {
                nxPt = 15;
            }

            Toppoints.push_front(QPoint(20, viewpointY.y()));
            Toppoints.push_front(QPoint(nxPt, viewpointY.y()));
            Toppoints.push_front(QPoint(20, viewpointY.y()));
        }
        if (i <= 0)
        {
            QPoint viewpointY=mapFromScene(QPointF(0,i));
            painter.setBrush(Qt::black);
            QMatrix mat;
            mat.translate(2, (viewpointY.y() + 2));
            mat.rotate(90);
            mat.translate(2, -(viewpointY.y() + 2));
            painter.setMatrix(mat);
            painter.drawText(QPoint(2, viewpointY.y() + 2), QString::number(-nIndex * nDistance));
            painter.resetMatrix();
        }
        nIndex++;
    }


    //
    //! 绘制线段
    QPen pen(Qt::black, 1, Qt::SolidLine);
    painter.setPen(pen);
    painter.setBrush(Qt::white);
    painter.drawPolyline(Toppoints);
    painter.drawPolyline(Bottompoints);
}

绘制左上角十字效果

上标尺和左标尺的高度,宽度都为20,所以就是再一个20*20的矩形中画两条横线。
在这里插入图片描述

void TGraphicsViewRefactor::DrawRuleCross(QPainter &painter)
{
    painter.setBrush(QColor(255, 255, 255, 255));
    painter.setPen(Qt::white);
    painter.drawRect(QRect(0, 0, 20, 20));
    painter.setPen(Qt::DashLine);
    painter.drawLine(QPoint(0, 10), QPoint(20, 10));
    painter.drawLine(QPoint(10, 0), QPoint(10, 20));
    painter.setPen(Qt::SolidLine);
    painter.drawLine(QPoint(0, 20), QPoint(20, 20));
    painter.drawLine(QPoint(20, 0), QPoint(20, 20));
}

绘制竖直的刻度线

绘制向下的刻度线,整点交错这种效果是通过修改画笔的透明度实现的,
其他的都没变,包括计算缩放比例和步长,
在这里插入图片描述

void TGraphicsViewRefactor::DrawHorGridLine(QPainter &painter)
{
    QPoint lefttop= QPoint(0,0);
    QPoint righttop =QPoint(this->width(),0);

    //! 转换成 QGraphicsScene 坐标
    QPointF scene_lefttop=mapToScene(lefttop);
    QPointF scene_righttop=mapToScene(righttop);


    //! 还是以宽 为标准
    float fscale = (scene_righttop.x() - scene_lefttop.x())*1.0 / this->width();

    //! 步长
    int  nDistance = 100;
    nDistance = ((1.0 / fscale * 100) / 10) * 10;
    if (nDistance > 50 && nDistance < 150)
    {
        nDistance = 100;
    }
    if (nDistance >= 150 && nDistance < 200)
    {
        nDistance = 200;
    }

    QPolygon BoldPoints;
    QPolygon ThinPoints;

    //! 从 0 -> ∞
    for (int i = 0; i < scene_righttop.x(); i += nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nxPt = i + j * (nDistance / 10);
            QPoint viewpointX=mapFromScene(nxPt,0);
            if (viewpointX.x() < 20)
                continue;
            if (j == 0)
            {
                BoldPoints.push_back(QPoint(viewpointX.x(), 20));
                BoldPoints.push_back(QPoint(viewpointX.x(), this->height()));
                BoldPoints.push_back(QPoint(viewpointX.x(), 20));
            }
            else
            {
                ThinPoints.push_back(QPoint(viewpointX.x(), 20));
                ThinPoints.push_back(QPoint(viewpointX.x(), this->height()));
                ThinPoints.push_back(QPoint(viewpointX.x(), 20));
            }
        }
    }

    //! 从 0 到 负无穷
    for (int i = 0; i >= scene_lefttop.x(); i -= nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nxPt = i - j * (nDistance / 10);
            QPoint viewpointX=mapFromScene(nxPt,0);
            if (viewpointX.x() < 20)
                continue;
            if (j == 0)
            {
                BoldPoints.push_back(QPoint(viewpointX.x(), 20));
                BoldPoints.push_back(QPoint(viewpointX.x(), this->height()));
                BoldPoints.push_back(QPoint(viewpointX.x(), 20));
            }
            else
            {
                ThinPoints.push_back(QPoint(viewpointX.x(), 20));
                ThinPoints.push_back(QPoint(viewpointX.x(), this->height()));
                ThinPoints.push_back(QPoint(viewpointX.x(), 20));
            }
        }
    }

    QPen boldpen(QBrush(QColor(0, 0, 0, 50)), 1);
    painter.setPen(boldpen);
    painter.drawPolyline(BoldPoints);
    QPen thinpen(QBrush(QColor(0, 0, 0, 25)), 1);
    painter.setPen(thinpen);
    painter.drawPolyline(ThinPoints);
}

绘制横线的刻度线

和绘制竖直的刻度线一样,只是坐标替换成了Y轴
在这里插入图片描述

void TGraphicsViewRefactor::DrawVerGridLine(QPainter &painter)
{
    QPoint lefttop= QPoint(0,0);
    QPoint leftbtn =QPoint(this->width(),this->height());

    //! 转换成 QGraphicsScene 坐标
    QPointF scene_lefttop=mapToScene(lefttop);
    QPointF scene_leftbtn=mapToScene(leftbtn);
    //! 还是以宽 为标准
    float fscale = (scene_leftbtn.x() - scene_lefttop.x())*1.0 / this->width();

    //! 步长
    int  nDistance = 100;
    nDistance = ((1.0 / fscale * 100) / 10) * 10;
    if (nDistance > 50 && nDistance < 150)
    {
        nDistance = 100;
    }
    if (nDistance >= 150 && nDistance < 200)
    {
        nDistance = 200;
    }

    QPolygon BoldPoints;
    QPolygon ThinPoints;

    //! 从 0 向下 前进
    for (int i = 0; i < scene_leftbtn.y(); i += nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nyPt = i + j * (nDistance / 10);
            QPoint viewpointY=mapFromScene(0,nyPt);
            if (viewpointY.y() < 20)
                continue;
            if (j == 0)
            {
                BoldPoints.push_back(QPoint(20, viewpointY.y()));
                BoldPoints.push_back(QPoint(this->width(), viewpointY.y()));
                BoldPoints.push_back(QPoint(20, viewpointY.y()));
            }
            else
            {
                ThinPoints.push_back(QPoint(20, viewpointY.y()));
                ThinPoints.push_back(QPoint(this->width(), viewpointY.y()));
                ThinPoints.push_back(QPoint(20, viewpointY.y()));
            }
        }
    }

    //! 从0 向上 移动
    for (int i = 0; i >= scene_lefttop.y(); i -= nDistance)
    {
        for (int j = 0; j < 10; ++j)
        {
            int nyPt = i - j * (nDistance / 10);
            QPoint viewpointY=mapFromScene(0,nyPt);
            if (viewpointY.y() < 20)
                continue;
            if (j == 0)
            {
                BoldPoints.push_back(QPoint(20, viewpointY.y()));
                BoldPoints.push_back(QPoint(this->width(), viewpointY.y()));
                BoldPoints.push_back(QPoint(20, viewpointY.y()));
            }
            else
            {
                ThinPoints.push_back(QPoint(20, viewpointY.y()));
                ThinPoints.push_back(QPoint(this->width(), viewpointY.y()));
                ThinPoints.push_back(QPoint(20, viewpointY.y()));
            }
        }
    }

    QPen boldpen(QBrush(QColor(0, 0, 0, 50)), 1);
    painter.setPen(boldpen);
    painter.drawPolyline(BoldPoints);
    QPen thinpen(QBrush(QColor(0, 0, 0, 25)), 1);
    painter.setPen(thinpen);
    painter.drawPolyline(ThinPoints);
}

重写QGraphicsView::paintEvent 事件

如果有需要可以通过其他属性值来判断是否显示上方和左侧的标尺,刻度线等。
在绘制标尺的时候是清除过上方宽度*20和左侧高度*20的内容的,如果需要标尺透明注释掉绘制就可以了。

void TGraphicsViewRefactor::paintEvent(QPaintEvent *event)
{
//    qDebug()<<"paintEvent ------>";
//    QPainter painter(this->viewport()); // this指向QWidget,也可以是QPixmap等

//    painter.begin(this); // 调用begin()方法
//    DrawHorizonRuler(painter);
//    painter.end();



    //! 先绘制 scene() 再绘制边框 防止覆盖
    //! 绘制QGraphicsItem
    QGraphicsView::paintEvent(event);

    //! 绘制标尺
    QPainter paint(this->viewport());//这行很重要,没有这行的话,绘制出来的东西都会被view里面的其他东西遮住
    //! 每次都重复计算了步长和缩放比例D
    //! 上边 -X轴
    DrawHorizonRuler(paint);
    //! 左侧 -Y轴
    DrawVerticalRuler(paint);
    //! 左上 十符号
    DrawRuleCross(paint);
    //! 竖线
    DrawHorGridLine(paint);
    //! 横线
    DrawVerGridLine(paint);
    //!绘制 面板
    //DrawCanvasRect(paint);
}

示例源码

见文章绑定资源

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐