Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Widget deletion process on app close?



  • I have data that I want to save when the user closes the last window QMainWindow. I initially set things to save in an overridden closeEvent method, but it takes a couple of seconds to save, so I moved it into a slot connected to the lastWindowClosed signal to avoid having a frozen window.

    I don't understand how this works. When is the object actually deleted? I would've thought immediately after the closeEvent was accepted, but I seem to be able to access things (e.g. said data) in my slot.

    Further, I see that I can still access my own member variables in my inherited class after returning from app.exec_(), though inherited member variables seem to be deleted. Why?


  • Qt Champions 2019

    @swpease said in Widget deletion process on app close?:

    I can still access my own member variables in my inherited class

    What inherited class? How is its life-time? Can you show your code?

    "When is the object actually deleted?" - that depends. If it does not have parent you are responsible for it's deletion. If it has a parent it is deleted when parent is deleted. Take a loop at https://doc.qt.io/qt-5/objecttrees.html



  • @swpease

    When is the object actually deleted?

    In addition to @jsulm, there is also the Qt::WA_DeleteOnClose widget attribute (https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum), which you may or may not be using on your various windows etc.


  • Banned

    Slightly different thought -- instead of firing off that secondary process why do you not just simply hide the window as you manipulate whatever you need to prior to fully shutting it down?



  • @Denni-0 Where is/are appropriate places to do that? If I put it in an overridden closeEvent(), it doesn't work, which makes sense to me. Maybe check for QCloseEvent in an overridden event()?



  • @jsulm Here is a pared down, working equivalent:

    import json
    
    from PySide2.QtCore import Qt
    from PySide2.QtWidgets import QApplication, QMainWindow
    
    
    class MainWindow(QMainWindow):
        def __init__(self, data, data_src='data.json'):
            super().__init__()
    
            self.setAttribute(Qt.WA_DeleteOnClose)
            self.data_modified = False
            self.data_src = data_src
            self.data = data
    
            QApplication.instance().lastWindowClosed.connect(self.save_dict)
    
        def save_dict(self):
            print(self.data)  # {'foo': 'bar'}
            print(self)  # <__main__.MainWindow(0x7fdf6ac551d0) at 0x109de7200>
    
    
    if __name__ == '__main__':
    
        import sys
    
        app = QApplication(sys.argv)
    
        data_src = 'data.json'
        with open(data_src) as f:
            data: dict = json.load(f)
    
        mainWin = MainWindow(data, data_src=data_src)
        mainWin.show()
        x = app.exec_()
    
        print(mainWin.data)  # {'foo': 'bar'}
        print(mainWin)  # RuntimeError: Internal C++ object (MainWindow) already deleted.
    
        sys.exit(x)
    

    where data.json is just {"foo": "bar"}

    In the actual program, there could be multiple instances of MainWindow, mimicking the sdi example, if that matters.


  • Banned

    Well I would think a properly coded override of the correct closeEvent -- there might be more than one -- would be where you capture that event -- hide the window -- do the things you need to do -- then let it finish closing down -- I do this with my program because when you close the MainWindow there are a lot of things that need to be closed prior to it fully closing


  • Lifetime Qt Champion

    Hi,

    Just a silly idea but if the saving takes that long, you can use a QSplashScreen to tell the user there's something going on. That would make it explicit that your app is not frozen.



  • @SGaist Thanks for the suggestion. It goes in line with the hide() suggestion. I was under the impression that it wouldn't work because I had initially tried using QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)), which didn't work. Now I'm getting confused. I have:

    def closeEvent(self, event):
        if self.maybe_save():
            self.write_settings()
            # All work sometimes if `self.text_edit.document().isModified()` is True.
    
            # Doesn't work (ever?) if `self.text_edit.document().isModified()` is False.
            # QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            # QApplication.instance().processEvents()
    
            # Both work always(?) if `self.text_edit.document().isModified()` is False.
            # self.hide()
            # QApplication.instance().processEvents()
    
            # splash = QSplashScreen(QPixmap(':/images/new.png'))
            # splash.show()
            # QApplication.instance().processEvents()
    
            if self.dict_modified:
                with open(self.data_src, 'w') as f:
                    json.dump(self.data, f)
            event.accept()
        else:
            event.ignore()
    

    with:

    def maybe_save(self):
        if self.text_edit.document().isModified():
            ret = QMessageBox.warning(self, "x",
                    "The document has been modified.\nDo you want to save your changes?",
                    QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
    
            if ret == QMessageBox.Save:
                return self.save()
            elif ret == QMessageBox.Cancel:
                return False
    
        return True
    

    Why would the call sometimes work?



  • @swpease said in Widget deletion process on app close?:

    Why would the call sometimes work?

    Please define what you mean by "(sometimes) work"? Crashes? Hangs? Does not save? Saves but does not close? ....

    def closeEvent(self, event):
        if self.maybe_save():
    
    ...
    
    def maybe_save(self):
            if ret == QMessageBox.Save:
                return self.save()
    

    So what does your save() method return?

    Using a Python debugger to step through your code, or even just print() statements, is a really useful skill.



  • hide() only works sometimes for me in the following:

    import json
    import time
    
    from PySide2.QtCore import Qt
    from PySide2.QtWidgets import QApplication, QMainWindow, QMessageBox
    
    
    class MainWindow(QMainWindow):
        def __init__(self, data, data_src='data.json'):
            super().__init__()
    
            self.setAttribute(Qt.WA_DeleteOnClose)
            self.data_modified = False
            self.data_src = data_src
            self.data = data
    
        def closeEvent(self, event):
            if self.maybe_save():
    
                # MainWindow doesn't always hide
                self.hide()
                QApplication.instance().processEvents()
    
                time.sleep(10)
                # with open(self.data_src, 'w') as f:
                #     json.dump(self.data, f)
    
                event.accept()
            else:
                event.ignore()
    
        def maybe_save(self):
            if True:
                ret = QMessageBox.warning(self, "x",
                        "The document has been modified.\nDo you want to save your changes?",
                        QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
    
                if ret == QMessageBox.Save:
                    return True
                elif ret == QMessageBox.Cancel:
                    return False
    
            return True
    
    
    if __name__ == '__main__':
    
        import sys
    
        app = QApplication(sys.argv)
    
        data_src = 'data.json'  # Mine is 21 MB; takes a couple of seconds to `dump`
        with open(data_src) as f:
            data: dict = json.load(f)
    
        mainWin = MainWindow(data, data_src=data_src)
        mainWin.show()
        sys.exit(app.exec_())

  • Lifetime Qt Champion

    You are trying to hide your window while you are already in the process do to it. That's not a good idea. Even more a bad idea is to block the thread with sleeping.

    Don't try to hide your widget in the close event, just show the splash screen, do your saving, and then everything will close nicely with the user knowing that things are going on.



  • @SGaist You mentioned blocking as a bad thing. Do you have a simple elaboration that could help me understand? From what I can determine, the QMessageBox is the source of the problem. I still have inconsistent behavior using a splash screen (it displays only sometimes), but only if the QMessageBox warning modal (blocking, right?) gets used.

    Could this be an OS/version sort of issue?

    Also, I'm not actually calling sleep in my code, I just plugged it in in case someone wanted to try running the code without needing to find a big JSON object to input. I was under the impression that it would behave the same as a lengthy file write.



  • @swpease
    I don't see how QMesssageBox is relevant to your issue. You are just using that to ask a question. Your issue lies in the time taken to do the save when the user tells you to do so.

    I agree with @SGaist. If you dump your idea of hiding the window while the save proceeds and just went for a QProgressDialog or maybe a QSplashScreen, or even just a Qt::WaitCursor, for the duration of the save, it seems to me you would have resolved this by now. (I'm surprised a 21MB save even takes 2 seconds, but there you are --- hmm, maybe I'm over-optimistic.)


  • Banned

    Okay I run this in PyQt5 instead of PySide2 and it works every time -- so if you have any trouble running this in PySide2 its a PySide2 issue.

    from PySide2.QtCore import Qt
    from PySide2.QtWidgets import QApplication, QMainWindow, QMessageBox
    
    import json
    import time
    
    
    class MainWindow(QMainWindow):
        def __init__(self, data_src):
            QMainWindow.__init__(self)
            self.data_src = data_src
    
            self.setAttribute(Qt.WA_DeleteOnClose)
            self.data_modified = False
    
        def closeEvent(self, event):
            if self.MaybeSave():
                self.hide()
                self.SaveData()
    
        def MaybeSave(self):
            if True:
                ret = QMessageBox.warning(self, "x",
                        "The document has been modified.\nDo you want to save your changes?",
                        QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
    
                if ret == QMessageBox.Save:
                    return True
                elif ret == QMessageBox.Cancel:
                    return False
    
            return True
    
        def SaveData(self):
            print('----- Saving Data')
            time.sleep(5)
        
    if __name__ == "__main__":
        MainEventHandler = QApplication([])
    
        data_src = 'data.json'
    
        MainApplication = MainWindow(data_src)
        MainApplication.show()
        
        MainEventHandler.exec()
    
      # If anyone wants more extensive free help I run an online lab-like classroom-like 
      # message server feel free and drop by you will not be able to post until I clear 
      # you as a student as this prevents spammers so if interested here is the invite
      # https://discord.gg/3D8huKC
    


  • Hmm... Well, I'm not sure what's going wrong on my end, so I'll just go with my original solution. I'll just have to live with the probability that somewhere out there, functional programming and containers fans are laughing at me. Thanks for the input.

    Jon: if you're going to criticize someone for asking for an explanation to a response, I suggest you find a new hobby.


  • Banned

    @swpease are you saying that code I provided does not work for you in PySide2?


Log in to reply