QGraphicsItem paints with offset (problem)
-
Hi all,
I want to create a QGraphicsView which would serve as a container for layers instead of items, each layer filling the entire scene. So I subclassed QGraphicsView and QGraphicsItem, overriden the paint event in QGraphicsItem, and resizeEvent in QGraphicsView.
Then I configured the whole thing like this:- Scrolling in QGraphicsView is disabled
- In the QGraphicsView::resizeEvent, I iterate over the QGraphicsItems, setting their bounding box to QGraphicsView::contentsRect().size() (to get rid of the QFrame borders) - the QGraphicsItem::boundingRect() simply returns QRectF(QPointF(), mContentsRectSize);
Then I redraw the scene with invalidateScene(); and viewport()->update();
Note that I don't call base resizeEvent() - The paint event in QGraphicsItem takes the bounding box whose size is equal to QGraphicsView::contentsRect().size() and draws from offset [0;0], for example: painter->fillRect(QRect(0, 0, 20, 20), Qt::red);
Everything works fine except that there is a 1px offset from the left as well as from the top. You can see it in this image:
http://pasteboard.co/vE1gqZB.png
Content that extends outside of the bounding box is visible, so it means the whole QGraphicsItem is offset by 1px.Calling QGraphicsItem::setPos(0,0); or [-1; -1] doesn't work. The only thing that "moves" the item is setting the position to [-0.5f; -0.5f] which kind of stretches into the empty offset but only with antialiasing turned on and it only "blurs" the area, so I don't consider it a solution.
I need to understand what is going on behind the scenes in QGraphicsView and how to fix this problem.
Thanks. -
Hi,
that's because your rectangle needs to be QRect(-penWidth/2, -penWidth/2, 20-penWidth, 20-penWidth) otherwise half of the border is drawn outside the intended area. -
ops, sorry, the minus signs are wrong. the rest is correct.
-
Hi,
that's because your rectangle needs to be QRect(-penWidth/2, -penWidth/2, 20-penWidth, 20-penWidth) otherwise half of the border is drawn outside the intended area.@Wieland Hi, thanks for the reply, but it doesn't answer my question, also lets asume there is no antialising, working with integers only. My rectangle is defined as QRect(0, 0, 20, 20).
I am filling the rectangle, not drawing borders. Take for example this code:void Layer::paint(QPainter * painter, const QStyleOptionGraphicsItem *, QWidget *) { // Size of the rectangle is taken from the parent QGraphicsView QRect content = QRect(QPoint(), getParentView()->contentsRect().size()); // Simply fill the whole rectangle with color painter->fillRect(content, Qt::red); }
Now one would assume that this will fill the content of the QGraphicsView with red. It does not, in my case, there is a 1px offset from the left and the top. To fill the whole view I have to do this:
QRect content = QRect(QPoint(-1, -1), getParentView()->contentsRect().size());
note the [-1;-1] offset -
@matejtomcik I can't reproduce it. Here is my code:
#ifndef MYITEM_H #define MYITEM_H #include <QObject> #include <QGraphicsItem> #include <QPainter> #include <QWidget> #include <QRect> class MyItem : public QGraphicsItem { public: QRect parViewContRect; QRectF boundingRect() const { return parViewContRect; } void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { QPen pen(Qt::NoPen); painter->setPen(pen); painter->fillRect( QRect(QPoint(), QSize( parViewContRect.width(), parViewContRect.height() ) ) , Qt::red); } }; #endif // MYITEM_H
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QGraphicsScene *scene = new QGraphicsScene; ui->graphicsView->setScene(scene); ui->graphicsView->setAlignment( Qt::AlignLeft | Qt::AlignTop ); // ui->graphicsView->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true); MyItem * item = new MyItem; scene->addItem(item); item->parViewContRect = ui->graphicsView->contentsRect(); }
And this is what it looks like (I changed the window color to black and disabled the GraphicViews frame. The background brush of the view is white and solid): https://drive.google.com/file/d/0B2D1UtsPfTx-YkRDRThnSXRyYzA/view?usp=sharing
-
@matejtomcik I can't reproduce it. Here is my code:
#ifndef MYITEM_H #define MYITEM_H #include <QObject> #include <QGraphicsItem> #include <QPainter> #include <QWidget> #include <QRect> class MyItem : public QGraphicsItem { public: QRect parViewContRect; QRectF boundingRect() const { return parViewContRect; } void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { QPen pen(Qt::NoPen); painter->setPen(pen); painter->fillRect( QRect(QPoint(), QSize( parViewContRect.width(), parViewContRect.height() ) ) , Qt::red); } }; #endif // MYITEM_H
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QGraphicsScene *scene = new QGraphicsScene; ui->graphicsView->setScene(scene); ui->graphicsView->setAlignment( Qt::AlignLeft | Qt::AlignTop ); // ui->graphicsView->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true); MyItem * item = new MyItem; scene->addItem(item); item->parViewContRect = ui->graphicsView->contentsRect(); }
And this is what it looks like (I changed the window color to black and disabled the GraphicViews frame. The background brush of the view is white and solid): https://drive.google.com/file/d/0B2D1UtsPfTx-YkRDRThnSXRyYzA/view?usp=sharing
@Wieland Hi, thanks for the reply, if you subclass QGraphicsView like this:
#ifndef MYVIEW_H #define MYVIEW_H #include <QGraphicsView> #include "MyItem.h" class MyView : public QGraphicsView { Q_OBJECT public: inline explicit MyView(QWidget * parent = 0) : QGraphicsView(parent) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } public: inline virtual void resizeEvent(QResizeEvent *) { QSize size = contentsRect().size(); static_cast<MyItem *>(scene()->items().at(0))->parViewContRect = QRect(QPoint(), size); invalidateScene(); viewport()->update(); } }; #endif // MYVIEW_H
Here is the result: http://pasteboard.co/wwmZHz6.png
There is a 1px offset but on the bottom-right side.
If you comment out this line:
ui->graphicsView->setAlignment( Qt::AlignLeft | Qt::AlignTop );
Then the item is offset by (in my case) 92x97 px.
But then if I add this to the resizeEvent method:
scene()->items().at(0)->setPos(-1, -1);
the item will fill the whole view.However it doesn't solve my problem. I added this to my original code and it didn't fix the problem. But I have noticed that if I disable cache on all of my QGraphicsItems, then it works. I use
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
on QGraphicsItem by default, then I change the bounding box like this:// Parameter canvasSize is equal to QGraphicsView::contentsRect().size() void Layer::updateViewport(const QSize & canvasSize) { if (cacheMode() != QGraphicsItem::NoCache) prepareGeometryChange(); mBoundingBox = QRect(QPoint(), canvasSize); if (cacheMode() != QGraphicsItem::NoCache) update(); }
Here is the result: http://pasteboard.co/wzteGW3.png
This happens when the cache is set to QGraphicsItem::DeviceCoordinateCache (or QGraphicsItem::ItemCoordinateCache) and I quickly resize the window. Here is a Qt project which can reproduce the error: http://matejtomcik.com/ViewProblem.zip