效果图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e83c8e3b332a21c28f6498bb1078c368.gif)
- 在
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
函数中的处理,其实一个矩形只需要俩个点,另外俩个点根据这俩个点变换,不然你拖动起来可能不是一个矩形了。