【问题标题】:QGraphicsView: repaint one item when another item is modifiedQGraphicsView:修改另一个项目时重新绘制一个项目
【发布时间】:2020-09-01 00:01:41
【问题描述】:

在 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);
}

基本上,当我创建连接器而不是起点和终点时,我将指针传递给两个状态。

当我拖动一个状态时,我在我的 QGraphicsView 子类中调用 mouseMoveEvent(QMouseEvent* event),我注意到哪个是被拖动的状态,然后我调用这个方法:

void Canvas::moveStateUnderMouse(QMouseEvent* event) {
  auto state = getStateUnderMouse(event);
  if (state != nullptr) {
    std::cout << "MOUSE IN STATE" << std::endl;
    viewport()->repaint();
  }
}

它工作正常。当我期望它时,我可以看到“MOUSE IN STATE”,只有当我选择一个状态并用鼠标拖动它时。但是我没有找到一种方法来告诉这里连接器也应该更新。由于连接器有所有需要的数据,因为它存储状态指针,我认为这里只调用viewport()-&gt;repaint() 就足够了,但连接器仍然存在。

当连接状态之一改变位置时,我应该怎么做才能更新连接器?

【问题讨论】:

  • 我猜,问题不在于连接器没有重新绘制,而是在您移动对象时它连接的点没有更新。连接器仍然连接旧坐标。确保在拖动时更新连接器的起点和终点。
  • paint 连接器事件中我不使用坐标。我有指向状态的指针,每次调用油漆时它都会检索新的点数据。
  • TransitionLine::m_startPointTransitionLine::m_endPoint 呢?
  • 我认为这是问题所在。我已经编写了更新数据的代码,但由于某种原因我没有调用它们。我查一下,谢谢。

标签: c++ qt qgraphicsview


【解决方案1】:

首先,我建议你看一下Qt的Diagram Scene example,它与你的问题有些相似。

不要在TransitionLine 类中保留起点和终点,而应为通过线连接的每个状态保留一个指针(在这种情况下,“Back”指针和“Right”指针),然后实现一个adjust() 方法(或任意命名),重新计算state1-&gt;pos()state2-&gt;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);
}  

当然,要使其工作,您需要在 SimpleStateShape 类的构造函数中调用 setFlag(ItemSendsGeometryChanges)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-10
    • 2013-01-04
    • 1970-01-01
    • 2013-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-18
    相关资源
    最近更新 更多