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 overriddencloseEvent
method, but it takes a couple of seconds to save, so I moved it into a slot connected to thelastWindowClosed
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? -
@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
-
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. -
@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. -
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 usingQApplication.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_())
-
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 theQMessageBox
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 howQMesssageBox
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 aQSplashScreen
, or even just aQt::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.) -
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.