Need QGraphicsScene signal or event for _after_ change

≡放荡痞女 提交于 2019-11-28 01:23:00

QGraphicsItem::itemChange() is the correct approach, you were probably just checking the wrong flag. Something like this should work fine:

QVariant::myGraphicsItem( GraphicsItemChange change, const QVariant &value )
{
  if( change == QGraphicsItem::ItemPositionHasChanged )
  {
     // ...
  }
}

Note the use of QGraphicsItem::ItemPositionHasChanged rather than QGraphicsItem::ItemPositionChange, the former is called after the position changes rather than before.

The solution is to combine various things that you're already doing. Instrument itemChange, looking for and count the items with updated geometry. Once you've counted as many items as there are in the current selection, fire off a signal that will have everything ready for updating your status. Make sure you've set the QGraphicsItem::ItemSendsGeometryChanges flag on all your items!

This code was edited to remove the lag inherent in using a zero-timer approach. Below is a sscce that demonstrates it.

You create circles of random radius by clicking in the window. The selection is toggled with Ctrl-click or ⌘-click. When you move the items, a centroid diamond follows the centroid of the selected group. This gives a visual confirmation that the code does indeed work. When the selection is empty, the centroid is not displayed.

I've gratuitously added code to show how to leverage Qt's property system so that the items can be generic and leverage the notifier property of a scene if it has one. In its absence, the items simply don't notify, and that's it.

// https://github.com/KubaO/stackoverflown/tree/master/questions/scenemod-11232425
#include <QtWidgets>

const char kNotifier[] = "notifier";

class Notifier : public QObject
{
   Q_OBJECT
   int m_count = {};
public:
   int count() const { return m_count; }
   void inc() { m_count ++; }
   void notify() { m_count = {}; emit notification(); }
   Q_SIGNAL void notification();
};

typedef QPointer<Notifier> NotifierPointer;
Q_DECLARE_METATYPE(NotifierPointer)

template <typename T> class NotifyingItem : public T
{
protected:
   QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override {
      QVariant v;
      if (change == T::ItemPositionHasChanged &&
          this->scene() &&
          (v=this->scene()->property(kNotifier)).isValid())
      {
         auto notifier = v.value<NotifierPointer>();
         notifier->inc();
         if (notifier->count() >= this->scene()->selectedItems().count()) {
            notifier->notify();
         }
      }
      return T::itemChange(change, value);
   }
};

// Note that all you need to make Circle a notifying item is to derive from
// NotifyingItem<basetype>.

class Circle : public NotifyingItem<QGraphicsEllipseItem>
{
   QBrush m_brush;
public:
   Circle(const QPointF & c) : m_brush(Qt::lightGray) {
      const qreal r = 10.0 + (50.0*qrand())/RAND_MAX;
      setRect({-r, -r, 2.0*r, 2.0*r});
      setPos(c);
      setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable |
               QGraphicsItem::ItemSendsGeometryChanges);
      setPen({Qt::red});
      setBrush(m_brush);
   }
};

class View : public QGraphicsView
{
   Q_OBJECT
   QGraphicsScene scene;
   QGraphicsSimpleTextItem text;
   QGraphicsRectItem centroid{-5, -5, 10, 10};
   Notifier notifier;
   int deltaCounter = {};
public:
   explicit View(QWidget *parent = {});
protected:
   Q_SLOT void gotUpdates();
   void mousePressEvent(QMouseEvent *event) override;
};

View::View(QWidget *parent) : QGraphicsView(parent)
{
   centroid.hide();
   centroid.setRotation(45.0);
   centroid.setPen({Qt::blue});
   centroid.setZValue(2);
   scene.addItem(&centroid);
   text.setPos(5, 470);
   text.setZValue(1);
   scene.addItem(&text);
   setRenderHint(QPainter::Antialiasing);
   setScene(&scene);
   setSceneRect(0,0,500,500);
   scene.setProperty(kNotifier, QVariant::fromValue(NotifierPointer(&notifier)));
   connect(&notifier, &Notifier::notification, this, &View::gotUpdates);
   connect(&scene, &QGraphicsScene::selectionChanged, &notifier, &Notifier::notification);
}

void View::gotUpdates()
{
   if (scene.selectedItems().isEmpty()) {
      centroid.hide();
      return;
   }
   centroid.show();
   QPointF centroid;
   qreal area = {};
   for (auto item : scene.selectedItems()) {
      const QRectF r = item->boundingRect();
      const qreal a = r.width() * r.height();
      centroid += item->pos() * a;
      area += a;
   }
   if (area > 0) centroid /= area;
   auto st = QStringLiteral("delta #%1 with %2 items, centroid at %3, %4")
         .arg(deltaCounter++).arg(scene.selectedItems().count())
         .arg(centroid.x(), 0, 'f', 1).arg(centroid.y(), 0, 'f', 1);
   this->centroid.setPos(centroid);
   text.setText(st);
}

void View::mousePressEvent(QMouseEvent *event)
{
   const auto center = mapToScene(event->pos());
   if (! scene.itemAt(center, {})) scene.addItem(new Circle{center});
   QGraphicsView::mousePressEvent(event);
}

int main(int argc, char *argv[])
{
   QApplication app{argc, argv};
   View v;
   v.show();
   return app.exec();
}
#include "main.moc"
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!