有没有办法 select PyQt 中的目录并显示其中的文件?

Is there a way to select a directory in PyQt and show the files within?

我希望用户 select 一个包含用于分析的文件的目录。我只想让用户 select 一个目录,而不是其中的文件。我正在使用:

QFileDialog.getExistingDirectory

这仅显示目录(未列出任何文件,即使它们存在)。这可能是正确的行为,但查看其中的文件可能对用户有帮助。理想情况下,其中的文件可见但 selectable

我可以用

QFileDialog.getOpenFileName 

和select他们选择的文件的父目录。这种方法并不完美,因为它们 select 是文件而不是文件目录。

有什么想法或建议吗?

QFileDialog 静态方法不允许这种方法,最重要的是,Qt 总是尝试使用底层操作系统提供的 native 文件对话框,其中有没有可用的 access/control.

解决方案是使用自定义的 QFileDialog 实例,确保它使用本机对话框,并设置相关选项。

由于您还想将任何 non-directory 项显示为已禁用,我们还需要将自定义 代理模型 应用到对话框。请注意,这一步并不是真正需要的,因为使用 setFileMode(dialog.Directory) 已经确保所选项目实际上是一个目录,否则不允许打开当前所选项目。

from PyQt5 import QtCore, QtWidgets

class DirProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, fsModel):
        super().__init__()
        self.fsModel = fsModel
        self.setSourceModel(fsModel)

    def lessThan(self, left, right):
        # QFileSystemModel populates its entries with some delay, which results 
        # in the proxy model not able to do the proper sorting (usually showing 
        # directories first) since the proxy does not always "catch up" with the 
        # source sorting; so, this has to be manually overridden by 
        # force-checking the entry type of the index.
        leftIsDir = self.fsModel.fileInfo(left).isDir()
        if leftIsDir != self.fsModel.fileInfo(right).isDir():
            return leftIsDir
        return super().lessThan(left, right)

    def flags(self, index):
        flags = super().flags(index)
        # map the index to the source and check if it's a directory or not
        if not self.fsModel.fileInfo(self.mapToSource(index)).isDir():
            # if it is a directory, remove the enabled flag
            flags &= ~QtCore.Qt.ItemIsEnabled
        return flags


class Test(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QHBoxLayout(self)
        self.lineEdit = QtWidgets.QLineEdit()
        layout.addWidget(self.lineEdit)
        self.selectBtn = QtWidgets.QToolButton(text='...')
        layout.addWidget(self.selectBtn)
        self.selectBtn.clicked.connect(self.selectDirectory)

    def selectDirectory(self):
        dialog = QtWidgets.QFileDialog(self, windowTitle='Select directory')
        dialog.setDirectory(self.lineEdit.text() or __file__)
        dialog.setFileMode(dialog.Directory)
        dialog.setOptions(dialog.DontUseNativeDialog)

        # find the underlying model and set our own proxy model for it
        for view in self.findChildren(QtWidgets.QAbstractItemView):
            if isinstance(view.model(), QtWidgets.QFileSystemModel):
                proxyModel = DirProxyModel(view.model())
                dialog.setProxyModel(proxyModel)
                break

        # try to hide the file filter combo
        fileTypeCombo = dialog.findChild(QtWidgets.QComboBox, 'fileTypeCombo')
        if fileTypeCombo:
            fileTypeCombo.setVisible(False)
            dialog.setLabelText(dialog.FileType, '')

        if dialog.exec_():
            self.lineEdit.setText(dialog.selectedFiles()[0])

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Test()
    w.show()
    sys.exit(app.exec_())