QGraphicsLineItem 绘制箭头连接类 --方法及踩坑(问题汇总)

在使用QGraphicsView架构制作流程图类的界面时,需要用带箭头的连线将两个item连接起来,来表明连接关系,本文介绍了箭头连接类的方法和相关问题

一.实现方法

架构自身为我们提供了QGraphicsItem的衍生子类QGraphicsLineItem,构造时传入两个点即可生成:

QGraphicsLineItem *line = new QGraphicsLineItem(x1, y1, x2, y2);

将line添加到scene中即可显示连线,但这样做有几个缺点:

  • 它只是一根线,而我们想要的是带有箭头的线
  • 传入的位置坐标已经固定,如果我们的item改变位置,连线不会改变,不具有灵活性

我的解决方法是在构造时绑定两个item,在paint()函数中实时获得它们的坐标作为线的起点和终点

class Arrow : public QGraphicsLineItem {
public:
    Arrow(QGraphicsItem *startItem, QGraphicsItem *endItem);

private:
    QGraphicsItem *m_startItem;
    QGraphicsItem *m_endItem;

 核心部分为重写的paint函数,需要获取最新的起点和终点坐标,并且根据这两个坐标补出两侧箭头的坐标 

void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
    Q_UNUSED(option);
    Q_UNUSED(widget);

    // 设置画笔
    QPen pen(Qt::black); // 可以根据需要修改颜色
    painter->setPen(pen);
    
    // 获取起始和结束点
    QPointF startPoint = m_startItem->sceneBoundingRect().center();
    QPointF endPoint = m_endItem->sceneBoundingRect().center();
    
    // 绘制线条
    painter->drawLine(startPoint, endPoint);

    //不加入可能会影响刷新
    setLine(startPoint,endPoint);
    
    // 计算箭头头部的位置和方向
    double angle = std::atan2(endPoint.y() - startPoint.y(), endPoint.x() - startPoint.x());
    QPointF arrowHead1 = endPoint - QPointF(sin(angle + M_PI / 6) * 10, cos(angle + M_PI / 6) * 10);
    QPointF arrowHead2 = endPoint - QPointF(sin(angle - M_PI / 6) * 10, cos(angle - M_PI / 6) * 10);
    
    // 绘制箭头头部
    painter->drawLine(endPoint, arrowHead1);
    painter->drawLine(endPoint, arrowHead2);
}

在获得起点和终点时,可以在item里设置一个函数专门返回连接点的坐标,这点根据需求灵活改变。

这个类一共有三条线,主体直线一条,两侧的箭头各一条,共需要四个点。主要难点部分在计算主线段两侧的箭头点,我在项目中使用的是下面的计算方法:

//起点坐标(x1,y1) ,终点坐标(x2,y2)
double length=10; //箭头长度
double angle=0.5;  //箭头与主线段的角度

float x3=x2-length*cos(atan2((y2-y1),(x2-x1))-angle);
float y3=y2-length*sin(atan2((y2-y1),(x2-x1))-angle);
float x4=x2-length*sin(atan2((x2-x1),(y2-y1))-angle);
float y4=y2-length*cos(atan2((x2-x1),(y2-y1))-angle);

painter->drawLine(x2,y2,x3,y3);
painter->drawLine(x2,y2,x4,y4);

完成以上步骤后,我们得到一个箭头类,它可以被添加到scene中并显示出来,但这其实这是第一步,后面有很多的细节需要优化。

二.相关问题及解决方法

1.连线刷新时有残留的像素  

在拖动item让连线刷新的时候,会发现拖动时会有干扰的像素存在scene中,而当拖动放开的时候,那些残余的像素的又会消失,十分影响体验。这其实是因为boundingRect没有重新实现,需要在其中向外扩充一点。其实其他的自定义item也可能会有这个问题,解决方法也是向外扩充一点边界,要比显示的范围大。

2.选中范围太小,只有恰好点击在线上才会被选中

除非线的宽度设置的很粗,否则在实际使用时不可能完全点到线,我们希望的是点击到线的周围就可以触发选中。解决方法是重写函数shape(),其实和boundingRect函数一样,在自定义item中都需要我们去重写的,只不过不写也不会报错,于是便留下了很多问题。

问题1和2有大佬写文章解释了其中的原因,我也是参考后才得以解决的,大家可以参考下,里面有具体的重写代码:

QGraphicsLineItem的轮廓

3.切换scene后,连线不显示

在一个scene中可以选择想要的流程图进行展示,可是当我切换到另一个是,其他的item都会显示出来,只有连线类没有显示。在paint函数中调用debug后,发现根本没有调用paint函数,类是创建出来了,但是核心的代码没有被调用,可不是没有显示。一番折腾后,在下面这篇博客找到了答案:

QGraphicsView放大时,paint有时不被调用,导致图像绘制不出来(2)

在创建完arrow后调用prepareGeometryChange,后然后在调用upDate。即可刷新出来

4.改变scene位置、缩放后,连线不显示

我在第一个流程图拖拽scene的位置,或者进行缩放后,再点击第二个流程图就会不显示连线,下面这篇文章给了我启发:

QGraphicsView放大时,paint有时不被调用,导致图像绘制不出来(1)

于是我再每次更换流程图时都重新设置scene的rect和缩放比例,虽然不清楚原因,但是问题解决了

附上view怎样重置缩放比例:

获取初始的缩放因子:
在进行任何缩放操作之前,你可以记录下 QGraphicsView 的初始缩放因子。这可以通过获取视图的初始变换矩阵来完成。

QTransform initialTransform = graphicsView->transform();

重置缩放因子:
如果希望在之后的某个时刻将视图的缩放恢复到初始状态,可以将视图的变换矩阵设置为初始变换矩阵。

graphicsView->setTransform(initialTransform);

  • 45
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值