[PySide] wait for QFileDialog to close

Sean Fisk sean at seanfisk.com
Thu Dec 12 00:37:10 CET 2013


I just tried it on CentOS with PySide 1.2.1. The progress bar does indeed
work. However, I do see some visual glitches: the layout doesn’t readjust
if the window is resized; the button sometimes disappears halfway or fully;
the button can’t be clicked. Throwing in a call to
QtGui.QApplication.processEvents() before time.sleep() helps the situation,
but the response is laggy and sluggish because the GUI is still only able
to update every half-second.

While threads are “evil” in many ways, I do think that for delays of half a
second or greater they are a good solution.


--
Sean Fisk


On Wed, Dec 11, 2013 at 6:16 PM, Frank Rueter | OHUfx <frank at ohufx.com>wrote:

>  Ok, will report back if I stumble across anything interesting. For now I
> will just assume that my example simply shouldn't work :-D
>
>
>
> On 12/12/13 12:07, Sean Fisk wrote:
>
> Frank,
>
>  Understood. I'm going to try your example on CentOS 6.4 with PySide
> 1.2.1 and see what happens. Let me know what happens with future progress
> bars. I'm going to start doing a lot of asynchronous stuff soon, so it
> would be helpful to know.
>
>  Thanks,
>
>
>  --
> Sean Fisk
>
>
> On Wed, Dec 11, 2013 at 4:45 PM, Frank Rueter | OHUfx <frank at ohufx.com>wrote:
>
>>  Hi Sean,
>>
>> I totally agree that it shouldn't work, and I paid little attention to it
>> as I just wanted to provide an example for the signal.
>>
>> I am on Kubuntu 12.10 using PySide 1.2.1.
>> I just ran the code on an OSX box with an old PySide installation (1.0.9)
>> and the progressbar didn't move there.
>>
>> I have a few simple apps to write which will involve proper use of
>> progressbar to indicate background image processing, so I'm sure I will run
>> into it again.
>>
>>
>> Cheers,
>> frank
>>
>>
>>
>> On 12/12/13 09:56, Sean Fisk wrote:
>>
>> 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/4381ab3f/attachment.html>


More information about the PySide mailing list