Re: Non-Modal Dialog
Chuck Rhode <[email protected]>
| Newsgroups | gmane.comp.python.pyqt-pykde |
|---|---|
| Message-ID | <[email protected]> |
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Sun, 6 Oct 2019 21:59:48 +0200 Maurizio Berti <[email protected]> wrote: > After some tests, I found that the problem is not about modal > windows at all. <snip> 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') That didn't change things for me. ... which is good. For my test, I require a long-running task that locks the GUI. Both QPixmap *save* and QImage *save* seem to fill the bill. I changed QMessageBox to *exec_* from *show* and fired it from the *started* signal of the thread per your example, which seems to be a straightforward approach, but that couldn't manage to display anything unless the thread paused with a *sleep* first. Returning to your kind reply.... > - 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 Hah! You're not the first to be bugged by this habit of mine. I am more comfortable making the exit from the function explicit AND TRAILING although doing so is rundundant. I mightily resist returning from the middle. I'm sure this has to do with my tenure as a COBOL '68 programmer where many (of those who were influential) considered it best practice, although it was (usually) redundant, to insert empty exit-paragraph labels explicitly to delimit the trailing edge of *perform*ed code. In Python, where everything is a function with a return value (which is usually None), I try to consider what to return and make the choice explicit -- even when IT IS nothing. When I'm writing my own classes, I'll usually make the *set* methods return *self*, for example. > - Avoid using object names that already are existing properties or > methods (like self.thread) Oh, heck! I remember, writing Pascal, where it was fairly common to prefix labels to indicate their type: x-strings, i-integers, f-functions, and xxx-methods, where xxx was the abbreviated name of the class. This had the virtue of avoiding tracking-over implementor's names during subclassing and having your own tracked-over in turn. In Python, it's much more chic to rely on implicit typing, and best practice is to avoid such prefixing, and I get into trouble. > - You can connect the finished signal directly to the close (or, > better, accept) slot of the popup and delete the thread itelf: <snip> > 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() That's cool! Didn't think of that. The modal dialog can be harnessed for non-modal duty. I dithered about whether to *del* the thread and popup structures. In my extended test suite, they are replaced by new ones for each scenario, and presumably the old ones are garbage-collected soon thereafter. Not bothering about garbage collection is one of the principal virtues of coding in Python, and I shouldn't have worried about trying to show their destruction explicitly. Aren't explicit deletes like *del* or *deleteLater* truely rundundant? Anyway here's a revised example. First is a module containing a "wOrker" object: > #!/usr/bin/python > # -*- coding: utf-8 -*- > # uidgets.py > # 2019 Oct 07 . ccr > """Local extensions to Qt widgets. > > """ > from __future__ import division > from PyQt5.QtWidgets import ( > QMessageBox, > ) > from PyQt5.QtCore import( > QThread, > ) > from PyQt5.QtCore import( > Qt, > ) > ZERO = 0 > SPACE = ' ' > NULL = '' > NUL = '\x00' > NA = -1 > class UiOrker(object): > """Fork a long-running task into its own thread after displaying a wait > box. > > *title* is the window title of the wait box. > > *text* is the text in the wait box. > > *parent* is the window that owns the wait box and the forked thread. > > *process* is the long-running task. > > *kill* is the epilog performed after the wait box is canceled or the > forked thread finishes, if any. > > *run()* starts the process. > > """ > def __init__( > self, > icon=QMessageBox.Information, > title=None, > text='Please wait.', > parent=None, > process=None, > kill=None, > ): > self.icon = icon > self.title = title > self.text = text > self.parent = parent > self.process = process > self.kill = kill > self.parms = None > return > def get_popup(self): > result = QMessageBox(self.icon, self.title, self.text, parent=self.parent) > result.setStandardButtons(QMessageBox.Cancel) > result.setWindowFlags(Qt.Dialog) > result.finished.connect(self.epilog) > return result > def get_fork(self, popup): > result = QThread(self.parent) > result.run = self.run_threaded > result.started.connect(popup.exec_) > result.finished.connect(popup.close) > return result > def run(self, *parms): > self.parms = parms > popup = self.get_popup() > self.fork = self.get_fork(popup) > self.fork.start() > return self > def run_threaded(self): > QThread.msleep(100) > self.process(*self.parms) > return > def epilog(self): > self.fork.exit(NA) > if self.kill: > self.kill(*self.parms) > return > # Fin And here is the UI: > #!/usr/bin/python > # -*- coding: utf-8 -*- > # nonmodal_example_2.py > # 2019 Oct 07 . ccr > """Demonstrate a class showing a non-modal dialog box while starting a > long-running task. > > """ > from __future__ import division > import sys > from PyQt5.QtWidgets import ( > QApplication, > QMainWindow, > QWidget, > QGridLayout, > QPushButton, > ) > from PyQt5.QtGui import ( > QPixmap, > QTransform, > ) > import uidgets > ZERO = 0 > SPACE = ' ' > NULL = '' > NUL = '\x00' > NA = -1 > 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 Test Scenario', self.central_widget) > self.btn_run.setMaximumSize(200, 30) > self.btn_run.clicked.connect(self.test) > 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(self): > """Fork a thread. > > """ > def long_running_task(*parms): > print 'process', parms > self.pixmap.toImage().save('/tmp/nonmodal_test.png') > return > def kill(*parms): > print 'kill', parms > return > uidgets.UiOrker(parent=self, process=long_running_task, kill=kill).run('Ui', ZERO) > return > if __name__ == "__main__": > APP = QApplication(sys.argv) > WIN = WinMain() > WIN.show() > result = APP.exec_() > sys.exit(result) > # Fin Still, it'd be nice not to have that niggling *sleep* at the beginning of the thread, you know. - -- .. Be Seeing You, .. Chuck Rhode, Sheboygan, WI, USA .. Weather: http://LacusVeris.com/WX .. 53° — Wind W 8 mph -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEARECAAYFAl2bqv0ACgkQYNv8YqSjllLimgCfVwkKprODT8wMme4pNfM8lk+R Eh8An2IxHZQRyU5F59eXHMCk1k6fFdG8 =SOSB -----END PGP SIGNATURE----- _______________________________________________ PyQt mailing list [email protected] https://www.riverbankcomputing.com/mailman/listinfo/pyqt