Need QGraphicsScene signal or event for _after_ change

前端 未结 2 2172
既然无缘
既然无缘 2020-12-06 20:44

I use QGraphicsScene of the Qt framework. Inside the scene I have some QGraphicsItems which the user can select and move. I would like to have an i

2条回答
  •  北海茫月
    2020-12-06 21:11

    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 
    
    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 NotifierPointer;
    Q_DECLARE_METATYPE(NotifierPointer)
    
    template  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();
             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.
    
    class Circle : public NotifyingItem
    {
       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(¢roid);
       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(¬ifier)));
       connect(¬ifier, &Notifier::notification, this, &View::gotUpdates);
       connect(&scene, &QGraphicsScene::selectionChanged, ¬ifier, &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"
    

提交回复
热议问题