Pop up dialog to save file in seperate thread

1k Views Asked by At

I have a function that gets called in a separate thread from the main one when a button gets clicked and it calls the QFileDialog::getSaveFileName() function to get a file handle to the file the user saved, but I can't do that on a separate thread because it modifies the GUI and you're not allowed to do that.

How can I get around this?

2

There are 2 best solutions below

0
On

QFileDialog::getSaveFileName() returns file name, it does not try to open the file nor return the file handle.

It's not clear from your question, but I assume that your button starts the thread to execute some lengthy task and to store the results to the file. So call QFileDialog::getSaveFileName() right there in the button click slot, obtain the file name and provide that name to the thread. Your thread will just read that file name, so probably there is no need for synchronization. And then, you just open the file with provided file name in that non-GUI thread.

2
On

In addition to comments regarding design and reading file names from GUI thread before creating the other one and passing handles as arguments, I understand that may be some scenarios where you need to invoke GUI dialogs from other threads.

One solution can be to emit a signal from your thread and capture in the GUI's. The drawback of this approach is that it may be difficult to get a result (the name of a file in your case).

A similar solution is to use a Qt::BlockingQueuedConnection to invoke a method of an object living in the GUI thread while blocking the other thread until the method returns.

Next example illustrates this using a helper object:

class FileDialogCaller : public QObject {
  Q_OBJECT

public:
  FileDialogCaller(QObject* parent = 0) : QObject(parent) {
    // The helper object will live in the GUI thread
    moveToThread(qApp->thread());
  }

  // Add the rest of parameters as needed
  QString getSaveFileName(QWidget* parent, const QString& caption, const QString& dir,
                          const QString& filter) {
    QString fileName;

    if (QThread::currentThread() != qApp->thread()) { // no GUI thread
      QMetaObject::invokeMethod(this, "getSaveFileName_", Qt::BlockingQueuedConnection,
                                Q_RETURN_ARG(QString, fileName),
                                Q_ARG(QWidget*, parent),
                                Q_ARG(QString, caption),
                                Q_ARG(QString, dir),
                                Q_ARG(QString, filter));
    } else { // in GUI thread, direct call
      fileName = getSaveFileName_(parent, caption, dir, filter);
    }

    return fileName;
  }

private:
  Q_INVOKABLE
  QString getSaveFileName_(QWidget* parent, const QString& caption, const QString& dir,
                          const QString& filter) {
    return QFileDialog::getSaveFileName(parent, caption, dir, filter);
  }
};

To use it just:

QString fileName = FileDialogCaller().getSaveFileName(nullptr, "Save", "", "Any (*.*)");