Re: Non-Modal Dialog
Kyle Altendorf <[email protected]>
| Newsgroups | gmane.comp.python.pyqt-pykde |
|---|---|
| Message-ID | <[email protected]> |
On October 7, 2019 6:08:29 PM EDT, Dennis Jensen <[email protected]> wrote: >Actually QPixMap is not only not Thread safe per-sae but elements of it > >are Thread prohibited as are all QtWidgets I know you shouldn't access GUI elements from other threads. I wasn't aware of this. Do you have a reference? >On 10/6/2019 2:59 PM, Maurizio Berti wrote: >> After some tests, I found that the problem is not about modal windows > >> at all. >> If you check carefully, even after the QThread.sleep, the dialog >> interaction is blocked until the image is saved. >> >> It turns out that the issue here is that QPixmap is *not* thread safe > >> (at least, not in all platforms). >> It does not seem to be any official documentation about this, but I >> found some information on this thread: >> >https://forum.qt.io/topic/52397/not-safe-to-use-qpixmap-outside-the-gui-thread/4 >> >> A simple solution is to convert the pixmap to a QImage and use its >> save() function, which seems to be thread safe and doesn't block the >GUI: >> >> WIN.pixmap.toImage().save('/tmp/nonmodal_test.png') >> >> A couple of slightly unrelated suggestions, if I may. >> - You don't need to return every function if the returned value is >not >> required, as Python implicitly returns None if no explicit return >exists >> - Avoid using object names that already are existing properties or >> methods (like self.thread) >> - You can connect the finished signal directly to the close (or, >> better, accept) slot of the popup and delete the thread itelf: >> >> class WaitMessage(QMessageBox): >> ''' a message box that can't be closed by the user >> ''' >> def __init__(self, parent): >> super(WaitMessage, self).__init__(QMessageBox.Information, 'Wait', >> 'This is a test, please wait', parent=parent) >> # setting NoButton in the constructor won't be enough, it >must >> be set explicitly >> # in this way the Escape key won't hide the dialog >> self.setStandardButtons(QMessageBox.NoButton) >> >> def closeEvent(self, event): >> # ignore any attempt to close the dialog via the title bar >buttoni >> event.ignore() >> >> class WinMain(QMainWindow): >> # ... >> def test_part_1(self): >> popup = WaitMessage(self) >> worker = ThdWorker(self) >> worker.started.connect(popup.exec_) >> worker.finished.connect(worker.deleteLater) >> worker.finished.connect(popup.deleteLater) >> worker.start() >> >> With this approach you won't need another function to delete the >popup >> nor the thread, as they will be deleted in Qt "scope" with >> deleteLater, and will be deleted by python followingly, since they're > >> not instance attributes. >> Just ensure that both the popup and the worker have a parent, >> otherwise they will be garbage collected as soon as the function >returns. >> >> Cheers, >> Maurizio >> >> >> Il giorno dom 6 ott 2019 alle ore 18:20 Chuck Rhode >> <[email protected] <mailto:[email protected]>> ha scritto: >> >> -----BEGIN PGP SIGNED MESSAGE----- >> Hash: SHA1 >> >> In the deep past, I've worked with VB, Tk, Pascal Delphi, and >Gtk. I >> seem to recall that all had non-modal dialog boxes for situations >> where >> you wanted to inform the user through the user interface that a >> long-running task was in progress. Is this not done anymore? >> >> I assume there are still long running tasks such as QPixmap >*save*s. >> >> These tasks cannot emit progress signals for powering a progress >bar. >> What alternatives are there? >> >> I can't get a cursor change to show up. >> >> Does one nowadays throw up a semitransparent overlay with a >spinner? >> That is not so simple or informative as a non-modal dialog, I >think. >> >> I've pored over Stackoverflow posts about Qt from the last >decade, and >> I don't see a lot that I can use. Most say its as simple as >> QMessageBox *open* instead of *exec_*, but this has not been my >> experience with PyQt. Here is code that works, however: >> >> #!/usr/bin/python >> # -*- coding: utf-8 -*- >> >> # nonmodal_example.py >> # 2019 Oct 06 . ccr >> >> """Demonstrate how to show a non-modal dialog box before starting >a >> long-running task. >> >> """ >> >> from __future__ import division >> import sys >> from PyQt5.QtWidgets import ( >> QApplication, >> QMainWindow, >> QWidget, >> QGridLayout, >> QPushButton, >> QMessageBox, >> ) >> from PyQt5.QtGui import ( >> QPixmap, >> QTransform, >> ) >> from PyQt5.QtCore import( >> QThread, >> ) >> >> ZERO = 0 >> SPACE = ' ' >> NULL = '' >> NUL = '\x00' >> NA = -1 >> >> >> class ThdWorker(QThread): >> >> """An (arbitrarily) long-running task. >> >> """ >> >> def run(self): >> QThread.sleep(1) >> WIN.pixmap.save('/tmp/nonmodal_test.png') >> return >> >> >> class WinMain(QMainWindow): >> >> """The (trivial) main window of the graphical user interface. >> >> """ >> >> def __init__(self): >> super(WinMain, self).__init__() >> self.resize(800, 600) >> self.central_widget = QWidget(self) >> self.setCentralWidget(self.central_widget) >> self.layout = QGridLayout() >> self.central_widget.setLayout(self.layout) >> self.btn_run = QPushButton('Run Next Test Scenario', >> self.central_widget) >> self.btn_run.setMaximumSize(200, 30) >> self.btn_run.clicked.connect(self.test_part_1) >> self.layout.addWidget(self.btn_run) >> figure = QPixmap() >> result = >> >figure.load('/usr/share/qt5/doc/qtdesigner/images/designer-screenshot.png') >> if result: >> pass >> else: >> raise NotImplementedError >> transformation = QTransform() >> transformation.scale(5.0, 5.0) >> self.pixmap = figure.transformed(transformation) >> return >> >> def test_part_1(self): >> >> """Fork a thread. >> >> """ >> >> self.popup = QMessageBox(QMessageBox.Information, None, >> 'This is a test. Please wait.') >> self.popup.show() >> self.thread = ThdWorker(self) >> self.thread.finished.connect(self.test_part_2) >> self.thread.start() >> return >> >> def test_part_2(self): >> self.popup.close() >> del self.popup >> del self.thread >> return >> >> >> if __name__ == "__main__": >> APP = QApplication(sys.argv) >> WIN = WinMain() >> WIN.show() >> result = APP.exec_() >> sys.exit(result) >> >> >> # Fin >> >> What really gripes me about this example, despite the fact that >it >> works for me, is the sleep at the beginning of the thread that >starts >> the long running thread. The sleep is essential. Although the >> QMessageBox *show* paints its frame, it doesn't have time to >paint its >> contents unless the thread pauses before it even gets going. >> >> There HAS TO BE a more elegant way to allow non-modal dialogs to >paint >> completely. I've tried lots of different Stackoverflow >> recommendations, and nothing works. I have a more complete (and >> considerably longer) test suite ready to show you that is >> available upon >> request. >> >> - -- >> .. Be Seeing You, >> .. Chuck Rhode, Sheboygan, WI, USA >> .. Weather: http://LacusVeris.com/WX >> .. 55° — Wind WSW 10 mph >> >> -----BEGIN PGP SIGNATURE----- >> Version: GnuPG v2 >> >> iEYEARECAAYFAl2aE+IACgkQYNv8YqSjllJHuwCfW+tQv04X3s8e6jE5gWZPqbeN >> kZgAn2nbhXFERp5rmIcEuO6yEvC8+HVF >> =bE0d >> -----END PGP SIGNATURE----- >> _______________________________________________ >> PyQt mailing list [email protected] >> <mailto:[email protected]> >> https://www.riverbankcomputing.com/mailman/listinfo/pyqt >> >> >> >> -- >> È difficile avere una convinzione precisa quando si parla delle >> ragioni del cuore. - "Sostiene Pereira", Antonio Tabucchi >> http://www.jidesk.net >> >> _______________________________________________ >> PyQt mailing list [email protected] >> https://www.riverbankcomputing.com/mailman/listinfo/pyqt _______________________________________________ PyQt mailing list [email protected] https://www.riverbankcomputing.com/mailman/listinfo/pyqt