I have a QGraphicsItem with an embedded QWidget, this QWidget have a QPushButton in it.
I'm trying to map the center of the QPushButton to the QGraphicsScene coordinates, so for example, I can add a Circle to the center of the QPushButton.
With the help from another post I was able to find the center of the QPushButton, but it doesn't correspond to its actual position in the QGraphicsScene.
What I tried:
Getting the QRect of the button and then its center, finally mapping to the global coordinates of the view.
Getting the QRect of the button, then its center and mapping it to the QGraphicsItem.
Getting the QRect of the button, then its center, mapping it to the QGraphicsItem and than mapping it to the scene.
In general, I tried mapping it to Scene, to Global and to Item but it always looks incorrect. And the farther I move the QGraphicsItem, the less accurate it gets. Here the circle is supposed to be positioned at the center of the "B" button:
Qwidget:
class NodeFrame : public QFrame
{
public:
NodeFrame();
QRect getButtonRect()
{
return layout->itemAt(0)->geometry();
}
private:
QPushButton* button = nullptr;
QHBoxLayout* layout = nullptr;
};
NodeFrame::NodeFrame()
{
setFixedSize(200,80);
// Creates and add a QPushButton to the frame.
// I need the position of this button on the QGraohicsScene
button = new QPushButton("B");
button->setFixedSize(40,20);
layout = new QHBoxLayout();
layout->addWidget(button);
layout->setAlignment(Qt::AlignCenter);
setLayout(layout);
}
QGraphicsItem:
class Node : public QGraphicsItem
{
public:
Node();
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QRect getButtonRect()
{
return frame->getButtonRect();
}
NodeFrame* frame = nullptr;
};
Node::Node()
{
setFlag(ItemIsMovable);
// Create a GraphicsProxyWidget to insert the nodeFrame into the scene
auto proxyWidget = new QGraphicsProxyWidget(this);
frame = new NodeFrame();
proxyWidget->setWidget(frame);
// Center the widget(frame) at the center of the QGraphicsItem
proxyWidget->setPos(boundingRect().center() - proxyWidget->boundingRect().center());
}
QRectF Node::boundingRect() const
{
return QRectF(-10, -10, 280, 150);
}
void Node::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QPainterPath path;
path.addRoundedRect(boundingRect(), 10, 10);
painter->drawPath(path);
}
main:
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// Create scene and view
auto scene = new QGraphicsScene();
auto view = new QGraphicsView(scene);
view->setMinimumSize(800, 800);
// Create the QGraphicsItem and add it to the scene
auto item = new Node();
scene->addItem(item);
item->setPos(50, 50);
auto btnRect = item->getButtonRect();
auto center = view->mapToGlobal(btnRect.center());
auto circle = new QGraphicsEllipseItem();
circle->setRect(QRectF(center.x(), center.y(), 25, 25));
scene->addItem(circle);
// Show the the view
view->show();
return app.exec();
}
Appreciate any help.
Solved. This was caused by two things:
1: QRectF getButtonRect() was returning layout->itemAt(0)->geometry(), (index 0 being the first and only widget in the layout) but button->frameGeometry() seems to be a more accurate visual representation of the button's geometry.
2: When adding the widget to the graphic item using QGraphicsProxyWidget, I was adjusting the position of the widget inside the graphic item using:
proxyWidget->setPos(boundingRect().center() - proxyWidget->boundingRect().center());
This was changing the position (obviously) of the widget inside the graphic item, so visually it didn't align with the result given by button->frameGeometry().
Related
We are having an issue with artifacts in an application using multiple QGraphicsScene/QGraphicsViews. Essentially it seems the problem is when when in mousePosChanged event handler for a scene, and call setPos() on an item in a different scene, as well as update a region on the view for the other scene, it leaves artifacts.
I tried to set up a minimal example that hopefully will be easy to spot what is wrong
Essentially I have two scenes (scene 1 and scene 2), each with one ellipse item. When the mouse moves in the first scene, its ellipse item tracks the mouse. It also sends out a signal with mouse position. The second scene is connected to this signal, and updates the position of its ellipse item to the same location. The second graphics view is also connected to the signal, and it updates its viewport in an arbitrary location.
The second graphics view ends up with artifacts as you move the mouse around (see picture below)
This is the code from my minimal example:
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QGraphicsScene *scene, QWidget *parent = 0);
public slots:
void onMouseMoveEvent();
};
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject *parent = 0);
public slots:
void setTrackerPos(const QPointF &pos);
signals:
void mousePosChanged(const QPointF &pos);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
private:
QGraphicsEllipseItem *mCursorTracker;
};
MyScene::MyScene(QObject *parent) :
QGraphicsScene(QRectF(0.,0.,1000.,1000.), parent),
mCursorTracker(new QGraphicsEllipseItem(0., 0., 50., 50.))
{
mCursorTracker->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
mCursorTracker->setBrush(QBrush(Qt::red, Qt::SolidPattern));
addItem(mCursorTracker);
}
void MyScene::setTrackerPos(const QPointF &pos)
{
mCursorTracker->setPos(pos);
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
mCursorTracker->setPos(mouseEvent->scenePos());
emit mousePosChanged(mouseEvent->scenePos());
}
MyView::MyView(QGraphicsScene *scene, QWidget *parent) :
QGraphicsView(scene, parent)
{
setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
setMouseTracking(true);
}
void MyView::onMouseMoveEvent(const QPointF &pos)
{
viewport()->update(QRect(0,0,250,250));
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(w);
MyScene *scene1 = new MyScene(w);
MyScene *scene2 = new MyScene(w);
MyView *view1 = new MyView(scene1, w);
MyView *view2 = new MyView(scene2, w);
layout->addWidget(view1);
layout->addWidget(view2);
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), scene2, SLOT(setTrackerPos(QPointF)));
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), view2, SLOT(onMouseMoveEvent()));
w->show();
return a.exec();
}
I understand that changing to a full update on the view will fix this. However in our real application it is too expensive to repaint the whole scene. The update on the viewport is for a small foreground layer, and only one graphics item position changes (like in this example)
I can't figure out how the setPos() function of the QGraphicsItem class works.
My Rect class has no parent, so its origin is relative to the scene.
I try to put the rectangle back at (0, 0) after it is moved with the mouse but it is placed in a different place depending on where I had moved it.
I suppose that means that the origin of the scene moves but what causes this change?
class Rect : public QGraphicsItem {
public:
Rect(): QGraphicsItem()
{
setFlag(ItemIsMovable);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->drawRect(0, 0, 20, 20);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
{
setPos(0, 0);
update();
QGraphicsItem::mouseReleaseEvent(event);
}
QRectF boundingRect() const
{
return QRectF(0, 0, 20, 20);
}
private:
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
Rect obj;
scene.addItem(&obj);
view.show();
return a.exec();
}
When you create a QGraphicsView you initially accept the default settings. A standard setting is, for example, that it is horizontally centered.
Another factor is that the default area size is probably up to the maximum size.
what you can do set a custom size for the scene. You do that with graphicsView->setSceneRect(0,0,300,300); (for example)
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
ui->graphicsView->setSceneRect(0,0, 300,300);
rectItem = new QGraphicsRectItem(0,0, 100, 100);
rectItem->setPen(QPen(Qt::darkMagenta, 2));
rectItem->setBrush(QGradient(QGradient::SaintPetersburg));
rectItem->setPos(190,10);
scene->addItem(rectItem);
So in summary: if you want to work with fixed values. maybe it is better to know the total size. (that was not clear from your code, that's why I gave this example)
I have QGraphicsView which contains many QGraphicsItem such as Rectangle, polylines. I have overriden paint() method. I am drawing QGraphicsItem using boost-graph. While drawing items, I am storing boost-graph pointer on every QGraphicsItem.
Now I am right clicking on some rectangle from scene and trying to hide it. While hiding it, I am trying to hide lines connected to it also. For that, I am taking boost-graph pointer stored at every item and iterating through it.
In boost graph, there is a flag isVisible, through setting-resetting it, I am hidding-unhidding that item.
myView.cpp
while(true)
{
// iterating thorugh boost-graph and finding co-ordinates
myRect* _rect = new myRect(rect co-ordinates);
_rect->setBrush(Qt::yellow);
_rect->setPtr(boost-graph pointer);
_scene->addItem(static_cast<QGraphicsRectItem*>(_rect));
}
void myView::HideSelectedRectangle() // after choosing hide from right mouse click, control comes here
{
foreach(QGraphicsItem* currentItem, _scene->selectedItems())
{
myRect* rItem = qgraphicsitem_cast<myRect*>(currentItem);
if(rItem)
{
VertexDescriptor vPtr = rItem->getBoostPtr(); // getting boost-graph ptr
// logic for making it hide
// Question is how paint() will know about this QGraphicsItem that it is hidden?
};
}
}
myRect.h
class myRect: public QGraphicsRectItem
{
public:
explicit myRect();
explicit myRect(QRectF &rectPoints,QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(rectPoints,parent){}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void setPtr(VertexDescriptor);
VertexDescriptor getPtr();
VertexDescriptor boostPtr;
}
myRect.cpp
void myRect::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);
if(selected)
{
painter->save();
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(shape());
painter->restore();
}
}
void myRect::setPtr(VertexDescriptor vIter)
{
this->boostPtr = vIter;
}
VertexDescriptor myRect::getPtr()
{
return boostPtr;
}
Now assume I have made _isVisible = false (which is in boost-graph) for rectangle and some connected lines.
And now I want to redraw view using paint(). And expecting, paint() should not draw those
rectangle and lines which are marked as not visible.
While doing this :
Is paint() redraw every QGraphicsItem from view or it will redraw only
those were updated ?
How paint() will know, which shape should it draw and its co-ordinates
?
Is it possible in my paint(), by checking QGraphicsItem's flag (isVisible) I can guide paint() which items to redraw and which not to
redraw ?
I am new to the Qt and I am confused how to use the QGraphicsScene. If I add for example 10 ellipses to the scene and animate them along the path, how would I do it? It gets even more complicated, because if I have 10 ellipses drawn along the ellipse and I want to animate those ellipses so they move away from the cente of the ellipse they are on. In the picture you can see the ellipses. Those are drawn with QPainter I haven't figured out how to add them to scene, but I would like the grey ellipses to move in between the inner and outter circle. I have gone through some examples, but can't really fit any of those to my situation.
Code used to draw ellipses with QPainter:
QPainter drElps;
drElps.setBrush(QBrush(QColor(0xaf, 0xaf, 0xaa)));
drElps.setPen(QPen(QColor(0xaf, 0xaf, 0xaa), 2));
double nrElps = 30;
int degree = (int)(360./nrElps);
painter.translate(QPointF(big_elps_circle_center));
while(--nrElps){
painter.drawEllipse(0, -60, 3, 3);
painter.rotate(degree);
}
painter.translate(QPOintF(back_to_origin));
Qt has a set of classes oriented to the animations, for this you must first create an object that inherits from QGraphicsObject, in this class you must implement the methods paint and boundingRect.
ellipseobject.h
#ifndef ELLIPSEOBJECT_H
#define ELLIPSEOBJECT_H
#include <QGraphicsObject>
class EllipseObject : public QGraphicsObject
{
Q_OBJECT
public:
EllipseObject(QGraphicsItem * parent = 0);
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
QRectF boundingRect() const;
};
#endif // ELLIPSEOBJECT_H
ellipseobject.cpp
#include "ellipseobject.h"
#include <QPainter>
EllipseObject::EllipseObject(QGraphicsItem *parent):QGraphicsObject(parent)
{
}
void EllipseObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(QColor(0xaf, 0xaf, 0xaa));
painter->setBrush(QBrush(QColor(0xaf, 0xaf, 0xaa)));
QRectF rect = boundingRect();
painter->drawEllipse(rect);
}
QRectF EllipseObject::boundingRect() const
{
return QRectF(-4, -4, 8, 8);
}
Then we can use the QPropertyAnimation class, with this we will animate the position.
EllipseObject *item = new EllipseObject;
QPropertyAnimation *animation = new QPropertyAnimation(item, "pos");
for(int j = 0; j < p; j++){
animation->setKeyValueAt( 1.0*j/(p-1),
(r+ delta*sin(2*M_PI*j/p) )*QPointF(qSin(2*M_PI*i/number_of_items), qCos(2*M_PI*i/number_of_items)));
}
animation->setDuration(2000);
Since we have several elements that are animated in parallel, we can use QParallelAnimationGroup to handle each of the animations.
group = new QParallelAnimationGroup(this);
[...]
group->addAnimation(animation);
If we want to be continuous we can make the following code.
group->start();
connect(group, &QParallelAnimationGroup::finished,[=](){
group->start();
});
The complete code is here.
If you draw them with QPainter then you are on your on. The point of QGraphivsScene is to inherit from QGraphicsItem, paint what you need to paint on the paintEvent, add them to the scene.
you are drawint more than 1 item on your paintEvent - so you are on your own - and QGraphivsScene cannot help you there.
Now, how to do that Correctly:
Inherit from QGraphicsScene or QGraphivsView
Inherit from QGraphicsEllipseItem and create a MovableEllipseItem
in the MovableEllipseItem constructor, create a QTimer, connect it's timeout() signal with a move slot that you will define
on your move slot, do the calculations on where the ellipse should be and move(x,y) it.
You can use a custom Graphics Item, the QGraphicsScene::advance mechanism, together with a QTimeLine to control the animation.
The outline is like this:
QGraphicsScene *sc = new QGraphicsScene;
QTimeLine *tl = new QTimeLine(1000);
tl->setFrameRange(-20, 20);
connect(tl, &QTimeLine::frameChanged, sc, &QGraphicsScene::advance);
connect(tl, &QTimeLine::finished, tl, &QTimeLine::toggleDirection);
connect(tl, &QTimeLine::finished, tl, &QTimeLine::start);
for (int i = 0; i < 30; ++i) {
sc->addItem(new AnimItem(360./30*i, tl));
}
In the custom AnimItem, you implement the drawing/animation logic. A good base would be QGraphicsEllipseItem. For example:
AnimItem::AnimItem(qreal angle, QTimeLine *timer, QGraphicsItem *parent)
: QGraphicsEllipseItem(0, 0, 3, 3, parent),
m_timer(timer)
{
QTransform t;
t.rotate(angle);
t.translate(0, -120);
setTransform(t);
setBrush(QBrush(QColor(0xaf, 0xaf, 0xaa)));
}
void AnimItem::advance(int phase)
{
if (phase == 1) {
QTransform t = transform();
t.translate(0, m_timer->currentFrame()/5);
setTransform(t);
}
}
I'm trying to create a BuildingTile class which has QGraphicsRectItem as base.
In this BuildingTile I'm trying to add QGraphicsEllipseItems and a QGraphicsSimpleTextItem but these do not use my BuildingTile's coordinate system although they say on http://doc.qt.io/qt-5/graphicsview.html: "Child coordinates are relative to the parent's coordinates. If the child is untransformed, the difference between a child coordinate and a parent coordinate is the same as the distance between the items in parent coordinates."
I would be really glad if someone could help me with this.
The header:
class BuildingTile : public QGraphicsRectItem
{
private:
Building* m_building;
bool m_empty;
QGraphicsSimpleTextItem* m_name;
QList<QGraphicsEllipseItem*> m_colonists;
public:
BuildingTile(qreal x, qreal y, QColor color, QString name, Building* m_building = 0);
bool isEmpty() const {return m_empty;}
void setEmpty(bool empty) {m_empty = empty;}
void setBuilding(Building* building) {m_building = building;}
};
The constructor:
BuildingTile::BuildingTile(qreal x, qreal y, QColor color, QString name, Building *building) : QGraphicsRectItem(x,y,150,75)
{
m_building = building;
setBrush(color);
for(int i = 0; i<3; i++)
{
QGraphicsEllipseItem* item = new QGraphicsEllipseItem(10+i*35, 40, 25, 25, this);
m_colonists.append(item);
item->setBrush(QColor(255,255,255));
}
m_name = new QGraphicsSimpleTextItem(name, this);
m_name->setPos(10,10);
}
MainWindow constructor:
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
{
QGraphicsScene* scene = new QGraphicsScene;
BuildingTile* item = new BuildingTile(0, 0, QColor(203,130,232), "small market");
scene->addItem(item);
item = new BuildingTile(150, 0, QColor(91,161,212), "indigo plant");
scene->addItem(item);
item = new BuildingTile(300, 0, QColor(120,113,107), "coffee roaster");
scene->addItem(item);
QGraphicsView* view = new QGraphicsView;
view->setScene(scene);
view->setAlignment(Qt::AlignTop | Qt::AlignLeft);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(view);
setLayout(layout);
}
All your BuildingTile items have their origin at the scene's origin, i.e. (0, 0) in scene coordinates.
For example (your second BuildingTile item):
item = new BuildingTile(150, 0, QColor(91,161,212), "indigo plant");
scene->addItem(item);
This creates a BuildingTile item located at (0, 0), containing a rectangle located at (150,0) of its own coordinate system. You're changing the position of the rectangle in its own coordinate system, but not the position of the rect's coordinate system in relation to its parent (the scene).
Now you create ellipses and labels in relation to the BuildingTile coordinate systems, which are all identical and located at (0,0) in "global" scene coordinates, so you end up with scene coordinates (10, 10) for all labels.
To achieve what you want, do:
item = new BuildingTile(0, 0, QColor(91,161,212), "indigo plant");
scene->addItem(item);
item->setPos(150, 0);