Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Removing a QGraphicsProxyWidget from QGraphicsScene crashes the app, but only when using a button on a toolbar
QtWS25 Last Chance

Removing a QGraphicsProxyWidget from QGraphicsScene crashes the app, but only when using a button on a toolbar

Scheduled Pinned Locked Moved Unsolved Qt for Python
qgraphicsviewqgraphicsproxywcrashqtoolbar
9 Posts 3 Posters 1.3k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • C Offline
    C Offline
    canol
    wrote on 6 Jun 2021, 13:16 last edited by
    #1

    Hello, I have a medium sized app which had some random crashing problems. The crashes don't print any error messages. After a long investigation, getting rid of a lot of code, now I have a minimal example.

    The app has a QGraphicsView, and I add and remove QWidgets dynamically on the graphics view. The widgets have a toolbar, and the toolbar had a close button on it, which removes and deletes the widget from QGraphicsView. The crashes would randomly happen when I used this close button.

    Here is a complete example (it is a PySide2 program, I haven't tried it with C++):

    import sys
    
    from PySide2.QtCore import Signal
    from PySide2.QtGui import QKeySequence
    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QAction, \
        QToolBar
    
    
    class MyWidget(QWidget):
        destroy_myself = Signal()
    
        def __init__(self):
            super().__init__()
    
            layout = QVBoxLayout()
            self.setLayout(layout)
    
            toolbar = QToolBar()
            layout.addWidget(toolbar)
            self._remove_widget_action = QAction(f"Remove Widget", self)
            self._remove_widget_action.setShortcut(QKeySequence("d"))
            self._remove_widget_action.triggered.connect(self._remove_widget_action_cb)
            toolbar.addAction(self._remove_widget_action)
    
        def _remove_widget_action_cb(self):
            self.destroy_myself.emit()
    
    
    class MyGraphicsView(QGraphicsView):
        def __init__(self):
            super().__init__()
    
            self._widget = None
    
            self._scene = QGraphicsScene()
            self.setScene(self._scene)
    
            remove_widget_action = QAction(f"Remove Widget", self)
            remove_widget_action.setShortcut(QKeySequence("a"))
            remove_widget_action.triggered.connect(self._create_widget)
            self.addAction(remove_widget_action)
    
        def _widget_wants_to_be_destroyed(self):
            if self._widget is not None:
                widget = self._widget
                self._scene.removeItem(widget.graphicsProxyWidget())
                self._widget = None
    
        def _create_widget(self):
            if self._widget is None:
                self._widget = MyWidget()
                self._scene.addWidget(self._widget)
                self._widget.destroy_myself.connect(self._widget_wants_to_be_destroyed)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        gv = MyGraphicsView()
        gv.show()
        app.exec_()
    

    OS: Windows 10
    Python: 3.8.6 64-bit
    PySide2: 5.15.2

    You can add a new widget to QGraphicsView using the "a" shortcut button on your keyboard. Then you can remove that widget by clicking on the toolbar button. Do this a few times, and you will eventually crash the program (without any error message).

    Note that you need to remove the widget by using mouse click in order to crash. Weirdly, if you use the "d" shortcut button on your keyboard to remove the widget, the program will not crash.

    My questions are:

    1) Why does it crash when I trigger the action using mouse click on the button?
    2) Why does it NOT crash, when I use the keyboard shortcut for the action?

    Here is another behavior I don't understand: if I implement the closing button, not using a toolbar, but using a regular QPushButton, it does not crash if I press on it via mouse. Here is a complete example, this time implemented using a QPushButton:

    import sys
    
    from PySide2.QtCore import Signal
    from PySide2.QtGui import QKeySequence
    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QAction, QPushButton
    
    
    class MyWidget(QWidget):
        destroy_myself = Signal()
    
        def __init__(self):
            super().__init__()
    
            layout = QVBoxLayout()
            self.setLayout(layout)
    
            self._button = QPushButton("Click to Destroy")
            layout.addWidget(self._button)
            self._button.clicked.connect(self._remove_widget_action_cb)
    
        def _remove_widget_action_cb(self):
            self.destroy_myself.emit()
    
    
    class MyGraphicsView(QGraphicsView):
        def __init__(self):
            super().__init__()
    
            self._widget = None
    
            self._scene = QGraphicsScene()
            self.setScene(self._scene)
    
            remove_widget_action = QAction(f"Remove Widget", self)
            remove_widget_action.setShortcut(QKeySequence("a"))
            remove_widget_action.triggered.connect(self._create_widget)
            self.addAction(remove_widget_action)
    
        def _widget_wants_to_be_destroyed(self):
            if self._widget is not None:
                widget = self._widget
                self._scene.removeItem(widget.graphicsProxyWidget())
                self._widget = None
    
        def _create_widget(self):
            if self._widget is None:
                self._widget = MyWidget()
                self._scene.addWidget(self._widget)
                self._widget.destroy_myself.connect(self._widget_wants_to_be_destroyed)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        gv = MyGraphicsView()
        gv.show()
        app.exec_()
    

    The example above uses a QPushButton, instead of a QToolBar+QAction, and it does not crash. So another question:

    3) Why does it not crash when I use QPushButton instead of QToolBar+QAction?

    I found a workaround, which avoids crashes even when using QToolBar+QAction: I call the removeItem() method using a QTimer.singleShot() method. Here is a complete example:

    import sys
    
    from PySide2.QtCore import Signal, QTimer
    from PySide2.QtGui import QKeySequence
    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QAction, QToolBar
    
    
    class MyWidget(QWidget):
        destroy_myself = Signal()
    
        def __init__(self):
            super().__init__()
    
            layout = QVBoxLayout()
            self.setLayout(layout)
    
            toolbar = QToolBar()
            layout.addWidget(toolbar)
            self._remove_widget_action = QAction(f"Remove Widget", self)
            self._remove_widget_action.setShortcut(QKeySequence("d"))
            self._remove_widget_action.triggered.connect(self._remove_widget_action_cb)
            toolbar.addAction(self._remove_widget_action)
    
        def _remove_widget_action_cb(self):
            self.destroy_myself.emit()
    
    
    class MyGraphicsView(QGraphicsView):
        def __init__(self):
            super().__init__()
    
            self._widget = None
    
            self._scene = QGraphicsScene()
            self.setScene(self._scene)
    
            remove_widget_action = QAction(f"Remove Widget", self)
            remove_widget_action.setShortcut(QKeySequence("a"))
            remove_widget_action.triggered.connect(self._create_widget)
            self.addAction(remove_widget_action)
    
        def _widget_wants_to_be_destroyed(self):
            if self._widget is not None:
                widget = self._widget
                QTimer.singleShot(0, lambda: self._scene.removeItem(widget.graphicsProxyWidget()))
                self._widget = None
    
        def _create_widget(self):
            if self._widget is None:
                self._widget = MyWidget()
                self._scene.addWidget(self._widget)
                self._widget.destroy_myself.connect(self._widget_wants_to_be_destroyed)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        gv = MyGraphicsView()
        gv.show()
        app.exec_()
    

    This example won't crash. I guess waiting for the control to return to event loop before removing the item helps. So my final question is:

    4) Why does using QTimer.singleShot() helps here?

    jeremy_kJ 1 Reply Last reply 9 Jun 2021, 05:14
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on 6 Jun 2021, 18:27 last edited by
      #2

      Hi,

      What if you call widget.deleteLater() rather than setting it as None ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      C 1 Reply Last reply 6 Jun 2021, 19:10
      0
      • SGaistS SGaist
        6 Jun 2021, 18:27

        Hi,

        What if you call widget.deleteLater() rather than setting it as None ?

        C Offline
        C Offline
        canol
        wrote on 6 Jun 2021, 19:10 last edited by
        #3

        @SGaist said in Removing a QGraphicsProxyWidget from QGraphicsScene crashes the app, but only when using a button on a toolbar:

        Hi,

        What if you call widget.deleteLater() rather than setting it as None ?

        If I replace removeItem() line with deleteLater() it still crashes. If I replace the None assignment line with deleteLater() but keep the removeItem(), it gives an error saying the object is already deleted.

        By the way, does moving the topic to Qt for Python means that this error does not happen in C++?

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on 6 Jun 2021, 19:27 last edited by
          #4

          Did you try to run your application using the debugger to see exactly what happens when the removal fails ?

          @canol said in Removing a QGraphicsProxyWidget from QGraphicsScene crashes the app, but only when using a button on a toolbar:

          By the way, does moving the topic to Qt for Python means that this error does not happen in C++?

          No it just means that you are developing with PySide2 hence it makes more sense to be in the Qt for Python sub-forum. You'll have more eyes from people using Qt with Python here.
          Whether it's related to the C++ implementation needs to be confirmed and even so, it does not hurt if in this sub-forum as it could also be a binding issue and not a problem with the underlying Qt libraries.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          C 1 Reply Last reply 6 Jun 2021, 19:41
          0
          • SGaistS SGaist
            6 Jun 2021, 19:27

            Did you try to run your application using the debugger to see exactly what happens when the removal fails ?

            @canol said in Removing a QGraphicsProxyWidget from QGraphicsScene crashes the app, but only when using a button on a toolbar:

            By the way, does moving the topic to Qt for Python means that this error does not happen in C++?

            No it just means that you are developing with PySide2 hence it makes more sense to be in the Qt for Python sub-forum. You'll have more eyes from people using Qt with Python here.
            Whether it's related to the C++ implementation needs to be confirmed and even so, it does not hurt if in this sub-forum as it could also be a binding issue and not a problem with the underlying Qt libraries.

            C Offline
            C Offline
            canol
            wrote on 6 Jun 2021, 19:41 last edited by
            #5

            @SGaist I am using PyCharm and run the program in debug mode but nothing is printed when the program crashes, or it does not throw an exception either. Is there a way to get more info when the program fails?

            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on 6 Jun 2021, 19:47 last edited by
              #6

              Since you can reliably trigger it, I would put a Pdb set_trace there and go step by step until if fails. You should then be able to get a stack trace.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              C 1 Reply Last reply 6 Jun 2021, 20:11
              0
              • SGaistS SGaist
                6 Jun 2021, 19:47

                Since you can reliably trigger it, I would put a Pdb set_trace there and go step by step until if fails. You should then be able to get a stack trace.

                C Offline
                C Offline
                canol
                wrote on 6 Jun 2021, 20:11 last edited by
                #7

                @SGaist I am very unfamiliar with pdb but I tried it. I put pdb set_trace to that method, and then typed "s"+Enter until it failed. But when it fails, it kills the Python process, so I cannot tell it to print the trace anymore, but it prints this output before killing the process:

                "Process finished with exit code -1073741819 (0xC0000005)"

                1 Reply Last reply
                0
                • SGaistS Offline
                  SGaistS Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on 6 Jun 2021, 21:26 last edited by
                  #8

                  In that case, you will have to go to the next level and use gdb.

                  The Python Wiki shows how pretty nicely.

                  Interested in AI ? www.idiap.ch
                  Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                  1 Reply Last reply
                  1
                  • C canol
                    6 Jun 2021, 13:16

                    Hello, I have a medium sized app which had some random crashing problems. The crashes don't print any error messages. After a long investigation, getting rid of a lot of code, now I have a minimal example.

                    The app has a QGraphicsView, and I add and remove QWidgets dynamically on the graphics view. The widgets have a toolbar, and the toolbar had a close button on it, which removes and deletes the widget from QGraphicsView. The crashes would randomly happen when I used this close button.

                    Here is a complete example (it is a PySide2 program, I haven't tried it with C++):

                    import sys
                    
                    from PySide2.QtCore import Signal
                    from PySide2.QtGui import QKeySequence
                    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QAction, \
                        QToolBar
                    
                    
                    class MyWidget(QWidget):
                        destroy_myself = Signal()
                    
                        def __init__(self):
                            super().__init__()
                    
                            layout = QVBoxLayout()
                            self.setLayout(layout)
                    
                            toolbar = QToolBar()
                            layout.addWidget(toolbar)
                            self._remove_widget_action = QAction(f"Remove Widget", self)
                            self._remove_widget_action.setShortcut(QKeySequence("d"))
                            self._remove_widget_action.triggered.connect(self._remove_widget_action_cb)
                            toolbar.addAction(self._remove_widget_action)
                    
                        def _remove_widget_action_cb(self):
                            self.destroy_myself.emit()
                    
                    
                    class MyGraphicsView(QGraphicsView):
                        def __init__(self):
                            super().__init__()
                    
                            self._widget = None
                    
                            self._scene = QGraphicsScene()
                            self.setScene(self._scene)
                    
                            remove_widget_action = QAction(f"Remove Widget", self)
                            remove_widget_action.setShortcut(QKeySequence("a"))
                            remove_widget_action.triggered.connect(self._create_widget)
                            self.addAction(remove_widget_action)
                    
                        def _widget_wants_to_be_destroyed(self):
                            if self._widget is not None:
                                widget = self._widget
                                self._scene.removeItem(widget.graphicsProxyWidget())
                                self._widget = None
                    
                        def _create_widget(self):
                            if self._widget is None:
                                self._widget = MyWidget()
                                self._scene.addWidget(self._widget)
                                self._widget.destroy_myself.connect(self._widget_wants_to_be_destroyed)
                    
                    
                    if __name__ == "__main__":
                        app = QApplication(sys.argv)
                        gv = MyGraphicsView()
                        gv.show()
                        app.exec_()
                    

                    OS: Windows 10
                    Python: 3.8.6 64-bit
                    PySide2: 5.15.2

                    You can add a new widget to QGraphicsView using the "a" shortcut button on your keyboard. Then you can remove that widget by clicking on the toolbar button. Do this a few times, and you will eventually crash the program (without any error message).

                    Note that you need to remove the widget by using mouse click in order to crash. Weirdly, if you use the "d" shortcut button on your keyboard to remove the widget, the program will not crash.

                    My questions are:

                    1) Why does it crash when I trigger the action using mouse click on the button?
                    2) Why does it NOT crash, when I use the keyboard shortcut for the action?

                    Here is another behavior I don't understand: if I implement the closing button, not using a toolbar, but using a regular QPushButton, it does not crash if I press on it via mouse. Here is a complete example, this time implemented using a QPushButton:

                    import sys
                    
                    from PySide2.QtCore import Signal
                    from PySide2.QtGui import QKeySequence
                    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QAction, QPushButton
                    
                    
                    class MyWidget(QWidget):
                        destroy_myself = Signal()
                    
                        def __init__(self):
                            super().__init__()
                    
                            layout = QVBoxLayout()
                            self.setLayout(layout)
                    
                            self._button = QPushButton("Click to Destroy")
                            layout.addWidget(self._button)
                            self._button.clicked.connect(self._remove_widget_action_cb)
                    
                        def _remove_widget_action_cb(self):
                            self.destroy_myself.emit()
                    
                    
                    class MyGraphicsView(QGraphicsView):
                        def __init__(self):
                            super().__init__()
                    
                            self._widget = None
                    
                            self._scene = QGraphicsScene()
                            self.setScene(self._scene)
                    
                            remove_widget_action = QAction(f"Remove Widget", self)
                            remove_widget_action.setShortcut(QKeySequence("a"))
                            remove_widget_action.triggered.connect(self._create_widget)
                            self.addAction(remove_widget_action)
                    
                        def _widget_wants_to_be_destroyed(self):
                            if self._widget is not None:
                                widget = self._widget
                                self._scene.removeItem(widget.graphicsProxyWidget())
                                self._widget = None
                    
                        def _create_widget(self):
                            if self._widget is None:
                                self._widget = MyWidget()
                                self._scene.addWidget(self._widget)
                                self._widget.destroy_myself.connect(self._widget_wants_to_be_destroyed)
                    
                    
                    if __name__ == "__main__":
                        app = QApplication(sys.argv)
                        gv = MyGraphicsView()
                        gv.show()
                        app.exec_()
                    

                    The example above uses a QPushButton, instead of a QToolBar+QAction, and it does not crash. So another question:

                    3) Why does it not crash when I use QPushButton instead of QToolBar+QAction?

                    I found a workaround, which avoids crashes even when using QToolBar+QAction: I call the removeItem() method using a QTimer.singleShot() method. Here is a complete example:

                    import sys
                    
                    from PySide2.QtCore import Signal, QTimer
                    from PySide2.QtGui import QKeySequence
                    from PySide2.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QWidget, QVBoxLayout, QAction, QToolBar
                    
                    
                    class MyWidget(QWidget):
                        destroy_myself = Signal()
                    
                        def __init__(self):
                            super().__init__()
                    
                            layout = QVBoxLayout()
                            self.setLayout(layout)
                    
                            toolbar = QToolBar()
                            layout.addWidget(toolbar)
                            self._remove_widget_action = QAction(f"Remove Widget", self)
                            self._remove_widget_action.setShortcut(QKeySequence("d"))
                            self._remove_widget_action.triggered.connect(self._remove_widget_action_cb)
                            toolbar.addAction(self._remove_widget_action)
                    
                        def _remove_widget_action_cb(self):
                            self.destroy_myself.emit()
                    
                    
                    class MyGraphicsView(QGraphicsView):
                        def __init__(self):
                            super().__init__()
                    
                            self._widget = None
                    
                            self._scene = QGraphicsScene()
                            self.setScene(self._scene)
                    
                            remove_widget_action = QAction(f"Remove Widget", self)
                            remove_widget_action.setShortcut(QKeySequence("a"))
                            remove_widget_action.triggered.connect(self._create_widget)
                            self.addAction(remove_widget_action)
                    
                        def _widget_wants_to_be_destroyed(self):
                            if self._widget is not None:
                                widget = self._widget
                                QTimer.singleShot(0, lambda: self._scene.removeItem(widget.graphicsProxyWidget()))
                                self._widget = None
                    
                        def _create_widget(self):
                            if self._widget is None:
                                self._widget = MyWidget()
                                self._scene.addWidget(self._widget)
                                self._widget.destroy_myself.connect(self._widget_wants_to_be_destroyed)
                    
                    
                    if __name__ == "__main__":
                        app = QApplication(sys.argv)
                        gv = MyGraphicsView()
                        gv.show()
                        app.exec_()
                    

                    This example won't crash. I guess waiting for the control to return to event loop before removing the item helps. So my final question is:

                    4) Why does using QTimer.singleShot() helps here?

                    jeremy_kJ Offline
                    jeremy_kJ Offline
                    jeremy_k
                    wrote on 9 Jun 2021, 05:14 last edited by
                    #9

                    @canol said in Removing a QGraphicsProxyWidget from QGraphicsScene crashes the app, but only when using a button on a toolbar:

                    Hello, I have a medium sized app which had some random crashing problems. The crashes don't print any error messages. After a long investigation, getting rid of a lot of code, now I have a minimal example.

                    For problems that don't appear to produce any usable output, turning on all QLoggingCategory categories can help. QT_LOGGING_RULES="*.*=true" python test.py should flood the console with information about what Qt is doing prior to the crash.

                    In addition, installing a QObject::eventFilter() on the widget or QApplication can provide insight into how far input processing proceeded.

                    class Filter(QObject):
                        def __init__(self, target, *args, **kwargs):
                            super().__init__(*args, **kwargs)
                            target.installEventFilter(self)
                    
                        def eventFilter(self, target, event):
                            print("Attempting to deliver {} to {}".format(event.type(), target))
                            return super().eventFilter(target, event)
                    
                    app = QtWidgets.QApplication([])
                    filter = Filter(app)
                    

                    The app has a QGraphicsView, and I add and remove QWidgets dynamically on the graphics view. The widgets have a toolbar, and the toolbar had a close button on it, which removes and deletes the widget from QGraphicsView. The crashes would randomly happen when I used this close button.

                    Here is a complete example (it is a PySide2 program, I haven't tried it with C++):

                    Porting the example to PyQt5 had crashes for the same input, on every attempt. Maybe PyQt is more aggressive in object reclamation than PySide.

                    My questions are:

                    **1) Why does it crash when I trigger the action using mouse click on the button?

                    The issue appears to be a deletion of the recipient object while there are events for it remaining in the queue. As mentioned in the documentation for QObject::~QObject(), this is a problem.

                    In C++, QObject::deleteLater() provides a convenient solution. Unfortunately, PySide and PyQt interfere by deleting the object when the last python reference is removed.

                    1. Why does it NOT crash, when I use the keyboard shortcut for the action?**

                    Different triggering mechanisms lead to a different sequence of events. If the last event triggers the action, deleting the object is fine. If there's a mouse release or a paint event after, deletion in the slot connected to the action is unsafe.

                    The example above uses a QPushButton, instead of a QToolBar+QAction, and it does not crash. So another question:

                    3) Why does it not crash when I use QPushButton instead of QToolBar+QAction?

                    As with #2, the events delivered are probably different.

                    This example won't crash. I guess waiting for the control to return to event loop before removing the item helps. So my final question is:

                    4) Why does using QTimer.singleShot() helps here?

                    Using QTimer.singleShot() schedules the slot for the next iteration of the event loop, after the currently pending events have been processed. The same thing can be accomplished using a queued connection for the action's slot instead of a 0-timer.

                    Asking a question about code? http://eel.is/iso-c++/testcase/

                    1 Reply Last reply
                    1

                    1/9

                    6 Jun 2021, 13:16

                    • Login

                    • Login or register to search.
                    1 out of 9
                    • First post
                      1/9
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved