QFileDialog - differences between PyQt4/PyQt5/PySide

2.4k views Asked by At

I have been working my way through Summerfields book on Rapid GUI programming with Python and QT (PyQt to be more precise), but the book from 2007 uses version 4.x and I am trying to get going with the current version (5.4.2).

There are some changes that I am trying to figure out and would love some assistance on how to find stuff. Here is an example for a file save dialog - from the book:

    fname = QFileDialog.getSaveFileName(self,
            "Image Changer - Save Image", fname,
            "Image files ({})".format(" ".join(formats)))

This does not work, perhaps primarily because in PyQt5 the QFileDialog returns a tuple rather than a string. The only way I can figure this out is just by trial and error. The PyQt5 documentation refers you to QT, which I really do not understand.

I got the following to work:

   fname = QFileDialog.getSaveFileName(self, 'some text',
            "whatever.png", '*.png')
   if "." not in fname[0]:
       fname[0] += ".png"
       self.addRecentFile(fname[0])
       self.filename = fname[0]
       return self.fileSave()

Wow, it works! But it is just by slogging through that I get any progress. I tried running python interpreter and typed:

from PyQt5.QtWidgets import  QFileDialog

help(QFileDialog)

This is (sort of) helpful, but the syntax of the help does not make a lot of sense to me, and I do not see what getSaveFileName is supposed to return.

What am I missing?

3

There are 3 answers

0
three_pineapples On

These methods of QFileDialog appear to be a bit special because PyQt have implemented their own methods rather than directly wrapping the Qt methods.

Firstly, the PyQt5 QFileDialog.getSaveFileName() method implements the behaviour of the QFileDialog.getSaveFileNameAndFilter() method from PyQt4 (source). Secondly, the QFileDialog.getSaveFileNameAndFilter() method in PyQt4 returns a tuple of (filename, selectedFilter) (source).

For reference, the calling signature of the PyQt4 QFileDialog.getSaveFileNameAndFilter() method is

getSaveFileNameAndFilter (QWidget parent = None, QString caption = QString(), 
                          QString directory = QString(), QString filter = QString(), 
                          QString initialFilter = QString(), Options options = 0)

Hopefully this helps resolve any confusion. Most PyQt5 classes/methods will not be this confusing to decode!

1
ekhumoro On

Some of the static functions of QFileDialog have an odd history in PyQt. If you don't know this history, it's hard to make sense of the differences between the various versions of PyQt.

The underlying issue is quite simple. In Python, if a function needs to return more than one value, the most common solution is to return a tuple. But in C++, this is not really possible, so the usual solution is to instead provide arguments that can be modified.

The C++ signature of QFileDialog.getSaveFileName is this:

getSaveFileName(
    QWidget * parent = 0, const QString & caption = String(),
    const QString & dir = QString(), const QString & filter = QString(),
    QString * selectedFilter = 0, Options options = 0)

As you can see, the four QString arguments aren't all the same. The first three are const, and so won't be modifed by the function, but the selectedFilter argument takes a pointer to a QString, which means it can be.

Originally, the main use of PyQt was for C++ proto-typing (rather than developing Python applications), and so its APIs were much more faithful to the Qt APIs. This meant that, up until PyQt-4.6, the only way to get the selected filter from QFileDialog, was to do it the C++ way, like this:

>>> s = QString() # string to be modified
>>> f = QFileDialog.getSaveFileName(None, 'Save', '', 'Img(*.png *.jpg)', s)
>>> print s
Img(*.png *.jpg)

And in fact, this still works in current versions of PyQt4 (providing it has QString enabled, of course).

PyQt4 steadily introduced a lot of changes that have gradually made it more and more Python-friendly over the years - but as the above example shows, this was all done without breaking backwards-compatibility. At the time, changing the signature of getSaveFileName to return a tuple would have caused far too much breakage, and so functions like getSaveFileNameAndFilter were added as a temporary compromise instead.

PyQt5 does not have such restrictions (it doesn't even need to provide QString anymore). So it has finally become possible to do the right thing (from a Python point of view) and just return a tuple from getSaveFileName. And this principle now applies in general: if you're using PyQt5, and you see a function in the Qt docs that modifies its arguments, you can always expect a tuple to be returned instead.


(PS: users of PySide - which is much younger than PyQt - have never had to deal with these issues. For them, the static QFileDialog functions have always done the right thing).

0
LPoup On

Solving the "tuple issue" of the getSaveFileName method (With PyQt5 ) can be bypassed using this syntax (adding coma and underscore) :

fname, _ = QFileDialog.getSaveFileName(self,
        "Image Changer - Save Image", fname,
        "Image files ({})".format(" ".join(formats)))