效果图

- 在
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
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]);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 四个角的双向箭头挺难画的,可以直接画一个简单的圆形就行。
- 对于移动操作而言,应该设置
ItemSendsGeometryChanges标志,这允许项目在几何变化时(如位置改变)发送通知。如果没有设置这个标志,项目在移动时将不会调用itemChange函数。 - 拖动矩形有俩种处理形式(看
shape于boundingRect函数)
- 点击边框拖动,这个需要计算
shape路径函数,得到矩形的边框。 - 点击矩形内部拖动,这个需要再每次变换后更新一下矩形。
- 矩形的变换关键在于
changeCachePoints函数中的处理,其实一个矩形只需要俩个点,另外俩个点根据这俩个点变换,不然你拖动起来可能不是一个矩形了。