QGraphicsItem resize
-
Hello,
I'm trying to make a QGraphicsItem where I can change the size of the object, but I have some problems I cannot solve my self. I found an implementation, but there the resize rectangle will not hide when the parent is not selected.
Is there better to make 4 Resizerectangle Items for every corner or better to make on GraphicsItem where I paint the selection frame and the resizehandles?
Whats the different between moving the item or moving the bounding rect of the item?Whats the different, when I handle the object in the mouse move event or in the item change event?
Best regards,
Martin
-
-
They wrote there that they use mouse move events not the change item event, but for me seems that these events are equal?
I already found this thread, but there the sizegrips are always visible. For that I read that when I hide the parent then the child will hidden too. I will try this later.
-
Just as a note:
There is also
http://www.davidwdrell.net/wordpress/?page_id=46
But i think the version @SGaist links to is better made. :) -
At the moment I use itemChange to move the parent. It works fine when the item is on scene coords (0,0). But when I move and release and try to move a second time. the item and his parent jumps back to (0,0). And begins than to move. How I can solve this to move item and his parent?
QVariant ResizeItem::itemChange(GraphicsItemChange change,const QVariant &value){ if (change == ItemPositionChange) { QPointF newPos = value.toPointF(); qreal xV = round(newPos.x()/gridsize)*gridsize; qreal yV = round(newPos.y()/gridsize)*gridsize; parentItem()->setPos(mapToParent(QPointF(xV,yV))); return QGraphicsItem::itemChange(change, pos()); // don't change position of item just position of parent }else if(change == ItemSelectedChange ){ // !isSelected() because seletion is after this event return QGraphicsItem::itemChange(change, value); }else{ return QGraphicsItem::itemChange(change, value); } }
-
@Wuzi
As far as I remember the link mrjj provided also shows the similar side effect. And here is how solved it:
When you reimplement mouseReleaseEvent on your ResizeItem class you may forgot to include QGraphicsItem::mouseReleaseEvent(event);
So it must be look like thisvoid ResizeItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { if(...){ ... } QGraphicsItem::mouseReleaseEvent(event); }
-
@samdol said in QGraphicsItem resize:
When you reimplement mouseReleaseEvent on your ResizeItem class you may forgot to include QGraphicsItem::mouseReleaseEvent(event);
But I didn't implement a mouseReleaseEvent, I just use itemChange event, because there I get the old and new position.
I debugged my project and found out, that the ItemPosition Change will challed two times, the first time sets every coords to zero, and the second time it moves the item. I thought, that when I use parentItem()->setPos there shouldn't be an item change on the child? Could this be the problem? -
I checked out the coords of the items:
QVariant ResizeItem::itemChange(GraphicsItemChange change,const QVariant &value){ if (change == ItemPositionHasChanged) { QPointF newPos = value.toPointF(); // (0,-100) QPointF point = scenePos(); // (0,-100) QPointF temp5 = parentItem()->scenePos(); // (0,0) QPointF temp3 = mapToParent(newPos); //(0,-200) QPointF point4 = mapToScene(pos()); // (0,-200) QPointF temp = pos(); // (0,-100) QPointF temp2 = parentItem()->pos(); // (0,0) QRectF temp213 = parentItem()->boundingRect(); // (0,0,300,300) QRectF temp532 = boundingRect(); // (0,0,301,301) parentItem()->setPos(mapToParent(newPos)); QPointF tem6 = parentItem()->pos(); // (0,-200) return pos(); //QPointF(xV,yV);//QGraphicsItem::itemChange(change, pos()); // don't change position of item just position of parent }else if(change == ItemPositionChange){ QPointF newPos = value.toPointF(); qreal xV = round(newPos.x()/gridsize)*gridsize; qreal yV = round(newPos.y()/gridsize)*gridsize; return QPointF(xV,yV);
Why temp3 is not the same than newPos?
-
When I debug the program it works fine, but when I start it normaly it moves faster than the mouse. Is it restricted to move the parentItem without moving the item itself while itemChange will called (is there a recursiv call that moves the parent item to fast)?
-
@Wuzi said in QGraphicsItem resize:
Why temp3 is not the same than newPos?
mapToParent converts coordinates from "this" item's coordinate system to the parent item's coordinate system. Compared to it's parent, "this" item has an offset of (0,-100). Therefore, any position you enter into mapToParent at this point will get that offset.
Your mistake here is that you take a position (newPos) which is already in the parent's coordinate system (because the position of an item is always given relative to it's parent), and map it - again - into the parent's coordinate system.
What you need to get point4 is parentItem()->mapToScene(newPos) - provided that parentItem() is not NULL.
-
parentItem()->setPos(parentItem()->mapToScene(xV,yV));
parentItem()->moveBy(xV,yV);I tried your hint, but this does not change something. I tried also moveBy (because xV,yV is everytime a delta value). But has the same effect.
I uploaded the project to dropbox:
https://www.dropbox.com/s/1skrl8p0b5yq5ua/Grafik.zip?dl=0 -
@Wuzi said in QGraphicsItem resize:
parentItem()->setPos(parentItem()->mapToScene(xV,yV));
parentItem()->moveBy(xV,yV);I tried your hint, but this does not change something. I tried also moveBy (because xV,yV is everytime a delta value). But has the same effect.
I uploaded the project to dropbox:
https://www.dropbox.com/s/1skrl8p0b5yq5ua/Grafik.zip?dl=0Well, I only explained why temp3 is not the same as newPos. I didn't know (from the code) what your intention was.
If we are talking about the code for a size handle (and the size handle should resize it's parent item), let's take it step by step:- Let's just consider the bottom right size handle. The one that only changes the parent item's size, but not it's position
- That size handle should be moveable (though itemFlag)
- You can restrict movement to a grid, much like you do in the "ItemPositionChange" section of code. But for a start, I would leave that part out until the resizing works properly
- Your parentItem needs some way to change it's size from the outside.
- We know that our size handle should be at the bottom-right of the parent item's boundingRect. Therefore, if the handle is moved, we can calculate how the parent's boundingRect must change to reflect the change in size
That should work with something like this (just off the top of my head)
// In the size handle if (change == ItemPositionHasChanged) { QRectF parentBoundingRect = parentItem().boundingRect(); // Correct boundingRect so the bottomRight matches the new handle position // Both the parent's boundingRect and pos() are in the parentItem's coordinate system, so no need to map anything parentBoundingRect.setBottomRight(pos()); //MISSING: Here you need some way to apply the new boundingRect to your parent // Note: As long as neither the topLeft of the boundingRect, nor the position of the parent changes, we should not need to adjust the handle position in any way }
And regarding your question "what's the difference between your item's position and the topLeft of your boundingRect?"
In theory, you can always choose whether to move the item around using setPos, or whether to translate the boundingRect and leave the position always on 0/0. To add confusion, you can also use transformations to move an item around.In practice, I find it useful to design the boundingRect in such a way that its 0/0 position is some kind of anchor or "special point" of the object. For example, if I draw a circle, it might make sense to have the origin of the boundingRect in the center. In the circle's item coordinate, the boundingRect would have a negative topLeft, e.g. (-5, -5) and a matching size (10/10). That way, when you apply a scaling transformation to the circle, it will grow or shrink without moving around, because the origin of the boundingRect is (by default) the transformation origin.
In summary, having a "sensible" origin for items, and then moving them around using setPos is usually much easier. -
Thank you Asperamanca for your explanation, I will need it later, but at the moment I would like just to move the parent item. My plan is: when I move the resizeItem, I catch the new position, change the position of the parent ( so resizeitem changes position absolute to, but not relative to parent and no new itemChange event will called), but the position of the item should not change relative to the parent (so I return pos(), the value before I moved the item). It works fine, that the resizeitem stays in the parent item, but if I move the resizeitem to fast, the parent with the resizeitem moves faster than the mouse. If I debug every position change it works fine, but I don't understand why it moves faster than the mouse?
-
The "jumping ahead" effect usually occurs because the child item you move around is affected by the repositioning of the parentItem after all, or because coordinate systems are mixed up in some place.
However, in your case I suspect an incorrect assumption: While in "ItemPositionHasChanged", pos() is already the new value. The return value of itemChange is ignored in this case. From the docs:
"The value argument is the new position (the same as pos()), and QGraphicsItem ignores the return value for this notification (i.e., a read-only notification)."You might get something like this to work in the "ItemPositionChange", however I do not know whether it's good to change an item's parent's position while in the "ItemPositionChange" itself.
There is a totally different approach, though it is somewhat more work (especially if you want to get it watertight):
- Overload mousePress, mouseMove and mouseRelease events (you can do the same with sceneEvent, by the way)
- Recognize when a drag start and ends. The simplest form: press with left button, release with left button
- When a drag starts, grab the mouse and store the drag (mouse) start position in scene coordinates
- When a mouseMove event arrives while a drag is running, compare the current mouse position with the drag start position
- Use that delta to reposition your parent. Since we are working in scene coordinates, moving the parent around won't affect our coordinate system. When we move the parent towards the drag position, the next drag delta will be that much smaller. This works very well with snap-to-grid, too.
- Ungab the mouse when the drag ends
-
Thank you Asperamanca, your hint with using mouse move instead of ItemChange works fine:
void ResizeItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event){ if( event->buttons() & Qt::LeftButton){ // If Leftbutton pressed QPointF delta = parentItem()->mapToParent(event->pos())-parentItem()->pos()-mouse_pos_parent_coords; parentItem()->moveBy(delta.x(), delta.y()); return; } QGraphicsItem::mouseMoveEvent(event); } void ResizeItem::mousePressEvent(QGraphicsSceneMouseEvent *event){ mouse_pos_parent_coords = event->pos(); QGraphicsItem::mousePressEvent(event); }
If somebody know, why it does not work to use ItemChange I will happy to know it :)