4

QGraphicsSceneQtフレームワークを使用しています。シーン内にはQGraphicsItem、ユーザーが選択して移動できる がいくつかあります。現在移動されている選択範囲 (多くの項目で構成されている可能性があります) の現在の x 座標と y 座標が表示される情報ラベルが必要です。

の信号changedで試してみましたQGraphicsScene。ただし、アイテムの x() および y() プロパティが新しい値に設定される前に発生します。そのため、ラベルには常に最後から 2 番目の座標が表示されます。マウスをゆっくり動かすと、表示はそれほど間違っていません。しかし、速い動きと突然の停止では、ラベルが間違っています。シーンが変わったに発生する信号が必要です。

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-クリックまたは ⌘-クリックで切り替えられます。アイテムを移動すると、重心の菱形が選択したグループの重心に従います。これにより、コードが実際に機能することを視覚的に確認できます。選択が空の場合、重心は表示されません。

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 に答える