目录
1.工程效果
本工程非常类似miscrosoft visio 软件的流程图组件:
2.工程存放路径
Examples\Qt-XX.XX.XX\widgets\graphicsview\diagramscene 目录下,XX.XX.XX为Qt的版本号,如:5.14.1。
3.工程难点分析
本工程有两个比较难懂的地方,其中一个代码如下:
void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (line != nullptr && myMode == InsertLine) {
QList<QGraphicsItem *> startItems = items(line->line().p1());
if (startItems.count() && startItems.first() == line)
startItems.removeFirst();
QList<QGraphicsItem *> endItems = items(line->line().p2());
if (endItems.count() && endItems.first() == line)
endItems.removeFirst();
removeItem(line);
delete line;
//! [11] //! [12]
if (startItems.count() > 0 && endItems.count() > 0 &&
startItems.first()->type() == DiagramItem::Type &&
endItems.first()->type() == DiagramItem::Type &&
startItems.first() != endItems.first()) {
DiagramItem *startItem = qgraphicsitem_cast<DiagramItem *>(startItems.first());
DiagramItem *endItem = qgraphicsitem_cast<DiagramItem *>(endItems.first());
Arrow *arrow = new Arrow(startItem, endItem);
arrow->setColor(myLineColor);
startItem->addArrow(arrow);
endItem->addArrow(arrow);
arrow->setZValue(-1000.0);
addItem(arrow);
arrow->updatePosition();
}
}
//! [12] //! [13]
line = nullptr;
QGraphicsScene::mouseReleaseEvent(mouseEvent);
}
第4~13行:检测拖动鼠标划线、当释放鼠标时,直线起始点和终止点下面的项对应的第一个项是不是直线对象自己,如果是则将直线对象从startItems 、endItems删除,因为拖动鼠标划线本意是想通过划线将两个非直线的item连接起来。直线自己连接自己无意义。
第16~28行:如果删除startItems 、endItems后,startItems 、endItems容器还不为空,证明startItems 、endItems中存放了非直线对象(item),则分别取出startItems 、endItems中的第一项,即取出两个非直线item,创建一个箭头对象(Arrow ),设置Arrow对象颜色、z次序,并将其设置到刚取出的非直线item对象中去。将箭头对象(Arrow )添加到场景并调用 Arrow::updatePosition()函数更新箭头对象(Arrow )位置。当调用 Arrow::updatePosition()时,会重新在这两个item之间创建一条直线对象。
另一个难点如下:
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *)
{
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
//! [4] //! [5]
QLineF centerLine(myStartItem->pos(), myEndItem->pos());
QPolygonF endPolygon = myEndItem->polygon();
QPointF p1 = endPolygon.first() + myEndItem->pos();
QPointF intersectPoint;
for (int i = 1; i < endPolygon.count(); ++i) {
QPointF p2 = endPolygon.at(i) + myEndItem->pos();
QLineF polyLine = QLineF(p1, p2);
QLineF::IntersectionType intersectionType =
polyLine.intersects(centerLine, &intersectPoint);
if (intersectionType == QLineF::BoundedIntersection)
break;
p1 = p2;
}
setLine(QLineF(intersectPoint, myStartItem->pos()));
//! [5] //! [6]
double angle = std::atan2(-line().dy(), line().dx());
QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,
cos(angle + M_PI / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,
cos(angle + M_PI - M_PI / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
//! [6] //! [7]
painter->drawLine(line());
painter->drawPolygon(arrowHead);
if (isSelected()) {
painter->setPen(QPen(myColor, 1, Qt::DashLine));
QLineF myLine = line();
myLine.translate(0, 4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
}
}
第14~26行:将myStartItem项位置点和myEndItem项位置点创建一个直线对象centerLine。将myEndItem项所在多边形的每个顶点取出并转为场景坐标,然后判断这个多边形每条边是否和centerLine相交,如果相交,则取出交点,且利用setLine函数,以交点为起始点,myStartItem项位置点为终点,更改Arrow类对象的直线。
第31~42:画出直线的箭头。
第43~49:当直线箭头被选中时,在上一步画出的直线的上边、下边画出1个像素画笔宽的虚线,这样给人的感觉是直线箭头被选中了。
经过这样操作后,就将myStartItem、myEndItem用直线箭头连接起来了,并能实现选中的感觉。
3 本工程存在的bug
本工程DiagramScene类的setLineColor、setTextColor、setItemColor函数存在bug,现以setLineColor为例子说明,其它类同。
void DiagramScene::setLineColor(const QColor &color)
{
myLineColor = color;
if (isItemChange(Arrow::Type)) {
Arrow *item = qgraphicsitem_cast<Arrow *>(selectedItems().first());
item->setColor(myLineColor);
update();
}
}
isItemChange函数如下:
bool DiagramScene::isItemChange(int type) const
{
const QList<QGraphicsItem *> items = selectedItems();
const auto cb = [type](const QGraphicsItem *item) { return item->type() == type; };
return std::find_if(items.begin(), items.end(), cb) != items.end();
}
setLineColor函数的意思就是看看选中的项中是否存在箭头类型的项,如果存在,则取出选中项列表中的第一个项,并将其设置为setLineColor函数参数的颜色,问题是:选中项列表中的第一个项可能不是箭头类型项,即如下代码:
Arrow *item = qgraphicsitem_cast<Arrow *>(selectedItems().first());
返回的item可能为nullptr,即 qgraphicsitem_cast<Arrow *>转换失败,导致item为nullptr,从而导致后面的代码崩溃。可以试着左边一个方框,右边一个方框,然后这两个方框用直线箭头连接,然后先选中左边的方框,再选中箭头,此时就崩溃了。