QGraphicsView 实现绘图标尺和刻度线特效
通过重写QGraphicsView 控件的'void paintEvent(QPaintEvent *event) override'事件实现绘制标尺和刻度线效果,直接绘制在控件上,不使用其他控件或图元
导读
在学习SVG的动画特效的时候,发现一个开源项目QtSVGEditor ,这个项目实现了SVG文件的修改保存,学习的时候发现这个项目里面的标尺特效写的很不错,很适合在QGraphicsView 控件中使用,所以学习借鉴后写了一个QGraphicsView版的标尺效果。
实际代码思路基本上是照抄的QtSVGEditor的,造轮子是不可能的,这辈子都不可能的。主要是修改和添加了QGraphicsView与QGraphicsScene之间的坐标转换。
实现效果
原理上基本就是重写
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);
}
示例源码
见文章绑定资源
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)