[PySide] wait for QFileDialog to close

Janwillem van Dijk jwevandijk at xs4all.nl
Wed Dec 11 15:16:27 CET 2013


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  sys
> import  os
> import  time
>
> from  PySideimport  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, filenamein  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 
> <mailto: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 <mailto: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 <mailto: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/1b3fc964/attachment.html>


More information about the PySide mailing list