4

I am trying to create a QFileDialog that allows the user to select multiple directories.

Following the discussion here and the faq here, but I'm not sure what I'm doing wrong. I get a file dialog, but it still only lets me select a single directory (folder).

This is on Windows 7

Code:

class FileDialog(QtGui.QFileDialog):
        def __init__(self, *args):
            QtGui.QFileDialog.__init__(self, *args)
            self.setOption(self.DontUseNativeDialog, True)
            self.setFileMode(self.DirectoryOnly)

            self.tree = self.findChild(QtGui.QTreeView)
            self.tree.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)

            self.list = self.findChild(QtGui.QListView)
            self.list.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)

if __name__ == '__main__':
    import sys
    app = QtGui.QApplication(sys.argv)
    ex = FileDialog()
    ex.show()
    sys.exit(app.exec_())

Edit:

So after playing around with this some more, if I select "Detail View" in the file dialog, multiselection works. However, if I select "List View" it does not work. Any idea why?

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
dan_g
  • 2,712
  • 5
  • 25
  • 44

1 Answers1

8

The example code from the FAQ is not robust, because it assumes the dialog only has one QListView and one QTreeView. The behaviour of findChild is indeterminate when there are several direct child objects: so it was probably just pure luck that it ever worked.

A more robust solution would be to reset the selection mode on any view for which the type of its model is a QFileSystemModel:

for view in self.findChildren((QListView, QTreeView)):
    if isinstance(view.model(), QFileSystemModel):
        view.setSelectionMode(QAbstractItemView.ExtendedSelection)

Here's a simple demo script:

from PyQt5 import QtWidgets

class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.button = QtWidgets.QPushButton('Choose Directories')
        self.button.clicked.connect(self.handleChooseDirectories)
        self.listWidget = QtWidgets.QListWidget()
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.listWidget)
        layout.addWidget(self.button)

    def handleChooseDirectories(self):
        dialog = QtWidgets.QFileDialog(self)
        dialog.setWindowTitle('Choose Directories')
        dialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
        dialog.setFileMode(QtWidgets.QFileDialog.DirectoryOnly)
        for view in dialog.findChildren(
            (QtWidgets.QListView, QtWidgets.QTreeView)):
            if isinstance(view.model(), QtWidgets.QFileSystemModel):
                view.setSelectionMode(
                    QtWidgets.QAbstractItemView.ExtendedSelection)
        if dialog.exec_() == QtWidgets.QDialog.Accepted:
            self.listWidget.clear()
            self.listWidget.addItems(dialog.selectedFiles())
        dialog.deleteLater()

if __name__ == '__main__':

    app = QtWidgets.QApplication(['Test'])
    window = Window()
    window.show()
    app.exec_()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • 1
    Since it took me half an hour to figure out I hope the next novice will be spared the trouble: after combining this answer with the example you can use ex.selectedFiles() after ex.show() to find the directories that were selected. – Frikster Nov 07 '16 at 22:07
  • Sighs, another 2 hours later. How do I stop code from executing following FileDialog until FileDialog has been closed? – Frikster Nov 08 '16 at 00:07
  • 1
    @DirkHaupt. `dialog.exec_()`. – ekhumoro Nov 08 '16 at 01:10
  • I'm glad there's a one liner answer, thank you so much. When using your custom dialog I notice you cant use shift like you can with QFileDialog.getOpenFileNames. Is there also an easy fix for this as well? My user may have to select 20+ folders and it'll be a pain to click each one each time. – Frikster Nov 08 '16 at 22:39
  • 2
    @DirkHaupt. `view.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)`. – ekhumoro Nov 08 '16 at 22:52
  • Just for Pyside2 my result was like this: `for view in self.findChildren(QtWidgets.QListView):` `if isinstance(view.model(), QtWidgets.QFileSystemModel):` `view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)` – mifth Aug 19 '21 at 16:28
  • ekhumoro or anyone else: Could you please consider taking the solutions in the comments and integrating them into the main answer? It' be nice for the next person to be able to see various parts of the solution and perhaps a standalone example too. – Nav Jan 20 '22 at 14:41
  • Thank you for the updated example, ekhumoro. It's excellent! – Nav Jan 21 '22 at 05:49