mousePressEvent() behaving strangely. QGraphicsItem, QGraphicsView, QGraphicsScene being used
-
I have a custom QGraphicsITem (BoardSquare) and a custom QGraphicsScene (BoardScene) that are added a base-class QGraphicsView.
BoardScene generates 64 BoardSquares, places them in a QVector, and adds them to the scene.
BoardScene constructor sets the scene as such:
this->setSceneRect(0, 0, 850, 850)
BoardSquare:
QRectF BoardSquare::boundingRect() const { return QRectF(0, 0, 100, 100) }
BoardSquare::paint(...) { ... painter->drawRect(x_offset, y_offset, 100, 100) }
x_offset and y_offset are generated at square construction inside of a BoardScene member function.
The resulting output is an 8x8 board centered within the SceneRect, offset 25 pixels from all sides. Dimensions = 800 x 800 pixels.void BoardSquare::mousePressEvent(QGraphicsSceneMouseEvent *event) { qDebug()<< "click"; }
The only time this functions is when I am in the the very top left most corner of the scene, in a box approx. 100 x 100 pixels.
What I think is happening is this:
The BoardScene is generating each square, and first placing them at (0,0) of the scene, or uppermost left corner.
The BoardScene is correctly displaying the desired locations of the squares.
The BoradScene is retaining the original (0,0) placement and holding that as the coordinates of each and every square generated.
So for the purposes of mouse events, all 64 squares are physically in that uppermost left corner, while they are displayed by the view as if they aren't.Any ideas what's causing this, or how to fix it?
-
Wait I think I may have seen the problem. Does the boundingRect() for each square to change as I generate them? My understanding was that the boundingRect just determined the size of the item, but maybe I was mistaken?
-
@SlappyDanger
boundingRect()
is x, y, width, height, not just size.
You don't normally need to define it.
NoteThis pure virtual function defines the outer bounds of the item as a rectangle; all painting must be restricted to inside an item's bounding rect.
BoardSquare::paint(...) { ... painter->drawRect(x_offset, y_offset, 100, 100) }
If
BoardSquare
is aQGraphicsItem
I don't think you should be using x, y outside its location.You are supposed to put your
BoardSquare
s at the right place in the scene by moving them there (setPos()
). I think you are not doing that but instead painting them where you want them? -
Instead of doing
painter->drawRect(x_offset, y_offset, 100, 100)
, draw the rect with no offsets, and instead set the position
of your BoardSquare item to (x_offset, y_offset). -
You are correct. I never call setPos at all.
My initial idea (I can't try anything right now) is to put set the initial position as I generate them in the BoardScene.
So it would look like
Void generateSquares() { ... x = some number y = some number BoardSquare *square = new BoardSquare() square->setPos(x, y) vector.pushback(square) .... addSquares(vector) }
I apologize if that's hard to read, I'm typing on my phone.
So that code would generate the x and y I need, use that for the corresponding square POSITION but not in the actual construction, and then add it to the scene.
My other idea would be do it inside the BoardSquare constructor. Just curious if there is a difference.
Thanks so much for your help!
@mchinand thank you as well!
-
@SlappyDanger
Yes, that's fine. Or pass to constructor.There is nothing wrong with your approach of having board squares as
QGraphicsItem
s on the scene. However, in my experience, when you are ready/in the long-run, you would be better drawing them all indrawBackground()
in a subclassedQGraphicsScene
, then the only items on the scene will be the pieces. -
I was looking into that, I think I'll just make that happen now.
In order to make that happen, I do that in the scene constructor by overriding QGraphicsScene::draw background(), correct?
I'll play around with it a little. Youve been a huge help!
-
@SlappyDanger said in mousePressEvent() behaving strangely. QGraphicsItem, QGraphicsView, QGraphicsScene being used:
I do that in the scene constructor by overriding QGraphicsScene::draw background(), correct
You derive
class ChessBoard : public QGraphicsScene
. You do not do anything about drawing in the constructor. You write aprotected: virtual void ChessBoard::drawBackground() override
. You draw the whole of the board, with its 8x8 squares, there via thepainter
.You do not call this. The Qt gfx framework will call it whenever the background needs drawing. Once you have it right you forget about it.
You then just concentrate on adding your
GraphicsItem
-derived pieces to the scene, moving their coordinates around (setPos()
) to desired squares.You will find, for example, that when you move a piece from one square to another the framework realises that where it moved from has now been "uncovered" and it will call
drawBackground()
for that area to cause the background to be redrawn.It will pass the
const QRectF &rect
todrawBackground()
for the area it wants redrawn. That would be the whole board initially, but afterwards just the area which needs redrawing. If you care you can be "efficient" about this, by "clipping" to that rectangle.A sample implementation of mine which is fairly typical reads:
/*virtual*/ void MapWindowGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect) /*override*/ { // call the base method QGraphicsScene::drawBackground(painter, rect); // draw that part of the pixmap which lies in `rect` painter->setClipRect(rect); painter->drawPixmap(0, 0, mapWindow()->pixmapMap()); }
Mine is a
QPixmap
for the background. Yours will be a loop drawing the squares at the right coordinates. If you want to be clever/optimal about it, you can calculate fromrect
's coordinates just which square(s) it intersects and make your loop only need to draw those. But so long as you have the
painter->setClipRect(rect)
it will only actually draw into thatrect
anyway, regardless of where your code here draws all over the board. -
@JonB
Thank you so much. I have a couple questions, was wondering if you could answer them, or better yet, point me to the locations in the documentation that would give me the answers I seek (if at all possible/easier).One thing I'd like to know, is how the painter is using the information passed with calls to setPos(), if at all. I THINK I know -
That setPos() gets passed to the QGraphicsView. The painter is painting in the QGraphicsScene at the coordinates given for the boundingRect, and the view is displaying them in the correct spot, and subsequently handling events, at the coordinates in setPos() for each item? And the scene maintains and updates the coordinates for each item, image, etc through the use of setPos()?
I'm sorry if that's confusing or doesn't make sense, I can try to clarify later if need be.
Second, the way that I'm trying to implement my QGraphicsScene::drawBackground() is -
EDIT : I was able to pass the board, I was just making yet another syntax mistake. However, I still have a question: If I direct the scene to paint the board in the background, will the scene just automatically recognize which items that have been added to it are painted in the background and therefore skip over them when drawing the other layer(s)?
Am I moving the paint() method from BoardSquare into BoardScene::drawBackground()?
I have 64 BoardSquares, which are custom QGraphicsItems with overriden paint() methods.
What information do I have to pass into drawBackground()?Am I supposed to be generating the squares inside the drawBackground() method?
This is all so confusing lol, i'm so sorry.
I'm fairly certain one solution is to just scrap the BoardSquares altogether and just draw a bunch of rectangles in the drawBackground() method, but I want to have flexibility in doing different things with the squares later on, like selecting a piece and having a valid move square change color, for example.
Generating the squares as before, and storing them in a private QVector<BoardSquare*>, m_board. But I am unable to pass m_board, in any way, to the overridden method (or function, I always get the names mixed up).
Is that possible?I just left the house, but my next idea to try is by using the QGraphicsScene::getItems() (if I have the wrong, I know there is an equivalent that returns a QList of all items currently in the scene). I would then add the BoardSquares to the scene first, and if my assumption is correct that items, stored in a QList generated by QGraphicsScene to get all the current items, are in chronological order of when they were set to the scene, I can potentially use that QList's first 64 items for the background function?I'm just not sure how to distinguish between which items to paint in the background and which ones not too, because I am never calling the drawBackground method itself. -
@SlappyDanger
Like I said earlier. For now you can not implement anydrawBground()
. Stick to doing it as you were, learning to useQGraphicsItem
s for the board squares, and do your animation on them.