我用QGraphicsScene
Qt框架的。 里面的场景我有一些QGraphicsItem
S的用户可以选择和移动。 我想有一个信息标签,其中,目前移动选择的当前x和y坐标(可以包含很多项目)的显示。
我曾尝试与信号changed
的QGraphicsScene
。 但它是X()和项目的Y()属性之前解雇被设置为新值。 所以标签始终显示第二到最后一个坐标。 如果一个人慢慢地移动鼠标,显示器是不是非常错误的。 但随着快速移动和突然停止,标签是错误的。 我需要现场竟改变后触发的信号。
我也试图重写itemChange
的方法QGraphicsItem
。 但它是相同的。 它改变之前被解雇。 (新坐标是该方法的参数里面,但我需要一次所有选定项的新坐标)
我也试图重写mouseMove
事件QGraphicsScene
和QGraphicsView
,但他们也都被设定的新坐标前 。
我做了一个测试:我用一个单稳态定时器,以使标签的信号后100毫秒更新一次。 然后一切工作正常。 但计时器对我来说是无解的。
我能做什么? 让所有项目未动,并通过我自己处理一切?
QGraphicsItem::itemChange()
是正确的做法,你可能只是检查了错误的标志。 这样的事情应该很好地工作:
QVariant::myGraphicsItem( GraphicsItemChange change, const QVariant &value )
{
if( change == QGraphicsItem::ItemPositionHasChanged )
{
// ...
}
}
注意使用QGraphicsItem::ItemPositionHasChanged
而非QGraphicsItem::ItemPositionChange
,位置变化之后 ,而不是之前,前者被调用。
解决的办法是,你已经在做各种各样的事情结合起来。 仪器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(¢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"