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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.