Need QGraphicsScene signal or event for _after_ change

前端 未结 2 2135
既然无缘
既然无缘 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 20:51

    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.

    0 讨论(0)
  • 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 <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"
    
    0 讨论(0)
提交回复
热议问题