[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pykde
Subject:    Re: [PyQt] Non-Modal Dialog
From:       Dennis Jensen <djensen () pgcontrols ! com>
Date:       2019-10-09 13:47:08
Message-ID: 2449f4e8-2497-261b-dd64-909ebfe7463f () pgcontrols ! com
[Download RAW message or body]

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@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@lacusveris.com <mailto:CRhode@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@riverbankcomputing.com
> > > <mailto:PyQt@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@riverbankcomputing.com
> > > https://www.riverbankcomputing.com/mailman/listinfo/pyqt
_______________________________________________
PyQt mailing list    PyQt@riverbankcomputing.com
https://www.riverbankcomputing.com/mailman/listinfo/pyqt


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic