[PyQt] Non-Modal Dialog

Dennis Jensen djensen at pgcontrols.com
Mon Oct 7 23:08:29 BST 2019


Actually QPixMap is not only not Thread safe per-sae but elements of it 
are Thread prohibited as are all QtWidgets

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 
> <CRhode at lacusveris.com <mailto:CRhode at lacusveris.com>> 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 PyQt at riverbankcomputing.com
>     <mailto:PyQt at riverbankcomputing.com>
>     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    PyQt at riverbankcomputing.com
> https://www.riverbankcomputing.com/mailman/listinfo/pyqt
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20191007/c37c81ed/attachment-0001.html>


More information about the PyQt mailing list