QFileDialog that accepts a single file or a single

2020-06-12 13:08发布

问题:

Is it possible to show a QFileDialog where the user can select a file or a directory, either one?

QFileDialog::getOpenFileName() accepts only files, while QFileDialog::getExistingDirectory() is directories-only, but I need to show a file dialog that can accept both a file or a directory (it makes sense for my program). QFileDialog::​Options didn't have anything promising.

回答1:

QFileDialog currently does not support this. I think the main problem for you here is that the FileMode is not a Q_FLAGS and the values are not power of 2, either, and so, you cannot write this to solve this issue.

setFileMode(QFileDialog::Directory|QFileDialog::ExistingFiles)

To solve this, you would need quite a bit of fiddling, e.g.:

  • Override the open button click operation.

  • Get the "treeview" indices properly for both files and directories.

My attempt below demonstrates the former, but I did not really go as far as solving the second because that seems to involve some more fiddling with the selection model.

main.cpp

#include <QFileDialog>
#include <QApplication>
#include <QWidget>
#include <QTreeWidget>
#include <QPushButton>
#include <QStringList>
#include <QModelIndex>
#include <QDir>
#include <QDebug>

class FileDialog : public QFileDialog
{
    Q_OBJECT
    public:
        explicit FileDialog(QWidget *parent = Q_NULLPTR)
            : QFileDialog(parent)
        {
            setOption(QFileDialog::DontUseNativeDialog);
            setFileMode(QFileDialog::Directory);
            // setFileMode(QFileDialog::ExistingFiles);
            for (auto *pushButton : findChildren<QPushButton*>()) {
                qDebug() << pushButton->text();
                if (pushButton->text() == "&Open" || pushButton->text() == "&Choose") {
                    openButton = pushButton;
                    break;
                }
            }
            disconnect(openButton, SIGNAL(clicked(bool)));
            connect(openButton, &QPushButton::clicked, this, &FileDialog::openClicked);
            treeView = findChild<QTreeView*>();
        }

        QStringList selected() const
        {
            return selectedFilePaths;
        }

    public slots:
        void openClicked()
        {
            selectedFilePaths.clear();
            qDebug() << treeView->selectionModel()->selection();
            for (const auto& modelIndex : treeView->selectionModel()->selectedIndexes()) {
                qDebug() << modelIndex.column();
                if (modelIndex.column() == 0)
                    selectedFilePaths.append(directory().absolutePath() + modelIndex.data().toString());
            }
            emit filesSelected(selectedFilePaths);
            hide();
            qDebug() << selectedFilePaths;
       }

    private:
        QTreeView *treeView;
        QPushButton *openButton;
        QStringList selectedFilePaths;
};

#include "main.moc"

int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    FileDialog fileDialog;
    fileDialog.show();
    return application.exec();
}

main.pro

TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++11
SOURCES += main.cpp

Build and Run

qmake && make && ./main


回答2:

What worked for me was to use:

file_dialog.setProxyModel(nullptr);

as suggested here, or

class FileFilterProxyModel : public QSortFilterProxyModel
{
protected:
    virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
    {
        QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
        return (fileModel!=NULL && fileModel->isDir(sourceModel()->index(sourceRow, 0, sourceParent))) || QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
    }
};
...
FileFilterProxyModel* proxyModel = new FileFilterProxyModel;
file_dialog.setProxyModel(proxyModel);

as suggested here, or

class FileDialog : public QFileDialog
{
    Q_OBJECT
public:
    explicit FileDialog(QWidget *parent = Q_NULLPTR)
        : QFileDialog(parent)
    {
        m_btnOpen = NULL;
        m_listView = NULL;
        m_treeView = NULL;
        m_selectedFiles.clear();

        this->setOption(QFileDialog::DontUseNativeDialog, true);
        this->setFileMode(QFileDialog::Directory);
        QList<QPushButton*> btns = this->findChildren<QPushButton*>();
        for (int i = 0; i < btns.size(); ++i) {
            QString text = btns[i]->text();
            if (text.toLower().contains("open") || text.toLower().contains("choose"))
            {
                m_btnOpen = btns[i];
                break;
            }
        }

        if (!m_btnOpen) return;

        m_btnOpen->installEventFilter(this);
        //connect(m_btnOpen, SIGNAL(changed()), this, SLOT(btnChanged()))
        m_btnOpen->disconnect(SIGNAL(clicked()));
        connect(m_btnOpen, SIGNAL(clicked()), this, SLOT(chooseClicked()));


        m_listView = findChild<QListView*>("listView");
        if (m_listView)
            m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);

        m_treeView = findChild<QTreeView*>();
        if (m_treeView)
            m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    }

    QStringList selectedFiles()
    {
        return m_selectedFiles;
    }

    bool eventFilter( QObject* watched, QEvent* event )
    {
        QPushButton *btn = qobject_cast<QPushButton*>(watched);
        if (btn)
        {
            if(event->type()==QEvent::EnabledChange) {
                if (!btn->isEnabled())
                    btn->setEnabled(true);
            }
        }

        return QWidget::eventFilter(watched, event);
    }

public slots:
    void chooseClicked()
    {
        QModelIndexList indexList = m_listView->selectionModel()->selectedIndexes();
        foreach (QModelIndex index, indexList)
        {
            if (index.column()== 0)
                m_selectedFiles.append(this->directory().absolutePath() + "/" + index.data().toString());
        }
        QDialog::accept();
    }

private:
    QListView *m_listView;
    QTreeView *m_treeView;
    QPushButton *m_btnOpen;
    QStringList m_selectedFiles;
};

as suggested here. Credits for the original authors and me.