在QGraphicsView中,我使用两个元素:状态和连接器。
我添加了两个状态,然后创建了一个连接器。我将两个状态的指针传递给连接器,以计算起点和终点。像这样的东西:
问题是,当我拖动两个状态之一时。我也希望连接器也更新。目前位置保持不变:
这是我的州立课程:
class SimpleStateShape : public QGraphicsObject{
Q_OBJECT
public:
enum { Type = UserType + SimpleStateType };
public:
SimpleStateShape(QGraphicsItem* parent = nullptr);
virtual ~SimpleStateShape();
public:
QRectF boundingRect() const override;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
int type() const override;
private:
Style::CanvasSimpleState m_style; // It contains style like pens and brushes, not important here
QSize m_size;
};
////
///////////////////////////////////////////////////////////////////////////////
// PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
SimpleStateShape::SimpleStateShape(QGraphicsItem* parent) :
QGraphicsObject(parent),
m_size(style.getMinimumSize()) {
setFlag(QGraphicsItem::ItemContainsChildrenInShape);
}
SimpleStateShape::~SimpleStateShape() {
}
///////////////////////////////////////////////////////////////////////////////
// VIRTUAL PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
QRectF SimpleStateShape::boundingRect() const {
qreal penWidth = m_style.getContourPen().widthF();
qreal x = -(m_size.width() + penWidth) * 0.5;
qreal y = -(m_size.height() + penWidth) * 0.5;
return QRectF(x, y, m_size.width(), m_size.height());
}
void SimpleStateShape::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
qreal penWidth = m_style.getContourPen().widthF();
qreal x = -(m_size.width() + penWidth) * 0.5;
qreal y = -(m_size.height() + penWidth) * 0.5;
QRectF boundingRect(x, y, m_size.width(), m_size.height());
painter->setPen(m_style.getContourPen());
painter->setBrush(m_style.getBackgroundBrush());
painter->drawRoundedRect(boundingRect, m_style.getCornerRadius(), m_style.getCornerRadius(), Qt::AbsoluteSize);
painter->drawText(boundingRect, Qt::AlignCenter | Qt::AlignVCenter, getName().c_str());
}
int SimpleStateShape::type() const {
return Type;
}
这是我的连接器:
class TransitionLine : public QGraphicsObject {
Q_OBJECT
public:
enum { Type = UserType + TransitionLineType };
public:
TransitionLine(QGraphicsItem* parent = nullptr);
virtual ~TransitionLine();
void setStartState(SimpleStateShape* shape);
void setEndState(SimpleStateShape* shape);
void showModifiers(bool flag);
void setSceneControlAnchorPosition(const QPointF& pos);
public:
QRectF boundingRect() const override;
QPainterPath shape() const override;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
int type() const override;
private:
void createSubShapes();
void drawArrow(QPainter* painter);
void setDefaultLineData();
void enableModifiers(bool enabled);
QPointF findRectLineIntersection(const QRectF& rect, const QPointF& p) const;
private:
Style::CanvasTransition m_style;
SimpleStateShape* m_startState;
SimpleStateShape* m_endState;
QPointF m_firstControlPoint;
QPointF m_startPoint;
QPointF m_endPoint;
QGraphicsEllipseItem* m_controlAnchor;
QGraphicsLineItem* m_startControlLine;
QGraphicsLineItem* m_endControlLine;
bool m_isNew;
bool m_modifiersEnabled;
};
////////////
TransitionLine::TransitionLine(QGraphicsItem* parent) :
QGraphicsObject(parent),
m_startState(nullptr),
m_endState(nullptr),
m_isNew(true),
m_modifiersEnabled(false) {
createSubShapes();
setBoundingRegionGranularity(BoundingRegionGranularity);
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
setFlag(QGraphicsItem::ItemIsSelectable);
setZValue(TransitionZValue);
}
TransitionLine::~TransitionLine() {
int i = 0;
}
void TransitionLine::setStartState(SimpleStateShape* shape) {
m_startState = shape;
}
void TransitionLine::setEndState(SimpleStateShape* shape) {
m_endState = shape;
}
void TransitionLine::showModifiers(bool show) {
enableModifiers(show);
}
void TransitionLine::setSceneControlAnchorPosition(const QPointF& pos) {
m_firstControlPoint = pos;
prepareGeometryChange();
update(boundingRect());
}
///////////////////////////////////////////////////////////////////////////////
// VIRTUAL PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
QRectF TransitionLine::boundingRect() const {
if (m_startState == nullptr || m_endState == nullptr) {
return QRect(0, 0, 1, 1);
}
QPointF startStatScenePos = m_startState->scenePos();
QPointF endStateScenePos = m_endState->scenePos();
QPointF firstControlScenePos = mapToScene(m_firstControlPoint);
qreal minX = std::min({ startStatScenePos.x(), endStateScenePos.x(), firstControlScenePos.x() });
qreal minY = std::min({ startStatScenePos.y(), endStateScenePos.y(), firstControlScenePos.y() });
qreal maxX = std::max({ startStatScenePos.x(), endStateScenePos.x(), firstControlScenePos.x() });
qreal maxY = std::max({ startStatScenePos.y(), endStateScenePos.y(), firstControlScenePos.y() });
return mapRectFromScene(QRectF(minX, minY, maxX - minX, maxY - minY));
}
QPainterPath TransitionLine::shape() const {
QPainterPath path;
path.moveTo(m_startPoint);
path.quadTo(m_firstControlPoint, m_endPoint);
QPainterPathStroker stroker;
stroker.setWidth(StrokeWidth);
return stroker.createStroke(path).simplified();
}
void TransitionLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
if (m_startState == nullptr || m_endState == nullptr) {
return;
}
if (m_isNew) {
setDefaultLineData();
m_isNew = false;
}
auto startBB = m_startState->sceneBoundingRect();
auto endBB = m_endState->sceneBoundingRect();
painter->setPen(m_style.getLinePen());
QPainterPath path;
path.moveTo(m_startPoint);
path.quadTo(m_firstControlPoint, m_endPoint);
painter->drawPath(path);
if (m_modifiersEnabled) {
m_controlAnchor->setPos(m_firstControlPoint);
m_startControlLine->setLine(m_startPoint.x(), m_startPoint.y(), m_firstControlPoint.x(), m_firstControlPoint.y());
m_endControlLine->setLine(m_endPoint.x(), m_endPoint.y(), m_firstControlPoint.x(), m_firstControlPoint.y());
}
}
int TransitionLine::type() const {
return Type;
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE SECTION //
///////////////////////////////////////////////////////////////////////////////
void TransitionLine::createSubShapes() {
m_controlAnchor = new QGraphicsEllipseItem(this);
m_startControlLine = new QGraphicsLineItem(this);
m_endControlLine = new QGraphicsLineItem(this);
m_controlAnchor->setVisible(false);
m_startControlLine->setVisible(false);
m_endControlLine->setVisible(false);
m_controlAnchor->setRect(-5, -5, 10, 10);
m_controlAnchor->setZValue(AnchorZValue);
}
void TransitionLine::drawArrow(QPainter* painter) {
}
void TransitionLine::setDefaultLineData() {
auto b1 = static_cast<QGraphicsItem*>(m_startState)->sceneBoundingRect();
m_startPoint = mapFromScene(findRectLineIntersection(m_startState->sceneBoundingRect(), m_endState->scenePos()));
m_endPoint = mapFromScene(findRectLineIntersection(m_endState->sceneBoundingRect(), m_startState->scenePos()));
m_firstControlPoint.rx() = 0.5 * (m_endPoint.x() - m_startPoint.x()) + m_startPoint.x();
m_firstControlPoint.ry() = 0.5 * (m_endPoint.y() - m_startPoint.y()) + m_startPoint.y();
}
void TransitionLine::enableModifiers(bool enabled) {
m_controlAnchor->setVisible(enabled);
m_startControlLine->setVisible(enabled);
m_endControlLine->setVisible(enabled);
m_modifiersEnabled = enabled;
if (enabled) {
m_controlAnchor->setFlag(QGraphicsItem::ItemIsSelectable, true);
m_controlAnchor->setFlag(QGraphicsItem::ItemIsMovable, true);
m_controlAnchor->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
}
}
QPointF TransitionLine::findRectLineIntersection(const QRectF& rect, const QPointF& p) const {
bool validate = false;
qreal x = p.x();
qreal y = p.y();
qreal minX = rect.x();
qreal maxX = rect.x() + rect.width();
qreal minY = rect.y();
qreal maxY = rect.y() + rect.height();
qreal midX = (minX + maxX) / 2;
qreal midY = (minY + maxY) / 2;
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
qreal m = (midY - y) / (midX - x);
if (x <= midX) { // check "left" side
qreal minXy = m * (minX - x) + y;
if (minY <= minXy && minXy <= maxY)
return QPointF(minX, minXy);
}
if (x >= midX) { // check "right" side
qreal maxXy = m * (maxX - x) + y;
if (minY <= maxXy && maxXy <= maxY)
return QPointF(maxX, maxXy);
}
if (y <= midY) { // check "top" side
qreal minYx = (minY - y) / m + x;
if (minX <= minYx && minYx <= maxX)
return QPointF(minYx, minY);
}
if (y >= midY) { // check "bottom" side
qreal maxYx = (maxY - y) / m + x;
if (minX <= maxYx && maxYx <= maxX)
return QPointF(maxYx, maxY);
}
// edge case when finding midpoint intersection: m = 0/0 = NaN
return QPointF(x, y);
}
基本上,当我创建连接器而不是起点和终点时,我将指针传递到两种状态。
当我拖动状态时,我在mouseMoveEvent(QMouseEvent* event)
子类中调用了QGraphicsView
,在其中我注意到被拖动的状态,然后调用此方法:
void Canvas::moveStateUnderMouse(QMouseEvent* event) {
auto state = getStateUnderMouse(event);
if (state != nullptr) {
std::cout << "MOUSE IN STATE" << std::endl;
viewport()->repaint();
}
}
它正常工作。仅当我选择一个状态并用鼠标拖动它时,我才能看到“状态中的鼠标”。但是,我在这里找不到告诉连接器也应该更新的方法。由于连接器具有所有需要的数据,因为它存储了状态指针,所以我认为仅在此处调用viewport()->repaint()
就足够了,但是连接器仍然存在。
当已连接状态之一改变位置时,我该如何拔插连接器?
首先,我建议您看一下Qt的Diagram Scene示例,它有点类似于您的问题。
而不是在TransitionLine
类中保留起点和终点,而应为通过线路连接的每个状态保留一个指针(在这种情况下,为“ Back”和“ Right”的指针) ,然后实现adjust()
方法(或随意命名),重新计算state1->pos()
和state2->pos()
之间的线,最后调用update()
。
然后,在SimpleStateShape
类中,您需要存储一个指向每个连接器的指针并重新实现itemChange
。它应该看起来像这样:
QVariant SimpleStateShape::itemChange(GraphicsItemChange change, QVariant value)
{
if (change == ItemPositionHasChanged && !m_connectors.isEmpty()) {
for (auto connector : m_connectors)
connector->adjust();
}
QGraphicsObject::itemChange(change, value);
}
当然,要使其正常工作,您需要在setFlag(ItemSendsGeometryChanges)
类的构造函数中调用SimpleStateShape
。