QGraphicsView:修改另一个项目时重新绘制一个项目

问题描述 投票:0回答:1

在QGraphicsView中,我使用两个元素:状态和连接器。

我添加了两个状态,然后创建了一个连接器。我将两个状态的指针传递给连接器,以计算起点和终点。像这样的东西:

creation

问题是,当我拖动两个状态之一时。我也希望连接器也更新。目前位置保持不变:

moved stated

这是我的州立课程:

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()就足够了,但是连接器仍然存在。

当已连接状态之一改变位置时,我该如何拔插连接器?

c++ qt qgraphicsview
1个回答
0
投票

首先,我建议您看一下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

© www.soinside.com 2019 - 2024. All rights reserved.