Crash after QGraphicsScene::removeItem() with custom item class - c++

I am populating a QGraphicsScene with instances of a custom item class (inherting QGraphicsPathItem). At some point during runtime, I try to remove an item (plus its children) from the scene by calling:
delete pItem;
This automatically calls QGraphicsScene::removeItem(), however it also leads to a crash in the class QGraphicsSceneFindItemBspTreeVisitor during the next repaint.

TL;DR: The solution is to ensure that QGraphicsItem::prepareGeometryChange() gets called before the item's removal from the scene.
The problem is that during the item removal from the scene, the scene internal index was not properly updated, resulting in the crash upon the next attempt of drawing the scene.
Since in my case, I use a custom subclass from QGraphicsPathItem, I simply put the call to QGraphicsItem::prepareGeometryChange() into its destructor since I am not manually removing the item from the scene (via QGraphicsScene::removeItem()), but instead I simply call delete pItem; which in return triggers the item's destructor as well as removeItem() later on.

I ran into the same issue using PySide2.
Disabling BSP indexing (as mentioned here) does work for me and is most likely the actual solution to the problem. But is a sub-optimal one, because the scene that I am working with can get arbitrarily large. I also tried to call prepareGeometryChange before removing the item, and while that did seem to work for a while, the error re-appeared just a few weeks later.
What worked for me (so far) is manually removing all child items before removing the item itself...
To that end, I am overwriting the QGraphicsScene::removeItem method in Python:
class GraphicsScene(QtWidgets.QGraphicsScene):
def removeItem(self, item: QtWidgets.QGraphicsItem) -> None:
for child_item in item.childItems():
super().removeItem(child_item)
super().removeItem(item)
Note that this will not quite work the same in C++ because QGraphicsScene::removeItem is not a virtual method, so you will probably have to add your own method removeItemSafely or whatever.
Disclaimer: Other methods have worked for me as well ... until they didn't. I have not seen a crash in QGraphicsSceneFindItemBspTreeVisitor::visit since introducing this workaround, but that does not mean that this is actually the solution. Use at your own risk.

I had this issue and it was a real pain to fix it. Besides the crash, I was also having "guost" items appearing on the screen.
I was changing the boundingRect size 2x inside a custom updateGeometry() method that updates the boundingbox and shape caches of the item.
I was initializing the boundig rectangle as QRectf():
boundingBox = QRectF();
... then doing some processing (and taking the opportunity to do some clean ups in unneeded objects from the scene).
And finally setting the value of the boundingRect to its new size:
boundingBox = polygon.boundingRect();
Calling prepareGeometryChange() in the beggining, alone, didn't solve the issue since I was changing it's size twice.
The solution was to remove the first attribution.

It seems the issue lasting for long time today and there are open bugs also.
But it seems to have a workaround, which I could find it useful and after hours of debugging and reading and investigations I have found it here:
https://forum.qt.io/topic/71316/qgraphicsscenefinditembsptreevisitor-visit-crashes-due-to-an-obsolete-paintevent-after-qgraphicsscene-removeitem/17
Some other tips and tricks regarding Graphics Scene here:
https://tech-artists.org/t/qt-properly-removing-qgraphicitems/3063/6

Related

QGraphicsItem is not visible after adding to scene

I'm working on a diagram visualisation tool and I ran into an issue where my QGraphicsScene does not display a shared_ptr<DiagramItem> when a raw pointer obtained via .get() is passed to scene->addItem().
Subsequent check via scene->items() shows that my DiagramItem is not a part of the scene. My guess is that it got freed as the refcounter on the shared_ptr will be zero after leaving the scope of the testing function...
But that was the testing case. In my actual code I'm using a shared_ptr that I got from elsewhere and is definitely present in memory with a non-zero refcounter. I get the raw pointer of that and pass it to scene->addItem(). It is also not displayed, but this time it is present in scene->items(). So why is it not being drawn?
If I switch from using shared_ptr<DiagramItem> to DiagramItem* then the issue disappears and everything is displayed properly. But due to limitations from the rest of the project, I cannot easily abandon smart pointers here, nor do I want to.
Did I run into some kind of memory limitation or am I doing something wrong?
I already tried calling show() and update() on the item and increasing the scene size in case the item doesn't fit (it does). I also tried breakpointing the paint() method, but that one doesn't get called at all.
I found a possibly related question here where similar behaviour occurs due to the object going out of scope and being deallocated, but that doesn't seem to be the case with my actual DiagramItem.
class DiagramItem : public QGraphicsItem
{
...
}
//Create scene
auto scene = new QGraphicsScene(nullptr);
//Item is created OR obtained from elsewhere
auto item1 = std::make_shared<DiagramItem>(nullptr, QString("aaa"), true);
auto item2 = GetDiagramItem(...);
//Raw pointers get passed to addItem
scene->addItem(item1.get());
scene->addItem(item2.get());
//Item1 is not present at all (directly created DiagramItem)
//Item2 is present but invisible (DiagramItem passed from elsewhere)
//myItem gets Item2
auto myItem = scene->items()[0];
...

QT - How can I make this array global for my GUI?

QSpinBox* spinright[size] = {ui->norm_spinBox_2,
ui->norm_spinBox_3,
ui->norm_spinBox_4,
ui->norm_spinBox_5,
ui->norm_spinBox_6,
ui->norm_spinBox_7,
ui->norm_spinBox_8};
I'd like to be able to access this array in two spots in my program. However, if there is a better alternative for this solution I’m all ears. I tried to create a function that could be called for this program; however it started to get long and dragged out that it was becoming less worth it to go this route.
This is how I've set up the Hierarchy. My overall attempt is to make some buttons appear and disappear when a button is pressed. If it's possible to make the vertical layouts disappear then this would be a better way to go.
In the end I'll take whatever solution, that may be offered here.
Thank you for your help.
void GuiTest::setLabelsVisible(int index, bool visible){
QLabel* labels[norm_size] = {ui->norm_label_2,
ui->norm_label_3,
ui->norm_label_4,
ui->norm_label_5,
ui->norm_label_6,
ui->norm_label_7,
ui->norm_label_8};
labels[index]->setVisible(visible);
}
the best way to do this, I found, would to go this direction. Instead of setting this list of objects as global just have separate functions that will manipulate the structure in some way.

Touch Event on Sprite with Cocos2d-x 3.x?

In my scene I have a vector with multiple custom sprites. When I tap on one of them, I want an action to be fired on another element on the scene, can be another sprite in the vector, or another node. I have been researching the best way to do this, but I'm not quite sure how to implement it. The options are:
Add a touch listener to the scene, and verify if it was tapped inside the bounds of the sprite with rect. containsPoint(point). And after that, I have to get the sprite that was tapped to do the action I want. For me, it doesn't seems very clean to do it this way. And if two sprites are overlaped, I have to verify if the sprite is behind or in the front in order to retrieve the desired sprite. I followed this example: Touch Event example
Add a touch listener in the subclass of the sprite (my custom sprite). And add onTouchBegan and onTouchEnded inside it. But this way, I don't know how to modify an attribute of another sprite, or another element in the scene (Is it possible to use Delegates like Objective-C does?). I followed this example: Subclass Sprite Example
My main problem is that I don't understand very well how to make a node interact with another node in the scene. I have seen a lot of tutorials, but in all of them, when you interact with a node, it only changes its attributes, not other nodes' attributes.
Thanks in advance.
I shall propose "EventCustom" way :)
You can add in your touchBegan / touchEnded methods (wherever you put them... you got the point...) additional code for passing an EventCusto to the _eventDispatcher and get it announced to the world ;)
EventCustom *e = new EventCustom("MyAwesomeEvent");
e->setUserData(ptrMyFantasticData); //This function takes a void pointer. cheers :)
_eventDispatcher->dispatchEvent(e);
You may subclass the EventCustom class but that is hardly necessary. You can always hang an object to it with setUserData().
Now the objects which need to react to the event can listen to it by
_myCustomListener = EventListenerCustom::create(
"MyAwesomeEvent",
CC_CALLBACK_1(
ListeningClass::onMyAwesomeEvent,
this
)
);
_eventDispatcher->addEventListenerWithXXXXXPriority(_myCustomListener, XXX);
//ScreenGraphPriority / FixedPriority depends on situation. Either should work.
It's always a good practice to remove your listeners when you go off, so somewhere, perhaps in onExit(), where you removed touch listeners remove this listener too, as
_eventDispatcher->removeEventListener(_myCustomListener);
Going a bit off the track, a clarification:
CC_CALLBACK_X are a bit tricky names. The X indicates the no. of args the target function will get. Here, event dispatcher will pass 1 arg i.e. ptr to object of EventCustom you handed it, so we use CC_CALLBACK_1. The next arg - here "this" - is the object on which the method will be invoked.
In short, we may say that this callback is going to result into a function call this->onMyAwesomeEvent(e);
For CC_CALLBACK_2 onwards, we can specify additional args, 3rd arg onwards.
Coming back to the issue at hand, ListeningClass::onMyAwesomeEvent will look something like
void ListeningClass::onMyAwesomeEvent(EventCustom *e)
{
MyFantasticData *d = (MyFantasticData *) e->getUserData();
CCLOG("[ListeningClass::onMyAwesomeEvent] %d", d->getMyPreciousInt());
}
Hope it helps :)
Set your elements tags or names with setTag and setName. Then if element x is touched, get them with getChildByTag or getChildByName and do what you need to do.
With the second option you list above.
To make a node interact with another node in the scene you can add touch callback function to your custom sprite object like that:
https://github.com/Longpc/RTS/tree/master/Classes/base/dialogBase
and in main scene you can define function to handle this callback. So you can do every thing to unit in you scene

QT add widgets to UI anywhere

The application that I'm building is supposed to create, destroy, and manipluate widgets that I've created
The problem is I'm not making a simple program with nice buttons where everything is symmetrical and needs to be evenly spaced and handled via a layout that will automatically move everything around and space it.
And yet, the only way I know of is to manually instance a layout and add the widgets to it, but then I can't set the coordinates of them
How can I simply instance my widget, and add it to the project generated frame?
This is how I'm instantiating my class, in which case I then set my own parameters:
Tile *tile = new Tile;
tile->setImg("://Images/placeholderTile.png");
tile->setCol(true);
tile->setGeometry(retX(line),retY(line),50,50);
To reiterate, I want to add my own widgets to a frame outside of the editor (only by code), and be able to manually move them around the frame by code.
I don't see an ".addWidget() as a method accessible from the QFrame, and yet they can be children within the designer, why can't I do this by code? Whenever I try to do it manually and add them to any layout, any attempt I make to manually set the widgets location doesn't do anything. I haven't overridden the setGeometry
I fixed my problem
After 2 hours of continual searching I finally came across my answer
I never thought that you could set the parent of a widget by code, as I thought you strictly had to add it in as a child of something else, not the reverse and declare that it should have a parent
So, by simply adding the line:
tile->setParent(ui->frame);
completely fixed my problem.
I will change this post back and submit the answer tomorrow when I'm allowed to by this site.
Thank you to those who actually came though. I'm just glad I managed to fix it before that.
All you need is to pass the parent to the widget's constructor:
Tile *tile = new Tile(ui->frame); // <-- here
tile->setImg("://Images/placeholderTile.png");
tile->setCol(true);
tile->setGeometry(retX(line),retY(line),50,50);
Since Tile is your own class, you should definitely have a Qt-style, parent-taking explicit constructor for it:
class Tile : public QWidget {
...
public:
explicit Tile(QWidget * parent = 0) : QWidget(parent) { ... }
};
Another approach is to write your own layout that would know about the relationships that are to be held between your objects. After you do it once, writing custom layouts isn't that hard.

Weird bug in Qt application

In my application, I have my re-implemented QGraphicsView checking for a mouseReleaseEvent(), and then telling the item at the position the mouse is at to handle the event.
The QGraphicsItem for my view is made up of two other QGraphicsItems, and I check which one of the two is being clicked on (or rather having the button released on), and handle the respective events.
In my Widget's constructor, I set one of the items as selected by default, using the same methods I used when the items detect a release.
When I debugged, I found that for the LabelItem, select is called without a problem from the constructor (and the result is clear when I first start the application). But, when I click on the items, the application terminates. I saw that I was getting into the select function, but not leaving it. So the problem is here.
Which is very weird, because the select function is just a single line setter.
void LabelItem::select()
{
selected = true;
}
This is the mouseReleaseEvent;
void LayerView::mouseReleaseEvent(QMouseEvent *event)
{
LayerItem *l;
if(event->button() == Qt::LeftButton)
{
l = (LayerItem *) itemAt(event->pos());
if(l->inLabel(event->pos()))
{ //No problem upto this point, if label is clicked on
l->setSelection(true); //in setSelection, I call select() or unselect() of LabelItem,
//which is a child of LayerItem, and the problem is there.
//In the constructor for my main widget, I use setSelection
//for the bottom most LayerItem, and have no issues.
emit selected(l->getId());
}
else if(l->inCheckBox(event->pos()))
{
bool t = l->toggleCheckState();
emit toggled(l->getId(), t);
}
}
}
When I commented the line out in the function, I had no errors. I have not debugged for the other QGraphicsItem, CheckBoxItem, but the application terminates for its events as well. I think the problem might be related, so I'm concentrating on select, for now.
I have absolutely no clue as to what could have caused this and why this is happening. From my past experience, I'm pretty sure it's something simple which I'm stupidly not thinking of, but I can't figure out what.
Help would really be appreciated.
If the LabelItem is on top of the LayerItem, itemAt will most likely return the LabelItem because it is the topmost item under the mouse. Unless the LabelItem is set to not accept any mouse button with l->setAcceptedMouseButtons(0).
Try to use qgraphicsitem_cast to test the type of the item. Each derived class must redefine QGraphicsItem::type() to return a distinct value for the cast function to be able to identify the type.
You also could handle the clicks in the items themselves by redefining their QGraphicsItem::mouseReleaseEvent() method, it would remove the need for the evil cast, but you have to remove the function LayerView::mouseReleaseEvent() or at least recall the base class implementation, QGraphicsView::mouseReleaseEvent(), to allow the item(s) to receive the event.
I have seen these odd behaviours: It was mostly binary incompatibility - the c++ side looks correct, and the crash just does not make sense. As you stated: In your code the "selected" variable cannot be the cause. Do you might have changed the declaration and forgot the recompile all linked objects. Just clean and recompile all object files. Worked for me in 99% of the cases.