[PySide] wait for QFileDialog to close

Sean Fisk sean at seanfisk.com
Wed Dec 11 21:56:13 CET 2013


Janwillem,

I'm glad you were able to able to figure out the problem. Sorry for my red
herrings!

Frank,

I thought about it, and I have no idea why the progress bar works fine for
you and not for me. It hangs for me, which is exactly what I expected to
happen. Maybe some platform difference? I'm on Mac OS 10.6 with PySide
1.2.1.


--
Sean Fisk


On Wed, Dec 11, 2013 at 9:16 AM, Janwillem van Dijk <jwevandijk at xs4all.nl>wrote:

>  The solution Frank proposed yesterday works (after I found out that you
> can get the output using selectedFiles()[0]).
> No problems with the progressbar.
> The actual processing can take a bit long because the exif's of digital
> camera shots are analysed (GExiv2 for photos and exiftool for movies ) and
> than copied to folders as
> /camara_make/year/month/imagetype/yyyymmddhhmmss_fname. When copying more
> than say 50 16MB raw photos the gui becomes blocked. In other apps I indeed
> solved that with threading but, although not elegant, I decided to live
> with that for this one.
> Many thanks for teaching me this extra bit of Python.
> Cheers, Janwillem
>
>
> On 11/12/13 05:45, Sean Fisk wrote:
>
>  Frank,
>
> Your example is a good demonstration of QFileDialog‘s signals. However,
> since the processing runs in the GUI thread, the progress bar is virtually
> useless as the GUI has no time to update it. It starts empty, the
> application hangs, and then it is filled when the processing is done.
>
> Janwillem,
>
> As I see it, if you would like a progress bar, you have three options:
>
>    1. Call QCoreApplication.processEvents()<http://seanfisk.github.io/pyside-docs/pyside/PySide/QtCore/QCoreApplication.html#PySide.QtCore.PySide.QtCore.QCoreApplication.processEvents>during your processing code. This is not always a great idea, and more of a
>    hack than a solution. But it usually works.
>    2. Split your processing into chunks as in this example<http://qt-project.org/wiki/Threads_Events_QObjects#72c9aabadf52900fbf3d4c1ff2b6008c>.
>    However, the code is a bit convoluted and it still runs in the GUI thread.
>    The whole page that contains that example is a great read for asynchronous
>    programming.
>    3. Send your processing to a thread, and dispatch events from the
>    thread indicating the progress.
>
> The first two solutions involve running processing code within the GUI
> thread. If any step of the processing takes longer than a second, then it’s
> probably not a good idea as the user will see the application hang. Here is
> an example implementation of the third solution:
>
> #!/usr/bin/env python
> # Example: Asynchronously process a directory of files with a progress bar.
> import sysimport osimport time
> from PySide import QtCore, QtGui
> class ProcessingThread(QtCore.QThread):
>     # Fired when each file is processed.
>     file_processed = QtCore.Signal(int, str)
>
>     def __init__(self, parent=None):
>         super(ProcessingThread, self).__init__(parent)
>         self.files = []
>
>     def run(self):
>         # Code that's run in the thread.
>         for i, filename in enumerate(self.files):
>             # The actual code for one file goes here. Stubbed out with
>             # time.sleep() for now.
>             time.sleep(0.5)
>             print 'Processed:', filename
>             # Send update to the GUI thread.
>             self.file_processed.emit(i + 1, filename)
> class MyWidget(QtGui.QWidget):
>     def __init__(self, parent=None):
>         super(MyWidget, self).__init__(parent)
>
>         # Setup UI.
>         self._layout = QtGui.QVBoxLayout(self)
>         self._button = QtGui.QPushButton('Open files...')
>         self._progress = QtGui.QProgressBar()
>         self._filelist = QtGui.QPlainTextEdit()
>         self._layout.addWidget(self._button)
>         self._layout.addWidget(self._filelist)
>         self._layout.addWidget(self._progress)
>
>         # Setup events.
>         self._button.clicked.connect(self._button_clicked)
>
>         # Create the thread. Note that this doesn't actually _start_ it.
>         self._thread = ProcessingThread()
>         self._thread.file_processed.connect(self._file_processed)
>
>         # We need to wait for the thread before exiting. Either use this or
>         # don't let the user close the window if processing is happening. See
>         # the next method in this class.
>         QtCore.QCoreApplication.instance().aboutToQuit.connect(
>             self._thread.wait)
>
>     # def closeEvent(self, event):
>     #     # This is an alternative to waiting for the threads. Just don't let
>     #     # the user close the window.
>     #     if self._thread.isRunning():
>     #         QtGui.QMessageBox.critical(
>     #             self, 'Processing',
>     #             'Cannot exit while processing is happening.')
>     #         event.ignore()
>     #     else:
>     #         event.accept()
>
>     def _button_clicked(self):
>         # If we are already running the processing, produce an error.
>         if self._thread.isRunning():
>             QtGui.QMessageBox.critical(
>                 self, 'Processing',
>                 'Can only process one directory at a time.')
>             return
>
>         # Get the directory name from the user.
>         dir_name = QtGui.QFileDialog.getExistingDirectory(
>             parent=self,
>             caption='Choose files...',
>             dir=os.getcwd())
>
>         # Activate the main dialog as it will be deactivated for some reason
>         # after the file dialog closes (at least on my machine).
>         self.activateWindow()
>
>         # Get the list of files in the directory and prime the progress bar.
>         files = os.listdir(dir_name)
>
>         # Set values for progress bar.
>         self._progress.setRange(0, len(files))
>         self._progress.setValue(0)
>
>         # Create and start the thread.
>         self._thread.files = files
>         self._thread.start()
>
>     def _file_processed(self, num_files_processed, filename):
>         # Called for each file that is processed.
>         self._filelist.appendPlainText(filename)
>         self._progress.setValue(num_files_processed)
> if __name__ == '__main__':
>     app = QtGui.QApplication(sys.argv)
>     w = MyWidget()
>     w.show()
>     w.raise_()
>     raise SystemExit(app.exec_())
>
> This is all fine, but it might not solve your original problem of the file
> dialog not closing. On my Mac, the file dialog is gone as soon as the call
> to getExistingDirectory() finishes. However, since I don’t have a
> runnable portion of your code, I can’t really test it. I would recommend
> attempting to run my example to see if it exhibits the same problem as your
> program. Hope this helps!
>
> Cheers,
>
>
>
>  --
> Sean Fisk
>
>
> On Tue, Dec 10, 2013 at 4:43 PM, Frank Rueter | OHUfx <frank at ohufx.com>wrote:
>
>>  Here is an example using signals/slots
>>
>>
>> On 11/12/13 09:56, Janwillem van Dijk wrote:
>>
>>  Here is the snippet: It reads the filenames in a folder and determines
>> new names for photo's based on the exif info.
>>
>> I apreciate that threading might be a solution but the problem seems too
>> simple for that. Can you give an example on how to use the signal concept?
>>
>>
>> self.outFolder = QFileDialog.getExistingDirectory(
>>
>> caption='Destination folder', dir=self.defOutFolder)
>>
>> self.outFiles = []
>>
>> if self.outFolder:
>>
>> self.outFolder = self.outFolder.replace('\\', '/')
>>
>> self.lineEdit_dest.setText(self.outFolder)
>>
>> self.progressBar.setRange(0, self.numFiles)
>>
>> for i, fname in enumerate(self.inFiles):
>>
>> self.progressBar.setValue(i + 1)
>>
>> newpath, newfname = rename_photo(self.inFolder, fname)
>>
>> newpath = path.join(self.outFolder, newpath)
>>
>> self.outFiles.append([fname, newpath, newfname])
>>
>> s = fname + ' --> ' + self.outFolder + '\n'
>>
>> s += path.join(newpath, newfname).replace(self.outFolder, '')
>>
>> self.plainTextEdit_dest.appendPlainText(s)
>>
>>
>>
>> On 10/12/13 21:35, Sean Fisk wrote:
>>
>>  Hi Janwillem,
>>
>> Are you running the “lengthy part that processes a files list” within the
>> GUI thread? If so, you will probably see your GUI hang while this is
>> happening (you won’t be able to click or do anything). In this case, you
>> should consider running the processing in a different thread using
>> QThread<http://seanfisk.github.io/pyside-docs/pyside/PySide/QtCore/QThread.html>or
>> QThreadPool<http://seanfisk.github.io/pyside-docs/pyside/PySide/QtCore/QThreadPool.html>
>> .
>>
>> Can you post the relevant part of the code?
>>
>> Thanks,
>>
>>
>>  --
>> Sean Fisk
>>
>>
>> On Tue, Dec 10, 2013 at 3:17 PM, Janwillem van Dijk <jwevandijk at xs4all.nl
>> > wrote:
>>
>>>  Hi, I have a PySide script that uses
>>> QFileDialog.getExistingDirectory(). After clicking the Open button the
>>> script proceeds with a lengthy part that processes a files list and writes
>>> to a QPlainTextEdit. Unfortunately the QFileDialog widget does only
>>> disappear after this processing is finished, hiding the QPlainTextEdit.
>>>
>>> How can I make that the QFileDialog widget is gone before the processing
>>> starts?
>>>
>>> Cheers, Janwillem
>>>
>>>
>>>
>>>
>>> _______________________________________________
>>> PySide mailing list
>>> PySide at qt-project.org
>>> http://lists.qt-project.org/mailman/listinfo/pyside
>>>
>>>
>>
>>
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/pyside/attachments/20131211/3ba38686/attachment.html>


More information about the PySide mailing list