qt 带箭头的直线 (类似viso)
20210926
好久没关注博客园,有些评论未回应。
如果要代码,点击下面连接:
https://files-cdn.cnblogs.com/files/warmlight/NewProject.rar
编译环境是:Qt5.9.0 MinGW 32 。
这个只是一个示例,功能不完全,也不排除代码有什么bug。如果要使用到项目中,一定要多测试。
2020.07.13
前几天有csdn网友给我提了一个bug:在画线是paint函数会进入无线循环。
我今天修改了一下画非箭头直线的函数。具体请点击博客园的链接下载。文中再贴出修改的地方。
2020.02.27
想要代码,留邮箱吧。或者到https://download.csdn.net/download/XuePiaoFei1/12195991下载,或者https://files-cdn.cnblogs.com/files/warmlight/NewProject.rar。
近来Qt开发时可能遇到这样的需求:两个(或多个)矩形,要用直线将它们连接起来,之后还要把它们保存到xml中,并且能够还原。
类似于下图:
首先想到的就是Qt自带的demo:diagramscene。因为demo中有箭头方向,开始节点和结束节点,连坐标都已经有了,保存还原都可以完成。但是,demo的直线不是竖直或水平的,所以肯定要对demo进行修改。(扯淡了,demo肯定不会符合个人的需求的,无论如何都要修改的。)
刚用Qt不久,网络上也搜不到类似的问题,也许是自己找不到,只能寄希望于Qt的demo,限制了自己的思想。可是目前没有更好的办法。
我的工程有几个要点,要能够保存和还原,箭头的首尾节点肯定要确定,或者说首尾节点的坐标一定要知道。
demo中的基本元素够了,我想只对arrow类作修改,希望能达到目标。
我先把我改造后的arrow类贴出来,然后再试图分析一下。
头文件:
1 /**************************************************************************** 2 ** 3 ** Copyright (C) 2016 The Qt Company Ltd. 4 ** Contact: https://www.qt.io/licensing/ 5 ** 6 ** This file is part of the examples of the Qt Toolkit. 7 ** 8 ** $QT_BEGIN_LICENSE:BSD$ 9 ** Commercial License Usage 10 ** Licensees holding valid commercial Qt licenses may use this file in 11 ** accordance with the commercial license agreement provided with the 12 ** Software or, alternatively, in accordance with the terms contained in 13 ** a written agreement between you and The Qt Company. For licensing terms 14 ** and conditions see https://www.qt.io/terms-conditions. For further 15 ** information use the contact form at https://www.qt.io/contact-us. 16 ** 17 ** BSD License Usage 18 ** Alternatively, you may use this file under the terms of the BSD license 19 ** as follows: 20 ** 21 ** "Redistribution and use in source and binary forms, with or without 22 ** modification, are permitted provided that the following conditions are 23 ** met: 24 ** * Redistributions of source code must retain the above copyright 25 ** notice, this list of conditions and the following disclaimer. 26 ** * Redistributions in binary form must reproduce the above copyright 27 ** notice, this list of conditions and the following disclaimer in 28 ** the documentation and/or other materials provided with the 29 ** distribution. 30 ** * Neither the name of The Qt Company Ltd nor the names of its 31 ** contributors may be used to endorse or promote products derived 32 ** from this software without specific prior written permission. 33 ** 34 ** 35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 ** 47 ** $QT_END_LICENSE$ 48 ** 49 ****************************************************************************/ 50 51 #ifndef ARROW_H 52 #define ARROW_H 53 54 #include <QGraphicsLineItem> 55 56 #include "diagramitem.h" 57 58 QT_BEGIN_NAMESPACE 59 class QGraphicsPolygonItem; 60 class QGraphicsLineItem; 61 class QGraphicsScene; 62 class QRectF; 63 class QGraphicsSceneMouseEvent; 64 class QPainterPath; 65 QT_END_NAMESPACE 66 67 enum LineType { 68 lineType1 = 1, //横竖 69 lineType2, //竖横 70 lineType3, //横竖横 71 lineType4 //竖横竖 72 }; 73 74 //! [0] 75 class Arrow : public QGraphicsLineItem 76 { 77 public: 78 enum { Type = UserType + 4 }; 79 80 Arrow(DiagramItem *startItem, DiagramItem *endItem, 81 QGraphicsItem *parent = 0); 82 ~Arrow() {m_bDeleteFlag = true;} 83 84 int type() const override { return Type; } 85 QRectF boundingRect() const override; 86 QPainterPath shape() const override; 87 void setColor(const QColor &color) { myColor = color; } 88 DiagramItem *startItem() const { return myStartItem; } 89 DiagramItem *endItem() const { return myEndItem; } 90 91 void setItemId(QString startItemId ,QString startEndId); 92 void updatePosition(); 93 94 QString getStartId(){return startId;} 95 QString getEndId(){return endId;} 96 97 void setStartItem(DiagramItem *startItem){ myStartItem = startItem; } 98 void setEndItem(DiagramItem *endItem){ myEndItem = endItem; } 99 100 void setArrowFlag(bool IsArrow){bisArrow = IsArrow;} 101 bool getArrowFlag(){return bisArrow;} 102 bool getDeleteFlage() {return m_bDeleteFlag;} 103 void DrawArrow(QPainter *painter, QPointF startPt, QPointF endPt, bool bArrow); 104 void ThreeLine(QPainter *painter, QPointF startPt, QPointF endPt);//三段线 105 void TwoLine(QPainter *painter, QPointF startPt, QPointF endPt); //两段线 106 //void SetLineStyle(bool bVhvLine);//线段的折线风格 107 //void SetLineType(int nLineType);//线段的数量 108 void SetLineType(LineType lineType); 109 int GetLineType() {return m_lineType;} 110 void DrawLine(QPainter *painter, QPointF startPt, QPointF endPt); 111 protected: 112 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override; 113 114 private: 115 DiagramItem *myStartItem; 116 DiagramItem *myEndItem; 117 QColor myColor; 118 QPolygonF arrowHead; 119 QString startId; 120 QString endId; 121 bool bisArrow; 122 bool m_bDeleteFlag;// 123 LineType m_lineType; 124 //bool m_bVhvLine;//true三段线,竖横竖; false三段线 横竖横 125 //bool m_bVhLine;// true两段线 竖横; false 两段线 横竖 126 //int m_nLineType;//2-2段线,3-3段线 127 }; 128 129 inline void Arrow::setItemId(QString startItemId ,QString startEndId){ 130 startId = startItemId; 131 endId = startEndId; 132 } 133 //! [0] 134 135 #endif // ARROW_H
cpp文件:
1 /**************************************************************************** 2 ** 3 ** Copyright (C) 2016 The Qt Company Ltd. 4 ** Contact: https://www.qt.io/licensing/ 5 ** 6 ** This file is part of the examples of the Qt Toolkit. 7 ** 8 ** $QT_BEGIN_LICENSE:BSD$ 9 ** Commercial License Usage 10 ** Licensees holding valid commercial Qt licenses may use this file in 11 ** accordance with the commercial license agreement provided with the 12 ** Software or, alternatively, in accordance with the terms contained in 13 ** a written agreement between you and The Qt Company. For licensing terms 14 ** and conditions see https://www.qt.io/terms-conditions. For further 15 ** information use the contact form at https://www.qt.io/contact-us. 16 ** 17 ** BSD License Usage 18 ** Alternatively, you may use this file under the terms of the BSD license 19 ** as follows: 20 ** 21 ** "Redistribution and use in source and binary forms, with or without 22 ** modification, are permitted provided that the following conditions are 23 ** met: 24 ** * Redistributions of source code must retain the above copyright 25 ** notice, this list of conditions and the following disclaimer. 26 ** * Redistributions in binary form must reproduce the above copyright 27 ** notice, this list of conditions and the following disclaimer in 28 ** the documentation and/or other materials provided with the 29 ** distribution. 30 ** * Neither the name of The Qt Company Ltd nor the names of its 31 ** contributors may be used to endorse or promote products derived 32 ** from this software without specific prior written permission. 33 ** 34 ** 35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 ** 47 ** $QT_END_LICENSE$ 48 ** 49 ****************************************************************************/ 50 #include "arrow.h" 51 #include <qmath.h> 52 #include <QPen> 53 #include <QPainter> 54 #include <QDebug> 55 56 //! [0] 57 Arrow::Arrow(DiagramItem *startItem, DiagramItem *endItem, QGraphicsItem *parent) 58 : QGraphicsLineItem(parent) 59 { 60 m_bDeleteFlag = false;//20191121 sdl 61 myStartItem = startItem; 62 myEndItem = endItem; 63 setFlag(QGraphicsItem::ItemIsSelectable, true); 64 myColor = Qt::black; 65 setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); 66 bisArrow = true; 67 if(myStartItem){startId = myStartItem->getItemId();} 68 if(myEndItem){endId = myEndItem->getItemId();} 69 m_lineType = LineType::lineType1; 70 } 71 //! [0] 72 73 //! [1] 74 QRectF Arrow::boundingRect() const 75 { 76 qreal extra = (pen().width() + 20) / 2.0; 77 78 return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(), 79 line().p2().y() - line().p1().y())) 80 .normalized() 81 .adjusted(-extra, -extra, extra, extra); 82 } 83 //! [1] 84 85 //! [2] 86 QPainterPath Arrow::shape() const 87 { 88 QPainterPath path = QGraphicsLineItem::shape(); 89 path.addPolygon(arrowHead); 90 return path; 91 } 92 //! [2] 93 94 //! [3] 95 void Arrow::updatePosition() 96 { 97 QPointF endPt = mapFromItem(myEndItem, 0, 0); 98 QLineF line(mapFromItem(myStartItem, 0, 0), endPt); 99 setLine(line); 100 } 101 102 103 //! [3] 104 105 //! [4] 106 void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *, 107 QWidget *) 108 { 109 if(myStartItem == NULL || myEndItem == NULL) return; 110 if(myStartItem->GetDeleteFlag() || myEndItem->GetDeleteFlag()) return; 111 if (myStartItem->collidesWithItem(myEndItem)) 112 return; 113 114 QPointF start = myStartItem->pos(); 115 QPointF end = myEndItem->pos(); 116 if(qFuzzyCompare(start.x(), end.x())){ //如果在同一竖直或水平线时,直接连接两节点中心点 117 DrawArrow(painter, start, end, true); 118 return; 119 } 120 if (qFuzzyCompare(start.y(), end.y())) { 121 DrawArrow(painter, start, end, true); 122 return; 123 } 124 125 DrawLine(painter, start, end); 126 // if(m_nLineType == 2){ 127 // TwoLine(painter, start, end); 128 // } 129 // else if (m_nLineType == 3) { 130 // ThreeLine(painter, start, end); 131 // } 132 } 133 //! [7] 134 135 void Arrow::DrawArrow(QPainter *painter, QPointF startPt, QPointF endPt, bool bArrow) 136 { 137 if(endPt == QPointF(0, 0)){ 138 return; 139 } 140 QPen myPen = pen(); 141 myPen.setColor(myColor); 142 qreal arrowSize = 10; 143 painter->setPen(myPen); 144 painter->setBrush(myColor); 145 //! [4] //! [5] 146 147 QLineF centerLine(startPt, endPt); 148 149 QPolygonF endPolygon; 150 endPolygon = myEndItem->polygon(); 151 QPointF p1 = endPolygon.first() + endPt; 152 QPointF p2; 153 QPointF intersectPoint; 154 QLineF polyLine; 155 for (int i = 1; i < endPolygon.count(); ++i) { 156 p2 = endPolygon.at(i) + endPt; 157 polyLine = QLineF(p1, p2); 158 QLineF::IntersectType intersectType = 159 polyLine.intersect(centerLine, &intersectPoint); 160 if (intersectType == QLineF::BoundedIntersection) 161 break; 162 p1 = p2; 163 } 164 165 setLine(QLineF(endPt, startPt/*myStartItem->pos()*/)); 166 167 //! [5] //! [6] 168 if (bArrow){ 169 setLine(QLineF(intersectPoint, startPt/*myStartItem->pos()*/));//如果是箭头所在的线 170 } 171 double angle = std::atan2(-line().dy(), line().dx()); 172 173 QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize, 174 cos(angle + M_PI / 3) * arrowSize); 175 QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize, 176 cos(angle + M_PI - M_PI / 3) * arrowSize); 177 178 arrowHead.clear(); 179 arrowHead << line().p1() << arrowP1 << arrowP2; 180 //! [6] //! [7] 181 painter->drawLine(line()); 182 //是否画箭头 183 if(bArrow){ 184 painter->drawPolygon(arrowHead); 185 } 186 if (isSelected()) { 187 painter->setPen(QPen(myColor, 1, Qt::DashLine)); 188 QLineF myLine = line(); 189 myLine.translate(0, 4.0); 190 painter->drawLine(myLine); 191 myLine.translate(0,-8.0); 192 painter->drawLine(myLine); 193 } 194 } 195 196 void Arrow::ThreeLine(QPainter *painter, QPointF start, QPointF end) 197 { 198 if(m_lineType == LineType::lineType4){ 199 qreal midY = (start.y() + end.y()) / 2; 200 QPointF startPt = myStartItem->pos(); 201 QPointF endPt = myEndItem->pos(); 202 203 endPt.setX(startPt.x()); 204 endPt.setY(midY); 205 DrawArrow(painter, startPt, endPt, false); 206 207 startPt = endPt; 208 endPt.setX(myEndItem->pos().x()); 209 endPt.setY(startPt.y()); 210 DrawArrow(painter, startPt, endPt, false); 211 212 startPt = endPt; 213 endPt = myEndItem->pos(); 214 215 DrawArrow(painter, startPt, endPt, true); 216 } 217 else if(m_lineType == LineType::lineType3){ 218 qreal midX = (start.x() + end.x()) / 2; 219 QPointF startPt = myStartItem->pos(); 220 QPointF endPt = myEndItem->pos(); 221 222 endPt.setX(midX); 223 endPt.setY(startPt.y()); 224 DrawArrow(painter, startPt, endPt, false); 225 226 startPt = endPt; 227 endPt.setX(startPt.x()); 228 endPt.setY(myEndItem->pos().y()); 229 DrawArrow(painter, startPt, endPt, false); 230 231 startPt = endPt; 232 endPt = myEndItem->pos(); 233 DrawArrow(painter, startPt, endPt, true); 234 } 235 } 236 237 void Arrow::TwoLine(QPainter *painter, QPointF startPt, QPointF endPt) 238 { 239 if(m_lineType == LineType::lineType2){ 240 endPt.setX(startPt.x()); 241 endPt.setY(endPt.y()); 242 DrawArrow(painter, startPt, endPt, false); 243 244 startPt = endPt; 245 endPt = myEndItem->pos(); 246 DrawArrow(painter, startPt, endPt, true); 247 } 248 else if(m_lineType == LineType::lineType1){ 249 endPt.setY(startPt.y()); 250 DrawArrow(painter, startPt, endPt, false); 251 252 startPt = endPt; 253 endPt = myEndItem->pos(); 254 DrawArrow(painter, startPt, endPt, true); 255 } 256 } 257 258 //void Arrow::SetLineStyle(bool bVhvLine) 259 //{ 260 // m_bVhvLine = bVhvLine; 261 //} 262 263 void Arrow::SetLineType(LineType lineType) 264 { 265 if(lineType > lineType4 || lineType < lineType1) 266 m_lineType = lineType1; 267 else{ 268 m_lineType = lineType; 269 } 270 } 271 272 void Arrow::DrawLine(QPainter *painter, QPointF start, QPointF end) 273 { 274 if(m_lineType == LineType::lineType4){ 275 qreal midY = (start.y() + end.y()) / 2; 276 QPointF startPt = myStartItem->pos(); 277 QPointF endPt = myEndItem->pos(); 278 279 endPt.setX(startPt.x()); 280 endPt.setY(midY); 281 DrawArrow(painter, startPt, endPt, false); 282 283 startPt = endPt; 284 endPt.setX(myEndItem->pos().x()); 285 endPt.setY(startPt.y()); 286 DrawArrow(painter, startPt, endPt, false); 287 288 startPt = endPt; 289 endPt = myEndItem->pos(); 290 291 DrawArrow(painter, startPt, endPt, true); 292 } 293 else if(m_lineType == LineType::lineType3){ 294 qreal midX = (start.x() + end.x()) / 2; 295 QPointF startPt = myStartItem->pos(); 296 QPointF endPt = myEndItem->pos(); 297 298 endPt.setX(midX); 299 endPt.setY(startPt.y()); 300 DrawArrow(painter, startPt, endPt, false); 301 302 startPt = endPt; 303 endPt.setX(startPt.x()); 304 endPt.setY(myEndItem->pos().y()); 305 DrawArrow(painter, startPt, endPt, false); 306 307 startPt = endPt; 308 endPt = myEndItem->pos(); 309 DrawArrow(painter, startPt, endPt, true); 310 } 311 else if(m_lineType == LineType::lineType2){ 312 end.setX(start.x()); 313 end.setY(end.y()); 314 DrawArrow(painter, start, end, false); 315 316 start = end; 317 end = myEndItem->pos(); 318 DrawArrow(painter, start, end, true); 319 } 320 else if(m_lineType == LineType::lineType1){ 321 end.setY(start.y()); 322 DrawArrow(painter, start, end, false); 323 324 start = end; 325 end = myEndItem->pos(); 326 DrawArrow(painter, start, end, true); 327 } 328 else {//默认是linetype1 329 m_lineType = LineType::lineType1; 330 end.setY(start.y()); 331 DrawArrow(painter, start, end, false); 332 333 start = end; 334 end = myEndItem->pos(); 335 DrawArrow(painter, start, end, true); 336 } 337 }
2020.07.13-------------
为了防止paint进入死循环,修改了DrawArrow函数,另外改了选中线时的操作。
1 void Arrow::DrawArrow(QPainter *painter, QPointF startPt, QPointF endPt, bool bArrow) 2 { 3 if(endPt == QPointF(0, 0)){ 4 return; 5 } 6 QPen myPen = pen(); 7 myPen.setColor(myColor); 8 qreal arrowSize = 10; 9 painter->setPen(myPen); 10 painter->setBrush(myColor); 11 12 QLineF centerLine(startPt, endPt); 13 QPolygonF endPolygon; 14 endPolygon = myEndItem->polygon(); 15 QPointF p1 = endPolygon.first() + endPt; 16 QPointF p2; 17 QPointF intersectPoint; 18 QLineF polyLine; 19 for (int i = 1; i < endPolygon.count(); ++i) { 20 p2 = endPolygon.at(i) + endPt; 21 polyLine = QLineF(p1, p2); 22 QLineF::IntersectType intersectType = 23 polyLine.intersect(centerLine, &intersectPoint); 24 if (intersectType == QLineF::BoundedIntersection) 25 break; 26 p1 = p2; 27 } 28 29 qDebug() << "drawArrow:" << m_nCountDrawArrow; 30 m_nCountDrawArrow++; 31 32 if (bArrow){ 33 setLine(QLineF(intersectPoint, startPt/*myStartItem->pos()*/));//如果是箭头所在的线 34 double angle = std::atan2(-line().dy(), line().dx()); 35 36 QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize, 37 cos(angle + M_PI / 3) * arrowSize); 38 QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize, 39 cos(angle + M_PI - M_PI / 3) * arrowSize); 40 41 arrowHead.clear(); 42 arrowHead << line().p1() << arrowP1 << arrowP2; 43 44 painter->drawLine(line()); 45 } 46 else { 47 painter->drawLine(endPt, startPt); 48 // setLine(QLineF(endPt, startPt/*myStartItem->pos()*/)); 49 } 50 51 //是否画箭头 52 if(bArrow){ 53 painter->drawPolygon(arrowHead); 54 } 55 if (isSelected()) { 56 painter->setPen(QPen(myColor, 1, Qt::DashLine)); 57 QLineF myLine = QLineF(startPt, endPt); 58 myLine.translate(0, 4.0); 59 painter->drawLine(myLine); 60 myLine.translate(0,-8.0); 61 painter->drawLine(myLine); 62 } 63 }
2020.07.13------------
这个类中,有一些代码冗余。
我在这里分了四类画线方式,即头文件中的LineType。在横竖横和竖横竖的类型中,中间线段取的是中点画线。箭头首尾的节点都有了,保存也可以的。也可以根据个人情况修改箭头类。
操作:编辑图形时,基本图形时拖拽到场景中的。连线不是拖拽,是单击选中一种类型,在场景中连接两个基本图形即可。选中一个画线方式,再次拖拽基本图形或右键鼠标后,需要再次选择画线方式,才能再次画线。右键鼠标会出现右键菜单,打开、保存、删除。保存,保存场景中图形为xml;打开,打开保存的xml文件;删除,可删除基本图形或者箭头。注意:删除基本图形时,如果基本图形有连线,连线也会被删除。
还有一些功能待完善,如修改基本图形的名称等。
结果图:
稍后把代码传到csdn。如果博客园能上传代码就好了。