[PyQt] Non-Modal Dialog

Kyle Altendorf sda at fstab.net
Wed Oct 9 02:48:23 BST 2019



On October 7, 2019 6:08:29 PM EDT, Dennis Jensen <djensen at pgcontrols.com> 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 
>> <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


More information about the PyQt mailing list