There is something weird with QGraphicsScene::removeItem, when I remove two items from the scene I get the following message:
QGraphicsScene::removeItem: item 0x5edb28's scene (0x0) is different from this scene (0x5e7790)
QGraphicsScene::removeItem: item 0x5edc18's scene (0x0) is different from this scene (0x5e7790)
Even though the two items are in the scene.
I've checked the items scene against the scene but it returns true:
if(Player->scene() == m_scene){
m_scene->addText("Same"); // this text is added
}else{
m_scene->addText("Different");
}
m_scene come from this class:
class Game : public QGraphicsView
{
Q_OBJECT
public:
Game();
public slots:
void FixedUpdate();
signals:
private:
QRectF *sceneSize;
QGraphicsScene *m_scene;
QGraphicsRectItem *m_rect;
QTimer *m_timer;
Character *Player;
Character *enemy;
};
And the class Character is a class inheriting from QGraphicsPolygonItem:
class Character : public QObject, public QGraphicsPolygonItem
{
Q_OBJECT
public:
Character(qreal x, qreal y, qreal w, qreal h, QString tag, QGraphicsScene *scene = 0, QGraphicsItem *parent = 0);
Character(QRectF rectF, QString tag, QGraphicsScene *scene = 0, QGraphicsItem *parent = 0);
Character(QString tag, QGraphicsScene *scene = 0, QGraphicsItem *parent = 0);
void setValue(qreal x, qreal y, qreal w, qreal h);
void setVelocity(qreal vx, qreal vy);
void setVelocityX(qreal vx);
void setVelocityY(qreal vy);
void setTag(QString tag);
qreal vx() const;
qreal vy() const;
qreal w() const;
qreal h() const;
QString tag() const;
QGraphicsScene *currentScene() const;
void fixedUpdate();
void removeCharacter();
signals:
public slots:
private:
qreal m_x;
qreal m_y;
qreal m_w;
qreal m_h;
qreal m_vx;
qreal m_vy;
QString m_tag;
QGraphicsScene *m_scene;
};
And this is where I call removeItem
void Game::FixedUpdate(){
if(Player->collidesWithItem(enemy)){
Player->setVelocityX(0);
enemy->setVelocityX(0);
//removeCharacter just call scene()->removeItem(this);
Player->removeCharacter();
enemy->removeCharacter();
}else{
Player->fixedUpdate();
enemy->fixedUpdate();
}
}
Can someone explain me why I get these messages, please?
EDIT:
Game::Game(){
sceneSize = new QRectF(0, 0, 640, 480);
m_scene = new QGraphicsScene(*sceneSize, this);
setScene(m_scene);
Player = new Character(QRectF(0, 0, 50, 50), "player", m_scene);
Player->setVelocityX(1.5);
//m_scene->addItem(Player);
enemy = new Character(QRectF(500, 0, 20, 20), "enemy", m_scene);
enemy->setVelocityX(-0.5);
//m_scene->addItem(enemy);
//m_scene->addText(QString::number(Player->x() + Player->vx()));
if(Player->scene() == m_scene){
m_scene->addText("Same");
}else{
m_scene->addText("Different");
}
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(FixedUpdate()));
m_timer->start(1000 / 60);
}
Finally I've found it.
It was in Game::FixedUpdate() function, it's Player->collidesWithItem(enemy) being true even after the pointers were removed from the scene. So I've put a check before it checks for collision, as so:
void Game::FixedUpdate(){
if(Player->scene() != NULL && enemy->scene() != NULL){
if(Player->collidesWithItem(enemy)){
Player->setVelocityX(0);
enemy->setVelocityX(0);
//removeCharacter just call scene()->removeItem(this);
Player->removeCharacter();
enemy->removeCharacter();
}else{
Player->fixedUpdate();
enemy->fixedUpdate();
}
}
}
Which does not give the messages after removal.
Related
I am trying to achieve rubber band rectangle zoom in. But here I need to zoom in only the part which comes under rubber band rectangle. I do not want to zoom in the whole view.
For that I was told to create rubber band rectangle whose point I will know. And then use
void QGraphicsView::ensureVisible(qreal x, qreal y, qreal w, qreal h, int xmargin = 50, int ymargin = 50)
or
void QGraphicsView::centerOn(qreal x, qreal y)
So just to understand, what this ensureVisible() does, I called it with some points which are present in my design. But just a pop up windows appeared but nothing was there to display. So I left that task and concentrated on, how to get rubber band rectagle points. So I tried to add some mouse events in my existing sample project but I got following error.
schematicdesign.cpp:75:47: error: non-static member 'mapToScene' found
in multiple base-class subobjects of type 'QGraphicsItem': class
SchematicDesign -> class QGraphicsRectItem -> class
QAbstractGraphicsShapeItem -> class QGraphicsItem class
SchematicDesign -> class QGraphicsPathItem -> class
QAbstractGraphicsShapeItem -> class QGraphicsItem
qgraphicsitem.h:360:13: note: member found by ambiguous name lookup
Such error is not only for mapToScene() but also for scene() and transform().
What I understood from the error is :
I have inherited schematicDesign class from multiple classes (
QGraphicsRectItem and QGraphicsPathItem ) so when it tries to find the
definition of mapToScene() or scene() or transform(), it finds in
multiple classes so gets confused in which one to pick. ( please
correct if I am wrong )
Here is my code:
Widget.h
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QGraphicsView
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_schematicButoon_clicked();
private:
Ui::Widget *ui;
QGraphicsScene* scene;
QGraphicsView* view;
};
Widget.cpp
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
view = new QGraphicsView(this);
view->setScene(scene);
ui->verticalLayout_2->addWidget(view);
}
void Widget::on_schematicButoon_clicked()
{
SchematicDesign* sd1 = new SchematicDesign();
QGraphicsRectItem* clkPin = sd1->createRect(20,160,20,20);
scene->addItem(clkPin);
// some more createRect() method
QPen mPen;
mPen.setWidth(3);
mPen.setBrush(Qt::yellow);
QPolygonF clknet;
clknet<< QPointF (40,170) << QPointF (186,170);
QGraphicsPathItem* clk = sd1->drawPolyline(clknet);
scene->addItem(clk);
// some more drawPolyline() method.
QGraphicsPathItem* text = sd1->addText(i1Instance,275.0,138.0,"I1");
scene->addItem(text);
// some more addText() method
}
SchematicDesign.h
class SchematicDesign : public QGraphicsRectItem,QGraphicsPathItem
{
public:
explicit SchematicDesign();
explicit SchematicDesign(qreal x, qreal y, qreal width, qreal height,QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(x, y, width, height, parent)
{}
explicit SchematicDesign(QPainterPath pPath);
signals:
public:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
QGraphicsRectItem* createRect(qreal x, qreal y, qreal width, qreal height);
QGraphicsPathItem* drawPolyline(QPolygonF poly);
QGraphicsPathItem* addText(QGraphicsRectItem *rect, double d1, double d2, QString s);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
private:
QPointF selectTopLeft;
bool drawingSelection;
QGraphicsRectItem* lastRect;
public:
QPoint rubberBandOrigin;
bool rubberBandActive;
};
#endif // SCHEMATICDESIGN_H
SchematicDesign.cpp
SchematicDesign::SchematicDesign(){}
SchematicDesign::SchematicDesign(QPainterPath pPath)
{}
void SchematicDesign::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
auto copied_option = *option;
copied_option.state &= ~QStyle::State_Selected;
auto selected = option->state & QStyle::State_Selected;
QGraphicsRectItem::paint(painter, &copied_option, widget);
QGraphicsPathItem::paint(painter, &copied_option, widget);
if (selected)
{
painter->save();
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(QGraphicsRectItem::shape());
painter->drawPath(QGraphicsPathItem::shape());
painter->restore();
}
}
QGraphicsRectItem *SchematicDesign::createRect(qreal x, qreal y, qreal width, qreal height)
{...... }
QGraphicsPathItem* SchematicDesign::drawPolyline(QPolygonF poly)
{....}
QGraphicsPathItem* SchematicDesign::addText(QGraphicsRectItem *rect, double d1, double d2, QString s)
{.....}
void SchematicDesign::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem * sceneItem = scene()->itemAt(mapToScene(event->pos()),transform()); //error
if(!sceneItem){
selectTopLeft = event->pos();
event->pos();
drawingSelection = true;
}
QGraphicsRectItem::mousePressEvent(event);
}
void SchematicDesign::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if(drawingSelection){
QGraphicsItem * itemToRemove = lastRect;
if(itemToRemove){
fitInView(itemToRemove,Qt::KeepAspectRatio); //error at fitInView : Use of undeclared identifier
scene()->removeItem(itemToRemove); //error at scene()
delete itemToRemove;
lastRect = nullptr;
}
}
drawingSelection = false;
QGraphicsRectItem::mouseReleaseEvent(event);
}
void SchematicDesign::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(drawingSelection){
//Selection region
int x = selectTopLeft.rx();
int y = selectTopLeft.ry();
int h = event->pos().rx();
int w = event->pos().ry();
//QRect selectRegion = QRect(selectTopLeft,event->pos());
QRect selectRegion = QRect(x,y,h,w);
QPainterPath path;
path.addRect(selectRegion);
scene()->setSelectionArea(mapToScene(path)); //error at scene()
//Draw visual feedback for the user
QGraphicsItem * itemToRemove = lastRect;
scene()->removeItem(itemToRemove); // error at scene()
lastRect = scene()->addRect(QRectF(mapToScene(selectTopLeft),
mapToScene(event->pos())).normalized()); // error at scene() and mapToScene()
lastRect->setBrush(QBrush(QColor(255, 0, 0, 50)));
delete itemToRemove;
}
QGraphicsRectItem::mouseMoveEvent(event);
}
How to tackle such type of error ? Should not I inherit my class from multiple classes ?
I am new at Qt and I want to write my custom QGraphicsItem which contains a rectangle and couple of buttons. I want to write a single custom component that could be easily added to QGraphicsScene and moved or resized with contents(buttons and rectangles) in it. In the end I want to add multiple customized QGraphicsItem to my QGraphicsScene. My question is how can I write this customized QGraphicsItem that contains buttons and rectangles which relative positions to each other are constant.
In this drawing green colored rectangles represent buttons and their relative position to each other always stays same (as if they are placed using qlayouts)
Thanks to #replete, from the example at http://doc.qt.io/qt-5/qtwidgets-graphicsview-dragdroprobot-example.html I was able to create a custom QGraphicsItem with clickable sub-parts in it. In code below BboxItem represents container QGraphicsItem and BboxItemContent represents childs of it. By emitting signals whith mause click events I was able to implement button like features. And I can move the BboxItem by setting its bounding rectangle.
BboxItem related source code:
BboxItemContent::BboxItemContent(QGraphicsItem *parent, int type, QColor color,QRectF *rect)
: QGraphicsObject(parent)
{
content_rectangle = rect;
content_type = type;
switch (type)
{
case 0:
rectangle_color = color;
icon = 0;
break;
case 1:
icon = new QImage(":/resource/assets/info_btn.png");
break;
case 2:
icon = new QImage(":/resource/assets/close_btn.png");
break;
}
}
BboxItemContent::~BboxItemContent()
{
delete icon;
}
QRectF BboxItemContent::boundingRect() const
{
return QRectF(content_rectangle->x(), content_rectangle->y(), content_rectangle->width(), content_rectangle->height());
}
void BboxItemContent::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (icon == 0)
{
QPen pen(rectangle_color, 3);
painter->setPen(pen);
painter->drawRect(*content_rectangle);
}
else
{
painter->drawImage(*content_rectangle, *icon);
}
}
void BboxItemContent::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
emit bboxContentClickedSignal();
}
void BboxItemContent::setRect(QRectF *rect)
{
content_rectangle = rect;
update();
}
BboxItem::BboxItem(QGraphicsItem *parent,QRectF *itemRect) : BboxItemContent(parent,0,Qt::red, itemRect)
{
setFlag(ItemHasNoContents);
bbox_area = new BboxItemContent(this, 0, Qt::red, itemRect);
info_btn = new BboxItemContent(this, 1, Qt::red, new QRectF(itemRect->x() - 30, itemRect->y(), 30, 30));
connect(info_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onInfoClickedSlot);
delete_btn= new BboxItemContent(this, 2, Qt::red, new QRectF((itemRect->x()+itemRect->width()), itemRect->y(), 30, 30));
connect(delete_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onDeleteClickedSlot);
}
void BboxItem::onDeleteClickedSlot()
{
//delete clicked actions
}
void BboxItem::onInfoClickedSlot()
{
//info clicked actions
}
void BboxItem::setRect(QRectF *rect)
{
bbox_area->setRect(rect);
info_btn->setRect(new QRectF(rect->x() - 30, rect->y(), 30, 30));
delete_btn->setRect(new QRectF((rect->x() + rect->width()), rect->y(), 30, 30));
}
Related Headers:
class BboxItemContent : public QGraphicsObject
{
Q_OBJECT
public:
BboxItemContent(QGraphicsItem *parent = 0, int type = 0, QColor color = Qt::red, QRectF *rect=nullptr);
~BboxItemContent();
// Inherited from QGraphicsItem
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
void setRect(QRectF *rect);
signals:
void bboxContentClickedSignal();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
private:
QImage *icon;
QColor rectangle_color;
QRectF *content_rectangle;
int content_type;
};
class BboxItem : public BboxItemContent {
Q_OBJECT
public:
BboxItem(QGraphicsItem *parent = 0,QRectF *itemRect=nullptr);
void setRect(QRectF *rect);
private slots:
void onDeleteClickedSlot();
void onInfoClickedSlot();
private:
BboxItemContent *delete_btn;
BboxItemContent *bbox_area;
BboxItemContent *info_btn;
};
I am trying to make a simple extension of QGraphicsRectItem that allows me to resize the rectangle and move it around with the mouse. I model the handles with elliptical arches on the corners I want to enable for dragging, which I implement as QGraphicsEllipseItems:
class QGraphicsBoxWithHandlesItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
typedef enum {
None,
BottomLeft,
TopRight
} ActiveAnchor;
private:
QGraphicsEllipseItem m_anchorBottomLeft;
QGraphicsEllipseItem m_anchorTopRight;
float m_anchorRadius;
ActiveAnchor m_activeAnchor;
public:
QGraphicsBoxWithHandlesItem(QRectF r, float handlesRadius = 20.0, QGraphicsItem *parent = nullptr);
void setAnchorRadius(float radius);
float getAnchorRadius();
QPainterPath shape() const;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent * event);
void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
};
I want to be able to detect clicks both on the rectangle and on the handle items (this is necessary because if the rectangle gets too small the handles are the only easily clickable area), so I thought I'd extend QGraphicsRectItem::shape() to add to the returned QPainterPath the paths of the sub-items (adjusting coordinates to make them relative to the parent item):
QPainterPath QGraphicsBoxWithHandlesItem::shape() const
{
auto curShape = QGraphicsRectItem::shape();
curShape.addPath( mapFromItem(&m_anchorBottomLeft, m_anchorBottomLeft.shape()) );
curShape.addPath( mapFromItem(&m_anchorTopRight, m_anchorTopRight.shape()) );
return curShape;
}
What I get, however, is that now clicks inside the handles areas are completely ignored and only clicks in the central area of the rectangle are handled.
What is the correct way to extend the clickable area of an item when it has a non-trivial shape?
Update: I tried to set the ItemIsSelectable flag on a handle and now, if I click on it, I see that it gets selected. I still don't get any mousePressEvent in the parent, however. What am I doing wrong?
Edit:
This is the constructor implementation:
QGraphicsBoxWithHandlesItem::QGraphicsBoxWithHandlesItem( QRectF r, float handlesRadius, QGraphicsItem * parent) :
QGraphicsRectItem(parent),
m_anchorRadius(handlesRadius),
m_activeAnchor(None)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setRect(r);
m_anchorBottomLeft.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorBottomLeft.setPos(rect().bottomLeft());
m_anchorBottomLeft.setSpanAngle(90 * 16); // angle is in 16ths of degree
m_anchorBottomLeft.setParentItem(this);
m_anchorTopRight.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorTopRight.setPos(rect().topRight());
m_anchorTopRight.setStartAngle(180 * 16); // angle is in 16ths of degree
m_anchorTopRight.setSpanAngle(90 * 16); // angle is in 16ths of degree
m_anchorTopRight.setParentItem(this);
}
Your QGraphicsEllipseItem are on top of the base item so the mouse events will never come to you.
What you have to do is use a sceneEventFilter, but since the QGraphicsEllipseItem are children of the main item they will never move, so they should not have a parent but you should add them directly to the scene.
The full functionality is implemented in the following code:
*.h
#ifndef QGRAPHICSBOXWITHHANDLESITEM_H
#define QGRAPHICSBOXWITHHANDLESITEM_H
#include <QGraphicsRectItem>
#include <QObject>
class QGraphicsBoxWithHandlesItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
enum ActiveAnchor{
None,
BottomLeft,
TopRight
};
public:
QGraphicsBoxWithHandlesItem(QRectF r, float handlesRadius = 20.0, QGraphicsItem *parent = nullptr);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
private:
QGraphicsEllipseItem m_anchorBottomLeft;
QGraphicsEllipseItem m_anchorTopRight;
float m_anchorRadius;
ActiveAnchor m_activeAnchor;
};
#endif // QGRAPHICSBOXWITHHANDLESITEM_H
*.cpp
#include "qgraphicsboxwithhandlesitem.h"
#include <QEvent>
#include <QGraphicsScene>
#include <QDebug>
QGraphicsBoxWithHandlesItem::QGraphicsBoxWithHandlesItem( QRectF r, float handlesRadius, QGraphicsItem * parent) :
QGraphicsRectItem(parent),
m_anchorRadius(handlesRadius),
m_activeAnchor(None)
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
setRect(r);
m_anchorBottomLeft.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorBottomLeft.setPos(rect().bottomLeft());
m_anchorBottomLeft.setSpanAngle(90 * 16); // angle is in 16ths of degree
//m_anchorBottomLeft.setParentItem(this);
m_anchorBottomLeft.setFlag(QGraphicsItem::ItemIsMovable);
m_anchorBottomLeft.setFlag(QGraphicsItem::ItemIsSelectable);
m_anchorTopRight.setRect(-m_anchorRadius, -m_anchorRadius, m_anchorRadius*2, m_anchorRadius*2);
m_anchorTopRight.setPos(rect().topRight());
m_anchorTopRight.setStartAngle(180 * 16); // angle is in 16ths of degree
m_anchorTopRight.setSpanAngle(90 * 16); // angle is in 16ths of degree
//m_anchorTopRight.setParentItem(this);
m_anchorTopRight.setFlag(QGraphicsItem::ItemIsMovable);
m_anchorTopRight.setFlag(QGraphicsItem::ItemIsSelectable);
}
QVariant QGraphicsBoxWithHandlesItem::itemChange(GraphicsItemChange change, const QVariant & value){
if(change == QGraphicsItem::ItemSceneHasChanged){
if(scene()){
scene()->addItem(&m_anchorBottomLeft);
scene()->addItem(&m_anchorTopRight);
m_anchorBottomLeft.installSceneEventFilter(this);
m_anchorTopRight.installSceneEventFilter(this);
}
}
else if (change == QGraphicsItem::ItemPositionHasChanged) {
m_anchorBottomLeft.setPos(mapToScene(rect().bottomLeft()));
m_anchorTopRight.setPos(mapToScene(rect().topRight()));
}
return QGraphicsRectItem::itemChange(change, value);
}
bool QGraphicsBoxWithHandlesItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
if(watched == &m_anchorTopRight){
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:{
//mousePressEvent
qDebug()<<"mousePressEvent m_anchorTopRight";
break;
}
case QEvent::GraphicsSceneMouseMove:{
// mouseMoveEvent
QRectF r = rect();
auto p = m_anchorTopRight.mapToScene(m_anchorTopRight.rect().center());
r.setTopRight(mapFromScene(p));
setRect(r);
qDebug()<<"mouseMoveEvent m_anchorTopRight";
break;
}
case QEvent::GraphicsSceneMouseRelease :{
//mouseReleaseEvent
qDebug()<<"mouseReleaseEvent m_anchorTopRight";
break;
}
}
}
if(watched == &m_anchorBottomLeft){
switch (event->type()) {
case QEvent::GraphicsSceneMousePress:{
//mousePressEvent
qDebug()<<"mousePressEvent m_anchorBottomLeft";
break;
}
case QEvent::GraphicsSceneMouseMove:{
// mouseMoveEvent
QRectF r = rect();
auto p = m_anchorBottomLeft.mapToScene(m_anchorBottomLeft.rect().center());
r.setBottomLeft(mapFromScene(p));
setRect(r);
qDebug()<<"mouseMoveEvent m_anchorBottomLeft";
break;
}
case QEvent::GraphicsSceneMouseRelease :{
//mouseReleaseEvent
qDebug()<<"mouseReleaseEvent m_anchorBottomLeft";
break;
}
}
}
return QGraphicsRectItem::sceneEventFilter(watched, event);
}
I have some QGraphicsItems in the QGraphicsScene which should keep the same size and position when scaling. I've tried QGraphicsItem::ItemIgnoresTransformations but it turns out that the items get wrong positions. Below is a sample code:
I have subclassed QGraphicsView like this:
class Graphics : public QGraphicsView
{
public:
Graphics();
QGraphicsScene *scene;
QGraphicsRectItem *rect;
QGraphicsRectItem *rect2;
protected:
void wheelEvent(QWheelEvent *event);
};
And in its constructor:
Graphics::Graphics()
{
scene = new QGraphicsScene;
rect = new QGraphicsRectItem(100,100,50,50);
rect2 = new QGraphicsRectItem(-100,-100,50,50);
scene->addLine(0,200,200,0);
rect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
scene->addItem(rect);
scene->addItem(rect2);
setScene(scene);
scene->addRect(scene->itemsBoundingRect());
}
The wheelEvent virtual function:
void Graphics::wheelEvent(QWheelEvent *event)
{
if(event->delta() < 0)
scale(1.0/2.0, 1.0/2.0);
else
scale(2, 2);
scene->addRect(scene->itemsBoundingRect());
qDebug() << rect->transform();
qDebug() << rect->boundingRect();
qDebug() << rect2->transform();
qDebug() << rect2->boundingRect();
}
orginal view looks like this:
1
take the line as road and rect aside as a symbol. When zoomed out, the rect maintain its size but jumps out of the scene:
2
which should be that topleft of rect to middle of line. I'm also confused with debug info showing that the boundingRect and transform stays the same, which seems that nothing has changed! What causes the problem and is there any way to solve it? Could someone help? Thank you!
Sorry for delay, now I've solved the problem myself.
I found QGraphicsItem::ItemIgnoresTransformations only works when the point you want stick to is at (0,0) in item's coordinate. You need also update boundingRect manually in this way. Nevertheless, the best solution I've found is subclass QGraphicsItem and set matrix in paint() according to world matrix. Below is my code .
QMatrix stableMatrix(const QMatrix &matrix, const QPointF &p)
{
QMatrix newMatrix = matrix;
qreal scaleX, scaleY;
scaleX = newMatrix.m11();
scaleY = newMatrix.m22();
newMatrix.scale(1.0/scaleX, 1.0/scaleY);
qreal offsetX, offsetY;
offsetX = p.x()*(scaleX-1.0);
offsetY = p.y()*(scaleY-1.0);
newMatrix.translate(offsetX, offsetY);
return newMatrix;
}
And the paint function:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPointF p(left, top);
painter->setMatrix(stableMatrix(painter->worldMatrix(), p));
painter->drawRect(left, top, width, height);
}
The second argument of stableMatrix is sticked point, in my sample code it's top-left of the item. You can change it to your preference. It works really fine!
Hope this post help :)
The solution to this is even simpler.
QGraphicsItem::ItemIgnoresTransformations
The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). [...]
And that's the key! Item ignores all transformations, but is still bound to its parent. So you need two items: a parent item that will keep the relative position (without any flags set) and a child item that will do the drawing (with QGraphicsItem::ItemIgnoresTransformations flag set) at parent's (0,0) point.
Here is some working code of a crosshair that have constant size and rotation, while keeping the relative position to its parent:
#include <QGraphicsItem>
#include <QPainter>
class CrossHair : public QGraphicsItem
{
private:
class CrossHairImpl : public QGraphicsItem
{
public:
CrossHairImpl (qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_len(len)
{
setFlag(QGraphicsItem::ItemIgnoresTransformations);
}
QRectF boundingRect (void) const override
{
return QRectF(-m_len, -m_len, m_len*2, m_len*2);
}
void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
painter->setPen(QPen(Qt::red, 1));
painter->drawLine(0, -m_len, 0, m_len);
painter->drawLine(-m_len, 0, m_len, 0);
}
private:
qreal m_len;
};
public:
CrossHair (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_impl(len, this) // <-- IMPORTANT!!!
{
setPos(x, y);
}
QRectF boundingRect (void) const override
{
return QRectF();
}
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
{
// empty
}
private:
CrossHairImpl m_impl;
};
I was trying to do a simple animation on QGrapichScene. I implemented void QGraphicsItem::advance(int) in class, that inherites QGraphicsItem, but after calling advance() my item not redrawn. In colliding mice example it works.
What have I done wrong?
Here is my code:
widget.h:
class Widget : public QWidget
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsView *view;
QHBoxLayout *layout;
QTimer t;
public:
Widget(QWidget *parent = 0);
~Widget();
};
widget.cpp:
Widget::Widget(QWidget *parent) : QWidget(parent)
{
layout = new QHBoxLayout(this);
view = new QGraphicsView(this);
scene = new QGraphicsScene(0, 0, 400, 400, view);
scene->addItem(new MyItem());
view->setScene(scene);
layout->addWidget(view);
setLayout(layout);
connect(&t, SIGNAL(timeout()), scene, SLOT(advance()));
t.start(100);
}
Widget::~Widget()
{
}
my_item.h:
class MyItem : public QGraphicsItem
{
private:
QRect bRect;
enum directon { left, right };
directon currentDir;
protected:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
public:
MyItem(int w = 20);
virtual void advance(int phase);
virtual QRectF boundingRect() const
{ return QRectF(bRect); }
};
my_item.cpp:
MyItem::MyItem(int w)
{
currentDir = right;
bRect = QRect(0, 0, w, w);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
qDebug() << "In void MyItem::paint(QPainter*, "
"const QStyleOptionGraphicsItem*, "
"QWidget*)";
painter->fillRect(bRect, Qt::red);
}
void MyItem::advance(int phase)
{
qDebug() << "In void MyItem::advance(int);"
<< "Phase =" << phase;
if(!phase)
return;
// Than move item to new positon...
}
It's on you to inform the graphics scene that your item's contents have changed. You have to call update() at the end of advance(). If you're simply moving the item, without changing its contents, then you don't need to call update() of course - the scene will detect such changes automatically.
I found something strange:
connect(&timer, SIGNAL(timeout()), scene, SLOT(advance()));
The slot need to combined after the Widget is shown, or the slot will never been called!
As the Qt example shown, you can combine it in the main function.