Andrej Repiský
Andrej Repiský

Reputation: 487

How to make "Browse for folder" dialog?

I'm trying to make a folder selection dialog which looks like this:

enter image description here

The images come from this thread: can the Open File dialog be used to select a Folder?

I tried both QFileDialog::getExistingDirectory() and making an instance of QFileDialog and setting the properties. It just shows the Open File dialog with hidden files.

Upvotes: 2

Views: 5316

Answers (3)

javierbg
javierbg

Reputation: 195

It's 7 years later, but I had the same need, in this case using Python 3.8 and Qt 5.13.2, so I translated Andrej's code for PySide2. I figured this might be useful for someone else having this problem.

Even though it is not a native Windows widget, I think it looks decent on both Windows and Linux. I decided to change the type of the return value of directory() and the initial_path parameter to pathlib.Path for my application.

class DirectorySelectionDialog(QDialog):
    def __init__(self, initial_path: Path, parent: QWidget) -> None:
        super().__init__(parent=parent)

        self.initial_path = initial_path
        self.setMinimumSize(200, 300)
        self.resize(400, 430)
        
        self.model = QFileSystemModel(parent=self)
        self.model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        root_index = self.model.setRootPath(str(self.initial_path))
        
        self.tree_view = QTreeView(parent=self)
        self.tree_view.setModel(self.model)
        self.tree_view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.tree_view.setHeaderHidden(True)
        self.tree_view.setSortingEnabled(True)
        self.tree_view.sortByColumn(0, Qt.AscendingOrder)
        for i in range(1, self.model.columnCount()):
            self.tree_view.setColumnHidden(i, True)
        self.tree_view.scrollTo(root_index)
        self.tree_view.selectionModel().setCurrentIndex(root_index, QItemSelectionModel.Current | QItemSelectionModel.Select)
        self.tree_view.selectionModel().selectionChanged.connect(self.onCurrentChanged)

        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        self.ok_button = button_box.button(QDialogButtonBox.Ok)

        label = QLabel("Folder:")
        self.folder_name = QLineEdit(parent=self)
        self.folder_name.setReadOnly(True)
        self.folder_name.setText(self.initial_path.name)
        path_layout = QHBoxLayout()
        path_layout.addWidget(label)
        path_layout.addSpacing(10)
        path_layout.addWidget(self.folder_name)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.tree_view)
        main_layout.addSpacing(10)
        main_layout.addLayout(path_layout)
        main_layout.addSpacing(10)
        main_layout.addWidget(button_box)
        self.setLayout(main_layout)

    def onCurrentChanged(self):
        file_info = self.model.fileInfo(self.tree_view.selectionModel().currentIndex())
        self.folder_name.setText(file_info.fileName())
        self.ok_button.setEnabled(file_info.isDir())
        self.ok_button.setDefault(file_info.isDir())

    def directory(self) -> Path:
        file_info = self.model.fileInfo(self.tree_view.selectionModel().currentIndex())
        return Path(file_info.absoluteFilePath())

Upvotes: 1

Andrej Repiský
Andrej Repiský

Reputation: 487

It seems that I have to answer myself. I did it purely in Qt in the end, by making the entire dialog:

Header file:

#pragma once

#include <QtWidgets/QDialog>

class QTreeView;
class QFileSystemModel;
class QLineEdit;
class QPushButton;

class CDirSelectionDlg : public QDialog {
    Q_OBJECT

public:
    CDirSelectionDlg(const QString initialPath, QWidget *parent = nullptr);
    QDir directory() const;

private:
    void onCurrentChanged();

    QTreeView *m_treeView;
    QFileSystemModel *m_model;
    QLineEdit *m_folderName;
    QPushButton *m_OKbutton;
    QString m_initialPath;
};

Source file:

#include "DirSelectionDlg.h"

#include <QLabel>
#include <QBoxLayout>
#include <QDialogButtonBox>
#include <QTreeView>
#include <QFileSystemModel>
#include <QPushButton>
#include <QLineEdit>

CDirSelectionDlg::CDirSelectionDlg(const QString initialPath, QWidget *parent) : QDialog(parent), m_initialPath(initialPath)
{
    setMinimumSize(200, 300);
    resize(400, 430);
    m_model = new QFileSystemModel(this);
    auto rootIdx = m_model->setRootPath(m_initialPath);
    m_treeView = new QTreeView(this);
    m_treeView->setModel(m_model);
    m_treeView->setSelectionMode(QAbstractItemView::SingleSelection);
    m_treeView->setHeaderHidden(true);
    m_treeView->setSortingEnabled(true);
    m_treeView->sortByColumn(0, Qt::AscendingOrder);
    for(int i = 1; i < m_model->columnCount(); i ++)    // don't show Size, Type, etc.
        m_treeView->setColumnHidden(i, true);
    m_treeView->scrollTo(rootIdx);
    m_treeView->selectionModel()->setCurrentIndex(rootIdx, QItemSelectionModel::Current | QItemSelectionModel::Select);
    connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &CDirSelectionDlg::onCurrentChanged);

    auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
    connect(buttonBox, &QDialogButtonBox::accepted, this, &CDirSelectionDlg::accept);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &CDirSelectionDlg::reject);
    m_OKbutton = buttonBox->button(QDialogButtonBox::Ok);

    auto label = new QLabel(tr("Folder:"));
    m_folderName = new QLineEdit(this);
    m_folderName->setReadOnly(true);
    m_folderName->setText(QFileInfo(m_initialPath).fileName());
    auto pathLayout = new QHBoxLayout();
    pathLayout->addWidget(label);
    pathLayout->addSpacing(10);
    pathLayout->addWidget(m_folderName);

    auto mainLayout = new QVBoxLayout();
    mainLayout->addWidget(m_treeView);
    mainLayout->addSpacing(10);
    mainLayout->addLayout(pathLayout);
    mainLayout->addSpacing(10);
    mainLayout->addWidget(buttonBox);
    setLayout(mainLayout);
}

void CDirSelectionDlg::onCurrentChanged()
{
    auto fileInfo = m_model->fileInfo(m_treeView->selectionModel()->currentIndex());
    m_folderName->setText(fileInfo.fileName());
    m_OKbutton->setEnabled(fileInfo.isDir());
    m_OKbutton->setDefault(fileInfo.isDir());
}

QDir CDirSelectionDlg::directory() const
{
    return QDir(m_model->fileInfo(m_treeView->selectionModel()->currentIndex()).absoluteFilePath());
}

I'm using Qt 5.3.1.

Upvotes: 1

ezaquarii
ezaquarii

Reputation: 1916

Try this one: QFileDialog::getExistingDirectory()

Both static methods open File selector in directory selection mode. If you don't like the look-n-feel of the opened dialog, you'll need to implement your own. By default Qt tries to open native one if possible, which should work in 99.9% of cases.

Upvotes: 1

Related Questions