[PyQt] Non-Modal Dialog
Dennis Jensen
djensen at pgcontrols.com
Wed Oct 9 14:47:08 BST 2019
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 <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