I have been working my way through Summerfields book on Rapid GUI programming with Python and QT... pyqt to be preciser, but the book from 2007 uses version 4.something 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. This is some tedious-@$$ stuff.
What am I missing?
解决方案
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).