I have a QGraphicsPixmap item in a QGraphicsScene. The item has flags set to ItemIsMovable, and ItemIsSelectable. How do I ensure that when the item is moved out of a certain boundary - it can be a QGraphicsScene or just a fixed frame size at fixed coordinates - the part becomes hidden?
Eg.
The left part of the basketball becomes hidden.
You have to use setClipPath().
In the following code I have created a class that inherits from QGraphicsPixmapItem (the same could do with other classes that inherit from QGraphicsItem) and I created the method setBoundaryPath() that receives a QPainterPath that indicates the visible area, for example in the code use:
QPainterPath path;
path.addRect(QRectF(100, 100, 400, 200));
That QPainterPath is a rectangle whose topleft is the point (100, 100) of the QGraphicsScene with size of 400 in width and 200 in height.
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
class GraphicsPixmapItem: public QGraphicsPixmapItem{
public:
GraphicsPixmapItem(const QPixmap & pixmap, QGraphicsItem *parent = 0):
QGraphicsPixmapItem(pixmap, parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
if(!m_boundaryPath.isEmpty()){
QPainterPath path = mapFromScene(m_boundaryPath);
if(!path.isEmpty())
painter->setClipPath(path);
}
QGraphicsPixmapItem::paint(painter, option, widget);
}
QPainterPath boundaryPath() const{
return m_boundaryPath;
}
void setBoundaryPath(const QPainterPath &boundaryPath){
m_boundaryPath = boundaryPath;
update();
}
private:
QPainterPath m_boundaryPath;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene scene(0, 0, 600, 400);
view.setScene(&scene);
view.setBackgroundBrush(QBrush(Qt::gray));
GraphicsPixmapItem *p_item = new GraphicsPixmapItem(QPixmap(":/ball.png"));
p_item->setPos(100, 100);
// Define the area that will be visible
QPainterPath path;
path.addRect(QRectF(100, 100, 400, 200));
p_item->setBoundaryPath(path);
scene.addItem(p_item);
// the item is added to visualize the intersection
QGraphicsPathItem *path_item = scene.addPath(path, QPen(Qt::black), QBrush(Qt::white));
path_item->setZValue(-1);
view.show();
return a.exec();
}
You can find the example code in this link.
Related
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)
(warning crossposted on: https://forum.qt.io/topic/105158/qgraphicsscene-item-is-drawn-at-twice-x2-position)
In the following code I am creating a custom widget which has a subclass of QGraphicsScene embeded in it. My aim is to click and add a point to the scene. A point is my subclass of QGraphicsItem (GIPoint). I want to move that point around and later connect it to other points and make a spline path.
The problem I am facing is that the point is not drawn at where I click but at a place which is formed by doubling the mouse-event's scenePos() coordinates. So if I click at (100,100) the point is drawn at (200,200). I suspect that I have misunderstood the coordinate system despite reading the documentation.
The question How to add item in a QGraphicsScene? seems relevant but the proposed solution to transform the mouse-event's coordinates via mapToScene(event->pos()); actually doubles up the position (before it will print that it draws on same position but it would then be x2. Now it also prints it as x2).
So I am asking additionally to point me to some simple-to-digest advice on how the widgets placement works. btw. is QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
correct regarding the (x,y) coordinates of the rectangle?
My example code follows:
/* use the following pro:
QT += widgets core gui
CONFIG += debug console
SOURCES = example.cpp
TARGET = example
*/
#include <QGraphicsItem>
#include <QPainter>
#include <QWidget>
#include <QRectF>
#include <QPointF>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QGraphicsView>
#include <QApplication>
#include <QOpenGLWidget>
#include <QMainWindow>
#include <QGridLayout>
#include <QDebug>
class GIPoint : public QGraphicsItem{
public:
GIPoint(QGraphicsItem * parent, const QPointF &position);
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
QRectF boundingRect() const override;
void paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
);
};
class GraphicsSceneWidget : public QGraphicsScene {
public:
explicit GraphicsSceneWidget(QObject *parent);
~GraphicsSceneWidget();
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
};
class VectorGraphicsWidget : public QWidget {
public:
VectorGraphicsWidget(QWidget *parent);
~VectorGraphicsWidget();
private:
GraphicsSceneWidget *myGraphicsSceneWidget;
};
// implementation
GIPoint::GIPoint(
QGraphicsItem *parent,
const QPointF &position
) : QGraphicsItem(parent) {
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setPos(position);
qWarning() << "GIPoint::GIPoint() : init at " << position;
}
QVariant GIPoint::itemChange(
GraphicsItemChange change,
const QVariant &value
){
if (change == QGraphicsItem::ItemPositionChange) {
qWarning("position changed");
}
return value;
}
QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
// return QRectF(0,0, 5, 5);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QPointF xx = scenePos();
QRectF rect = QRectF(xx.x(), xx.y(), 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
//painter->fillRect(rect, brush);
painter->drawRect(rect);
}
GraphicsSceneWidget::GraphicsSceneWidget(QObject *parent)
: QGraphicsScene(parent)
{}
GraphicsSceneWidget::~GraphicsSceneWidget(){}
void GraphicsSceneWidget::mousePressEvent(
QGraphicsSceneMouseEvent *event
){
GIPoint *gip = new GIPoint(Q_NULLPTR, event->scenePos());
addItem(gip);
QGraphicsScene::mousePressEvent(event);
}
VectorGraphicsWidget::VectorGraphicsWidget(QWidget *parent) :
QWidget(parent)
{
myGraphicsSceneWidget = new GraphicsSceneWidget(this);
QGraphicsView *view = new QGraphicsView(myGraphicsSceneWidget);
myGraphicsSceneWidget->setSceneRect(QRectF(0, 0, 500, 500));
QGridLayout *centralLayout = new QGridLayout;
centralLayout->addWidget(view);
setLayout(centralLayout);
myGraphicsSceneWidget->addRect(
QRectF(0, 0, 100, 100),
QPen(Qt::black),
QBrush(Qt::green)
);
view->show();
}
VectorGraphicsWidget::~VectorGraphicsWidget() {
delete myGraphicsSceneWidget;
}
int main(int argc, char **argv){
QApplication app(argc, argv);
app.setApplicationName("test");
app.setOrganizationName("myorg");
app.setOrganizationDomain("myorg.com");
QMainWindow *w = new QMainWindow();
w->resize(500, 500);
w->setCentralWidget(new VectorGraphicsWidget(Q_NULLPTR));
w->show();
return app.exec();
}
The problem is that the boundingRect() and the paint() methods is respect the coordinate system of the item, not the scene. So the solution is not to use scenePos() in both methods but 0, 0:
QRectF GIPoint::boundingRect() const {
return QRectF(0, 0, 10, 10);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QRectF rect = QRectF(0, 0, 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
painter->fillRect(rect, brush);
painter->drawRect(rect);
}
Although I would recommend using QGraphicsRectItem as it implements what you have done.
I have class named Pixmap deriving from QGraphicsPixmapItem and StartScreen class deriving from QGraphicsScene. I want to use animations (QPropertyAnimation class) to resize displayed image in certain time range. Other actions like setting position or rotation aren't problem but I couldn't find any property like size (e.g. setSize() method). How can I do that in the other way? Thanks for advance.
StartScreen::StartScreen(int windowWidth, int windowHeight)
{
setSceneRect(0, 0, windowWidth, windowHeight);
setBackgroundBrush(QBrush(QImage(":/images/background.png")));
Pixmap * logo = new Pixmap(":/images/logo.png");
addItem(logo);
logo->setPos((windowWidth - logo->pixmap().width()) / 2, (windowWidth - logo->pixmap().width()) / 2 - 75);
//QPropertyAnimation * animation = new QPropertyAnimation(logo, "");
}
QPropertyAnimation applies to Qt Properties, but only objects that inherit from QObject have Qt properties, so if you want to use animations you can use QGraphicsObject and create your own item, or create a class that inherits the item you want and QObject.
class Pixmap: public QObject, public QGraphicsPixmapItem{
Q_OBJECT
Q_PROPERTY(qreal scale READ scale WRITE setScale)
Q_PROPERTY(qreal rotation READ rotation WRITE setRotation)
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
public:
using QGraphicsPixmapItem::QGraphicsPixmapItem;
};
In the previous example, take advantage of the fact that QGraphicsItem, and therefore its derived classes, have the methods pos(), setPos(), scale(), setScale(), rotation() and setRotation(), so only use them in Q_PROPERTY.
In the next part I show an example:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView w;
QGraphicsScene scene(0, 0, 640, 480);
w.setScene(&scene);
w.show();
Pixmap* logo = new Pixmap(QPixmap(":/image.jpg"));
scene.addItem(logo);
QSequentialAnimationGroup group;
QPropertyAnimation animation_scale(logo, "scale");
animation_scale.setDuration(1000);
animation_scale.setStartValue(2.0);
animation_scale.setEndValue(0.1);
QPropertyAnimation animation_pos(logo, "pos");
animation_pos.setDuration(1000);
animation_pos.setStartValue(QPointF(0, 0));
animation_pos.setEndValue(QPointF(100, 100));
/**
* it must indicate the center of rotation,
* in this case it will be the center of the item
*/
logo->setTransformOriginPoint(logo->boundingRect().center());
QPropertyAnimation animation_rotate(logo, "rotation");
animation_rotate.setDuration(1000);
animation_rotate.setStartValue(0);
animation_rotate.setEndValue(360);
group.addAnimation(&animation_scale);
group.addAnimation(&animation_pos);
group.addAnimation(&animation_rotate);
group.start();
return a.exec();
}
#include "main.moc"
In the following link there is an example
Or you can use QVariantAnimation instead of QPropertyAnimation:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView w;
QGraphicsScene scene(0, 0, 640, 480);
w.setScene(&scene);
w.show();
QGraphicsPixmapItem* logo = new QGraphicsPixmapItem(QPixmap(":/image.jpg"));
scene.addItem(logo);
QSequentialAnimationGroup group;
QVariantAnimation animation_scale;
animation_scale.setDuration(1000);
animation_scale.setStartValue(2.0);
animation_scale.setEndValue(0.5);
QObject::connect(&animation_scale, &QVariantAnimation::valueChanged, [logo](const QVariant &value){
logo->setScale(value.toReal());
});
animation_scale.start();
QVariantAnimation animation_pos;
animation_pos.setDuration(1000);
animation_pos.setStartValue(QPointF(0, 0));
animation_pos.setEndValue(QPointF(100, 100));
QObject::connect(&animation_pos, &QVariantAnimation::valueChanged, [logo](const QVariant &value){
logo->setPos(value.toPointF());
});
/**
* it must indicate the center of rotation,
* in this case it will be the center of the item
*/
logo->setTransformOriginPoint(logo->boundingRect().center());
QVariantAnimation animation_rotate;
animation_rotate.setDuration(1000);
animation_rotate.setStartValue(0);
animation_rotate.setEndValue(360);
QObject::connect(&animation_rotate, &QVariantAnimation::valueChanged, [logo](const QVariant &value){
logo->setRotation(value.toReal());
});
group.addAnimation(&animation_scale);
group.addAnimation(&animation_pos);
group.addAnimation(&animation_rotate);
group.start();
return a.exec();
}
In the following link there is an example
I have a QGraphicsItemGroup aggregating several child items, and I want to show only part of the group.(not the numbers of child items, area). Just like the image here.
I want to show the display area.
To do that, I have tried the override the QGraphicsItemGroup::boundingRect(). However, nothing have happened. And i find this in QT docs, maybe this is the reason why doesn't work.
The boundingRect() function of QGraphicsItemGroup returns the bounding rectangle of all items in the item group.
Also, I know I can change the size of QGraphicsView to make it work. However I put the View as CentralWidget, as I also need to display other object in the View, I can not change the size of the View.
How can I set the display range of a QGraphicItemGroup?
To perform this task we can overwrite shape() by returning a QPainterPath that defines the visible region, so that it spreads to its children we enable the flag ItemClipsChildrenToShape:
class GraphicsItemGroup: public QGraphicsItemGroup{
public:
GraphicsItemGroup(QGraphicsItem * parent = 0):QGraphicsItemGroup(parent){
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
}
QPainterPath shape() const
{
if(mShape.isEmpty())
return QGraphicsItemGroup::shape();
return mShape;
}
void setShape(const QPainterPath &shape){
mShape = shape;
update();
}
private:
QPainterPath mShape;
};
Example:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setLayout(new QVBoxLayout);
QGraphicsView view;
QPushButton button("click me");
w.layout()->addWidget(&view);
w.layout()->addWidget(&button);
view.setScene(new QGraphicsScene);
GraphicsItemGroup group;
view.scene()->addItem(&group);
auto ellipse = new QGraphicsEllipseItem(QRectF(0, 0, 100, 100));
ellipse->setBrush(Qt::red);
auto rect = new QGraphicsRectItem(QRect(150, 150, 100, 100));
rect->setBrush(Qt::blue);
group.addToGroup(ellipse);
group.addToGroup(rect);
QObject::connect(&button, &QPushButton::clicked, [&group](){
QPainterPath shape;
if(group.shape().boundingRect() == group.boundingRect()){
shape.addRect(0, 50, 250, 150);
}
group.setShape(shape);
});
w.show();
return a.exec();
}
Output:
The complete example can be found in the following link.
I have QGraphicsView, QGraphicsScene and QGraphicsRectItem.
QGraphicsRectItem in the QGraphicsScene and the last one in the QGraphicsView. I want to move QGraphicsRectItem with mouse by clicking on it only! But in my implementation it moves if I click on any position on my QGraphicsScene. Whether it is my QGraphicsRectItem or some other place. And the second issue. The item has been moved to the center of the scene. Clicking on it again it starts to move from the home location.
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset); //p1 movable item
}
}
What do I do wrong?
UPDATE:
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
widget.h
#ifndef STEER_H
#define STEER_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QPoint>
#include <QGraphicsRectItem>
class Steer : public QGraphicsView
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint offset;
public:
explicit Steer(QGraphicsView *parent = 0);
~Steer(){}
public slots:
void mousePressEvent(QMouseEvent * click);
void mouseMoveEvent(QMouseEvent * event);
};
#endif // STEER_H
widget.cpp
#include "widget.h"
#include <QBrush>
Steer::Steer(QGraphicsView *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(760, 160, 10, 80);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset);
}
}
I'd try a different approach that is a little easier to understand:
#include <QtWidgets>
class Steer : public QGraphicsView
{
public:
Steer()
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(0, 0, 10, 80);
p1->setX(760);
p1->setY(160);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
protected:
void mousePressEvent(QMouseEvent * click)
{
if (p1->contains(p1->mapFromScene(click->localPos()))) {
lastMousePos = click->pos();
} else {
lastMousePos = QPoint(-1, -1);
}
}
void mouseMoveEvent(QMouseEvent * event)
{
if(!(event->buttons() & Qt::LeftButton)) {
return;
}
if (lastMousePos == QPoint(-1, -1)) {
return;
}
p1->setPos(p1->pos() + (event->localPos() - lastMousePos));
lastMousePos = event->pos();
}
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint lastMousePos;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
There are a few things to point out here:
Don't use setRect() to set the position of a QGraphicsRectItem. It doesn't work the way you think it might. Always use setPos() to change the position of an item.
Rename offset to something more descriptive. I chose lastMousePos. Instead of just updating it once when the mouse is pressed, also update it whenever the mouse is moved. Then, it's simply a matter of getting the difference between the two points and adding that to the position of the item.
Check if the mouse is actually over the item before reacting to move events. If the mouse isn't over the item, you need some way of knowing that, hence the QPoint(-1, -1). You may want to use a separate boolean flag for this purpose. This solves the problem that you saw, where it was possible to click anywhere in the scene to get the item to move.
Also, note the mapFromScene() call: the contains() function works in local coordinates, so we must map the mouse position which is in scene coordinates before testing if it's over the item.
The event functions are not slots, they're virtual, protected functions.
You could also consider handling these events in the items themselves. You don't need to do it from within QGraphicsView, especially if you have more than one of these items that need to be dragged with the mouse.