In my Python GUI(PyQt/PySide) application, I am using QFileDialog
to let users to pick ONLY ONE directory so that the application will save multiple files in that directory. This directory can be either exiting or non-existing. If the directory does not exist, my application will create the directory for the users via os.makedirs(dir)
So initially, I had this for opening QFileDialog
:
dir = QFileDialog.getExistingDirectory(self, "Please pick a directory...", path)
This will limit users to pick directory only and not files. However, this won't allow users to pick a non-existing directory. So I had to change it to this:
dir, _filter = QFileDialog.getSaveFileName(self, "Please pick a directory...", path, "Directory", "", QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
This way, I allow users to "save" a new directory. However, this won't allow users to pick an existing directory. (Also, I am not sure if my filters above are correct if I want users to only see directories)
By sticking with the static functions of QFileDialog
, is there a way for me to allow users to pick a directory that can be either existing and non-existing?
If I have to go to the non-static functions path to achieve that, I may need more assistance on that, as I have no experience in working with non-static functions of QFileDialog
. Some sample code would be welcome, as I don't know how to set up a QFileDialog
as close to the native file dialog as possible.
This cannot be achieved with the static functions, as they are helper functions that only provide standard and common behavior.
Since it also is a custom behavior, this can only achieved using a non-native dialog: while some systems may provide a way to "select" non existing directories, this is normally not expected by a directory dialog.
You have to use a QFileDialog subclass and change some of its behavior:
In order to achieve the first, you need access to two widgets of the dialog: the path field and the select button.
Luckily, QFileDialog is constructed using a standard Designer UI, with each widget having its own objectName
, allowing us to access them with findChild()
:
fileNameEdit
;Save
button of a QDialogButtonBox;Then, we connect the textChanged
signal to a custom function that checks if the current text refers to an existing directory or a non existing path; note that QFileDialog already connects that signal to an internal function, allowing an empty path for selection, indicating the currently visible directory, so we only need to do the above check in case the button has been previously disabled.
Finally, we need to override the accept()
function of the dialog. In a similar way, we call the default behavior if no path is entered in the line edit, otherwise we check if it's a directory or the path doesn't exist.
class SelectDirDialog(QFileDialog):
def __init__(self, parent, caption='', path=''):
super().__init__(parent)
# DontUseNativeDialog must be set before anything else
self.setOptions(self.DontUseNativeDialog)
# the following order is important: AcceptSave automatically changes
# the title to "Save as" (or its translation), while we need the
# possible default for the AcceptOpen in Directory mode
self.setFileMode(self.Directory)
title = caption or self.windowTitle()
self.setAcceptMode(self.AcceptSave)
self.setWindowTitle(title)
self.setDirectory(path or QDir.currentPath())
self.fileNameEdit = self.findChild(QLineEdit, 'fileNameEdit')
self.fileNameEdit.textChanged.connect(self.checkOkButton)
self.okButton = self.findChild(QDialogButtonBox).button(
QDialogButtonBox.Save)
def accept(self):
files = self.selectedFiles()
if not files:
super().accept()
return
info = QFileInfo(files[0])
if info.isDir() or not info.exists():
# Important! We cannot call the accept() of QFileDialog,
# otherwise it will use the default behavior that
# doesn't accept non existing directories; call accept()
# on QDialog instead, which is what QFileDialog actually does.
QDialog.accept(self)
def checkOkButton(self):
if self.okButton.isEnabled():
return
info = QFileInfo(self.fileNameEdit.text())
self.okButton.setEnabled(info.isDir() or not info.exists())
def selectedPath(self):
files = self.selectedFiles()
return files[0] if files else ''
...
dlg = SelectDirDialog(self)
if dlg.exec():
print(dlg.selectedPath())
Notes:
ShowDirsOnly
;self.Option.DontUseNativeDialog
, self.FileMode.Directory
, etc.;