4

我使用QGraphicsSceneQt 框架。在场景里面我有一些QGraphicsItem用户可以选择和移动的 s。我想要一个信息标签,其中显示当前移动的选择(可以包含许多项目)的当前 x 和 y 坐标。

我已经尝试过changed使用QGraphicsScene. 但它在项目的 x() 和 y() 属性设置为新值之前被触发。所以标签总是显示倒数第二个坐标。如果一个人慢慢移动鼠标,显示不是很错误。但是随着快速移动和突然停止,标签是错误的。我需要一个在之后触发的信号我需要一个在场景改变

我也尝试过覆盖itemChange. QGraphicsItem但它是一样的。它在更改之前被触发。(新坐标在这个方法的参数里面,但是我需要一次所有选中项的新坐标)

我也尝试过覆盖 和 的mouseMove事件,QGraphicsSceneQGraphicsView它们也是在设置新坐标之前。

我做了一个测试:我使用了一个单次计时器,以便在信号发出 100 毫秒后更新标签。然后一切正常。但是计时器对我来说不是解决方案。

我能做些什么?让所有物品无法移动并由我自己处理?

4

2 回答 2

5

QGraphicsItem::itemChange()是正确的方法,您可能只是检查了错误的标志。这样的事情应该可以正常工作:

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

注意使用QGraphicsItem::ItemPositionHasChanged而不是QGraphicsItem::ItemPositionChange,前者是在位置改变之后而不是之前调用的。

于 2012-06-28T14:14:00.523 回答
3

解决方案是将您已经在做的各种事情结合起来。Instrument itemChange,查找并计算具有更新几何的项目。一旦您计算出与当前选择中的项目一样多的项目,请发出一个信号,让一切准备好更新您的状态。确保您已QGraphicsItem::ItemSendsGeometryChanges在所有物品上设置标志!

编辑此代码以消除使用零计时器方法所固有的滞后。下面是一个演示它的sscce

您可以通过在窗口中单击来创建随机半径的圆。使用 Ctrl-click 或 ⌘-click 切换选择。移动项目时,质心菱形会跟随所选组的质心。这可以直观地确认代码确实有效。选择为空时,不显示质心。

我无偿添加了代码来展示如何利用 Qt 的属性系统,以便项目可以是通用的,并利用notifier场景的属性(如果有的话)。在它不存在的情况下,这些项目根本不会通知,仅此而已。

示例截图

// 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"
于 2012-06-27T22:38:07.430 回答