效果图
在scene
上绘制一个图元QGraphicsObject
的矩形,可以自由拖动且拖动四个角可以自由变换矩形需要如下处理。
矩形四个角
四个角的点需要独立处理继承于QGraphicsObject
,当我们点击时拖动时发送信号给矩形,进行矩形变换。
public : int pointIndex; QPointF pressPoint;
RectanglePoint :: RectanglePoint ( ICanvasScene* canvasScene, int index, QGraphicsItem* parent) : QGraphicsObject ( parent) , pointIndex ( index)
{ setAcceptHoverEvents ( true ) ;
} RectanglePoint :: ~ RectanglePoint ( ) { } QList< RectanglePoint* > RectanglePoint :: createRectanglePoints ( ICanvasScene* canvasScene, QPolygonF& cachePoints)
{ QList< RectanglePoint* > result; if ( nullptr == canvasScene) { return result; } for ( int i = 0 ; i < cachePoints. size ( ) ; ++ i) { RectanglePoint* point = new RectanglePoint ( canvasScene, i) ; if ( point) { result. append ( point) ; } } return result;
} QRectF RectanglePoint :: boundingRect ( ) const
{ return QRectF ( - controlSize, - controlSize, controlSize * 2 , controlSize * 2 ) ;
} void RectanglePoint :: paint ( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { } void RectanglePoint :: mousePressEvent ( QGraphicsSceneMouseEvent* event)
{ if ( event-> button ( ) != Qt:: LeftButton) { return GraphicsLayer :: mousePressEvent ( event) ; } event-> accept ( ) ; pressPoint = event-> scenePos ( ) ; emit positionChanged ( QPointF ( ) , pointIndex, startedChanged) ;
} void RectanglePoint :: mouseMoveEvent ( QGraphicsSceneMouseEvent* event)
{ if ( ! event-> buttons ( ) . testFlag ( Qt:: LeftButton) ) { return GraphicsLayer :: mouseMoveEvent ( event) ; } QPointF startP = pressPoint; QPointF offset = event-> scenePos ( ) - startP; event-> accept ( ) ; emit positionChanged ( offset, pointIndex, inChanged) ;
} void RectanglePoint :: mouseReleaseEvent ( QGraphicsSceneMouseEvent* event)
{ if ( event-> button ( ) != Qt:: LeftButton) { return GraphicsLayer :: mouseReleaseEvent ( event) ; } QPointF startP = pressPoint; QPointF offset = event-> scenePos ( ) - startP; event-> accept ( ) ; emit positionChanged ( offset, pointIndex, finishedChanged) ;
}
createRectanglePoints
函数是静态函数,矩形生成时会调用,并传入四个点位。positionChanged
的第三个参数为当前操作的状态的枚举(鼠标点击,移动与释放)
矩形
private : QRectF mboundingRect; QPolygonF cachePoints; QPolygonF startChangePoints; QList< RectanglePoint* > rectanglePoints;
const qreal controlSize = 5.0 ;
GisGridCustomRectItem :: GisGridCustomRectItem ( QRectF boundingRect, ICanvasScene* canvasScene, QGraphicsItem* parent) : QGraphicsObject ( parent) , mboundingRect ( boundingRect)
{ setFlags ( QGraphicsObject:: ItemIsMovable | QGraphicsObject:: ItemIsSelectable| QGraphicsObject:: ItemSendsGeometryChanges) ; cachePoints << boundingRect. topLeft ( ) << boundingRect. topRight ( ) << boundingRect. bottomRight ( ) << boundingRect. bottomLeft ( ) ; rectanglePoints = RectanglePoint :: createRectanglePoints ( canvasScene, cachePoints) ; QListIterator< RectanglePoint* > RectanglePointIter ( rectanglePoints) ; while ( RectanglePointIter. hasNext ( ) ) { RectanglePoint* RectanglePointGraph = RectanglePointIter. next ( ) ; if ( RectanglePointGraph) { RectanglePointGraph-> setParentItem ( this ) ; connect ( RectanglePointGraph, & RectanglePoint:: positionChanged, this , & GisGridCustomRectItem:: onRectPositionChange) ; RectanglePointGraph-> setZValue ( RectanglePointGraph-> zValue ( ) + 1 ) ; } } refreshPointPoistion ( ) ;
} GisGridCustomRectItem :: ~ GisGridCustomRectItem ( ) { } void GisGridCustomRectItem :: paint ( QPainter* painter, const QStyleOptionGraphicsItem* , QWidget* )
{ painter-> save ( ) ; QPen pen; painter-> setBrush ( QColor ( 230 , 230 , 230 , 100 ) ) ; if ( isSelected ( ) ) { pen. setColor ( QColor ( 0x2E9FE6 ) ) ; } painter-> setPen ( pen) ; QPainterPath path; path. addPolygon ( cachePoints) ; path. closeSubpath ( ) ; painter-> drawPath ( path) ; if ( isSelected ( ) ) { drawPointsRect ( painter) ; } painter-> restore ( ) ;
} QRectF GisGridCustomRectItem :: boundingRect ( ) const
{ return mboundingRect; return shape ( ) . controlPointRect ( ) ;
} QPainterPath GisGridCustomRectItem :: shape ( ) const
{ return QGraphicsObject :: shape ( ) ; QPainterPath path; path. addPolygon ( cachePoints) ; path. closeSubpath ( ) ; QPainterPathStroker stroker; stroker. setWidth ( 15 ) ; path = stroker. createStroke ( path) ; return path;
} QVariant GisGridCustomRectItem :: itemChange ( GraphicsItemChange change, const QVariant& value)
{ return QGraphicsObject :: itemChange ( change, value) ;
} void GisGridCustomRectItem :: drawPointsRect ( QPainter* painter)
{ double arrowLength = 7 ; double angle45 = M_PI / 4 ; double angle225 = 5 * M_PI / 4 ; drawSingleArrow ( painter, cachePoints[ 0 ] , angle45, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 0 ] , angle225, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 3 ] , - angle45, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 3 ] , M_PI - angle45, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 1 ] , M_PI - angle45, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 1 ] , - angle45, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 2 ] , angle225 - M_PI, arrowLength) ; drawSingleArrow ( painter, cachePoints[ 2 ] , angle225, arrowLength) ;
} void GisGridCustomRectItem :: onRectPositionChange ( QPointF delta, int index, itemRectChange state)
{ ICanvasScene* canvasScene = getCanvasScene ( ) ; if ( ! canvasScene) { return ; } ICanvasView* canvasView = canvasScene-> getCanvasView ( ) ; if ( ! canvasView) { return ; } auto interactioMode = ( InteractionMode) canvasView-> getInteractionMode ( ) ; if ( interactioMode == kAreaAmplification) { return ; } switch ( state) { case startedChanged: startChangePoints = cachePoints; break ; case inChanged: changeCachePoints ( delta, index) ; break ; case finishedChanged: changeCachePoints ( delta, index) ; refreshPointPoistion ( ) ; default : break ; } update ( ) ;
} void GisGridCustomRectItem :: changeCachePoints ( QPointF delta, int index)
{ QPolygonF pointF; cachePoints[ index] = startChangePoints[ index] + delta; if ( index == 1 || index == 3 ) { pointF << cachePoints[ 1 ] << cachePoints[ 3 ] ; } else { pointF << cachePoints[ 0 ] << cachePoints[ 2 ] ; } QPointF p1 = pointF[ 0 ] ; QPointF p2 = pointF[ 1 ] ; QPointF p3 ( p2. x ( ) , p1. y ( ) ) ; QPointF p4 ( p1. x ( ) , p2. y ( ) ) ; pointF. clear ( ) ; if ( index == 1 || index == 3 ) { pointF << p3 << p1 << p4 << p2; } else { pointF << p1 << p3 << p2 << p4; } cachePoints = pointF; double minX = std:: min ( { p1. x ( ) , p2. x ( ) , p3. x ( ) , p4. x ( ) } ) ; double minY = std:: min ( { p1. y ( ) , p2. y ( ) , p3. y ( ) , p4. y ( ) } ) ; double maxX = std:: max ( { p1. x ( ) , p2. x ( ) , p3. x ( ) , p4. x ( ) } ) ; double maxY = std:: max ( { p1. y ( ) , p2. y ( ) , p3. y ( ) , p4. y ( ) } ) ; QRectF rect ( minX, minY, maxX - minX, maxY - minY) ; mboundingRect = rect;
} void GisGridCustomRectItem :: drawSingleArrow ( QPainter* painter, QPointF basePoint, double angle, double length)
{ QPointF endPoint ( basePoint. x ( ) + length * qCos ( angle) , basePoint. y ( ) + length * qSin ( angle) ) ; painter-> drawLine ( basePoint, endPoint) ; double arrowHeadAngle = M_PI / 5 ; double arrowHeadLength = 5 ; QPointF arrowLeft ( endPoint. x ( ) - arrowHeadLength * qCos ( angle - arrowHeadAngle) , endPoint. y ( ) - arrowHeadLength * qSin ( angle - arrowHeadAngle) ) ; QPointF arrowRight ( endPoint. x ( ) - arrowHeadLength * qCos ( angle + arrowHeadAngle) , endPoint. y ( ) - arrowHeadLength * qSin ( angle + arrowHeadAngle) ) ; painter-> drawLine ( endPoint, arrowLeft) ; painter-> drawLine ( endPoint, arrowRight) ;
} void GisGridCustomRectItem :: refreshPointPoistion ( )
{ QListIterator< RectanglePoint* > rectanglePointsIter ( rectanglePoints) ; while ( rectanglePointsIter. hasNext ( ) ) { RectanglePoint* ponit = rectanglePointsIter. next ( ) ; if ( ponit) { ponit-> setPos ( cachePoints[ ponit-> pointIndex] ) ; } }
}
四个角的双向箭头挺难画的,可以直接画一个简单的圆形就行。 对于移动操作而言,应该设置ItemSendsGeometryChanges
标志,这允许项目在几何变化时(如位置改变)发送通知。如果没有设置这个标志,项目在移动时将不会调用itemChange
函数。 拖动矩形有俩种处理形式(看shape
于boundingRect
函数) 点击边框拖动,这个需要计算shape
路径函数,得到矩形的边框。 点击矩形内部拖动,这个需要再每次变换后更新一下矩形。 矩形的变换关键在于changeCachePoints
函数中的处理,其实一个矩形只需要俩个点,另外俩个点根据这俩个点变换,不然你拖动起来可能不是一个矩形了。