Re: Non-Modal Dialog
Dennis Jensen <[email protected]>
| Newsgroups | gmane.comp.python.pyqt-pykde |
|---|---|
| Message-ID | <[email protected]> |
I tried to find that official documentation but I am not sure where it is buried and frankly yes its buried somewhere within the official documentation. The reference verbatim stated that you cannot put a object that inherits from QtWidgets into a sub-Thread as well as some objects that inherit from QtGui they can only be put within the Main Thread (aka QApplication) however they did not state which QtGui objects which I found a bit annoying. I found this because I was trying to put a QWidget into a QThread but it was not working and I was trying to find out what I was doing wrong and then I stumbled across a comment by someone somewhere saying you cannot put a QWidget into a sub-Thread and then while researching that I stumbled across the official Qt documentation that collaborated it and a bit more. If I happen to stumble across this documentation again I will post it here. On a similar but different note : Sub-Classing a QThread became wrong in version 4.4 prior to that it was actually necessary to sub-class a QThread thus the confusion around how this is supposed to be implemented. So like with many features prior to a certain version it is done one way and after it is done differently. Just like you have to use print without ( ) in PyQt4 but in PyQt5 you have to use print with ( ) On 10/8/2019 8:48 PM, Kyle Altendorf wrote: > > 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