接受单个文件或单个目录的 QFileDialog

Posted

技术标签:

【中文标题】接受单个文件或单个目录的 QFileDialog【英文标题】:QFileDialog that accepts a single file or a single directory 【发布时间】:2014-12-17 07:29:13 【问题描述】:

是否可以显示一个 QFileDialog ,用户可以在其中选择一个文件或一个目录,无论是哪一个?

QFileDialog::getOpenFileName() 只接受文件,而QFileDialog::getExistingDirectory() 只接受目录,但我需要显示一个可以接受文件或目录的文件对话框(这对我的程序有意义)。 QFileDialog::​Options 没有任何希望。

【问题讨论】:

快速搜索给我:qtcentre.org/threads/… @vahancho 谢谢,我实际上需要一个文件或一个目录,对不起,如果我的措辞有点模棱两可。 @vahancho:这似乎是为了多选。 @sashoalm: ***.com/questions/6484793/… 不过不确定是否有帮助.. @JoachimPileborg SetFileMode 不允许合并,它仍然只是文件或目录,但不能两者兼而有之。 dialog.setFileMode(QFileDialog::ExistingFile | QFileDialog::Directory); 不起作用。 【参考方案1】:

QFileDialog 目前不支持这个。我认为这里的主要问题是 FileMode 不是 Q_FLAGS 并且值也不是 2 的幂,所以你不能写这个来解决这个问题。

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

要解决这个问题,你需要相当多的摆弄,例如:

覆盖打开按钮的点击操作。

为文件和目录正确获取“treeview”索引。

我在下面的尝试演示了前者,但我并没有真正解决第二个问题,因为这似乎涉及更多对选择模型的摆弄。

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

构建并运行

qmake && make && ./main

【讨论】:

【参考方案2】:

相当老的问题,但我认为我有一个比大多数懒人更简单的解决方案。 您可以将 QFileDialog 的信号 currentChanged 与动态更改 fileMode 的函数连接。

//header
class my_main_win:public QMainWindow  

private:
    QFileDialog file_dialog;    


//constructor 

my_main_win(QWidget * parent):QMainWindow(parent)

    connect(&file_dialog,QFileDialog::currentChanged,this,[&](const QString & str)
        
        QFileInfo info(str);
        if(info.isFile())
            file_dialog.setFileMode(QFileDialog::ExistingFile);
        else if(info.isDir())
            file_dialog.setFileMode(QFileDialog::Directory);
    );

然后像你一样简单地调用 file_dialog。

if(file_dialog.exec())
    QStringList dir_names=file_dialog.selectedFiles():

【讨论】:

【参考方案3】:

对我有用的是使用:

file_dialog.setProxyModel(nullptr);

按照here 的建议,或

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);

按照here 的建议,或

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;
;

建议here。感谢原作者和我。

【讨论】:

你的第一个提案真的很简洁很好看!【参考方案4】:

连接到 currentChanged 信号,然后将文件模式设置为当前选择的项目(目录或文件)。这是一个 Python3 实现:

class GroupFileObjectDialog(QFileDialog):

    def __init__(self, parent):
        super().__init__(parent)
        self.setOption(QFileDialog.DontUseNativeDialog)
        self.setFileMode(QFileDialog.Directory)
        self.currentChanged.connect(self._selected)

    def _selected(self,name):
        if os.path.isdir(name):
            self.setFileMode(QFileDialog.Directory)
        else:
            self.setFileMode(QFileDialog.ExistingFile)

在 linux / Ubuntu18.04 上运行的 PyQt 5.14 上测试。

【讨论】:

以上是关于接受单个文件或单个目录的 QFileDialog的主要内容,如果未能解决你的问题,请参考以下文章

springMvc接受单个文件,多个文件,多组文件

如何允许单个 Wordpress 站点接受我的 .ttf 文件上传?

单个文件或目录的分代备份,保留扩展名

将 DVD VIDEO_TS 目录转换为单个 mp4 或 mkv 文件?

从GitHub repo DownGit下载单个文件夹或目录

如何创建只接受数字和单个点的 SwiftUI TextField?