在一个简单的画图程序中,其中一个最基本的目标是在QGraphicsView上画一根线,线的两端可以进行拖拽,这在QGraphics View框架中实现其实是很简单的,处理一下鼠标事件,在paint函数中给选中状态下的线段两端各画一个小矩形,简单的线段编辑就可以实现了。
好了,开始拖拽了,这时意外出现了
随着鼠标的拖动,线段两端的小矩形会出现残影
这应该是item的update区域没有包含那些残影的范围
QGraphicsLine的update区域应该是它的boundingRect函数所返回的矩形区域,重新实现一下boundingRect函数
QRectF rc = QGraphicsLineItem::boundingRect();
rc.adjust(-w, -w, w, w);
在往外扩大端点小矩形的宽度大小,这样再拖动就不会出现问题了
拖动整根线段是QGraphics View框架自带的功能,线段宽度很小的时候,尤其为1的时候,非常难选中,虽然可以进行框选,但选中线段后对整根线段进行拖拽就显得很困难了,要用鼠标仔细移到线段上才能进行拖动
QGraphicsItem的鼠标操作区域应该是shape函数返回的QPainterPath区域,于是我决定重新实现shape函数,实现一块包含线段和线段端点,斜率和线段相同的四边形区域,这样原来boundingRect函数的实现也可以去掉了,直接可以用系统默认的
写了一段代码
QPointF pt1 = line().p1();
QPointF pt2 = line().p2();
if (pt1.x() > pt2.x()) {
QPointF&& tmp = std::move(pt2);
pt2 = std::move(pt1);
pt1 = std::move(tmp);
}
QPolygonF polygon;
polygon << QPointF(pt1.x() - w, pt1.y() - w) << QPointF(pt1.x() - w, pt1.y() + w)
<< QPointF(pt2.x() + w, pt2.y() + w) << QPointF(pt2.x() + w, pt2.y() - w)
<<QPointF(pt1.x() - w, pt1.y() - w) ;
QPainterPath path;
path.addPolygon(polygon);
预想的实现应该是这样
实际上在拖动以后却会变成这样:
很明显,这种实现方法是错误的
如果直接把QGraphicsLineItem的shape画出来,那应该是这样的:
很完美的斜率与线段相同的矩形,手头上一下子没有QGraphicsLineItem的源码,加上对Graphics View框架也是学习不多,没明白源码是怎么实现的,于是决定还是自己实现一个,实现的目标是这样:
首先取线段的斜率,并计算角度
再用绕某点旋转的三角函数公式double angle = std::atan((down_point.y() - up_point.y()) / (down_point.x() - up_point.x()));
x=(x1-x2)cosθ-(y1-y2)sinθ+x2
y=(y1-y2)cosθ+(x1-x2)sinθ+y2
将线段旋转成水平状态,在水平状态下,矩形的四个顶点是很容易计算的:
w *= 1.414;
QPointF top_left = p1 + QPointF(-w * val, -w);
QPointF bottom_left = p1 + QPointF(-w * val, w);
QPointF top_right = p2 + QPointF(w * val, -w);
QPointF bottom_right = p2 + QPointF(w * val, w);
然后在将这4个点转回去,好了,现在看上去一切都很好
心里还是有个疙瘩,QGraphicsLineItem的shape是怎么实现的,如果我用同样的方法岂不是更简单
还是觉得下载一个源代码来看下这一段的实现
打开一看,原来就是一个QPainterPathStroker类的使用,查了一下这个类的文档,顿时感觉自己做的全是无用功,就用QPainterPathStroker重新实现一下自己的shape函数来扩大原本的shape区域吧,看看用系统类来实现有多么的简单
先计算一下线段端点小矩形的宽度 w,然后就
w *= 1.414;
path = QGraphicsLineItem::shape();
QPainterPathStroker stroker;
stroker.setWidth(w);
path = stroker.createStroke(path);
return path;
看看这样实现出来的效果
没有任何的问题
最后只需要在paint函数中去掉测试代码
painter->drawPath(shape());
这段代码就可以正常使用了
如果能多吸收些知识,早知道QPainterPathStroker的用法,必然能少走这些弯路