首页 Qt 学习之路 2 Qt 学习之路 2(44):QFileSystemModel

Qt 学习之路 2(44):QFileSystemModel

40 10

上一章我们详细了解了QStringListModel。本章我们将再来介绍另外一个内置模型:QFileSystemModel。看起来,QFileSystemModelQStringListModel要复杂得多;事实也是如此。但是,虽然功能强大,QFileSystemModel的使用还是简单的。

让我们从 Qt 内置的模型说起。实际上,Qt 内置了两种模型:QStandardItemModelQFileSystemModelQStandardItemModel是一种多用途的模型,能够让列表、表格、树等视图显示不同的数据结构。这种模型会将数据保存起来。试想一下,列表和表格所要求的数据结构肯定是不一样的:前者是一维的,后者是二维的。因此,模型需要保存有实际数据,当视图是列表时,以一维的形式提供数据;当视图是表格时,以二维的形式提供数据。QFileSystemModel则是另外一种方式。它的作用是维护一个目录的信息。因此,它不需要保存数据本身,而是保存这些在本地文件系统中的实际数据的一个索引。我们可以利用QFileSystemModel显示文件系统的信息、甚至通过模型来修改文件系统。

QTreeView是最适合应用QFileSystemModel的视图。下面我们看一段代码:

FileSystemWidget::FileSystemWidget(QWidget *parent) :
    QWidget(parent)
{
    model = new QFileSystemModel;
    model->setRootPath(QDir::currentPath());

    treeView = new QTreeView(this);
    treeView->setModel(model);
    treeView->setRootIndex(model->index(QDir::currentPath()));

    QPushButton *mkdirButton = new QPushButton(tr("Make Directory..."), this);
    QPushButton *rmButton = new QPushButton(tr("Remove"), this);
    QHBoxLayout *buttonLayout = new QHBoxLayout;
    buttonLayout->addWidget(mkdirButton);
    buttonLayout->addWidget(rmButton);

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(treeView);
    layout->addLayout(buttonLayout);

    setLayout(layout);
    setWindowTitle("File System Model");

    connect(mkdirButton, SIGNAL(clicked()),
            this, SLOT(mkdir()));
    connect(rmButton, SIGNAL(clicked()),
            this, SLOT(rm()));
}

构造函数很简单,我们首先创建了QFileSystemModel实例,然后将其作为一个QTreeView的模型。注意我们将QFileSystemModel的根目录路径设置为当前目录。剩下来的都很简单,我们添加了按钮之类,这些都不再赘述。对于 treeView 视图,我们使用了setRootIndex()对模型进行过滤。我们可以尝试一下,去掉这一句的话,我们的程序会显示整个文件系统的目录;而这一句的作用是,从模型中找到 QDir::currentPath()所对应的索引,然后显示这一位置。也就是说,这一语句的作用实际是设置显示哪个目录。我们会在后面的章节中详细讨论index()函数。

现在,我们可以运行以下程序看看界面:

QFileSystemModel 示例

虽然我们基本一行代码都没写(有关文件系统的代码都没有写),但是从运行截图可以看出,QFileSystemModel完全将所能想到的东西——名称、大小、类型、修改时间等全部显示出来,可见其强大之处。

下面是mkdir()槽函数:

void FileSystemWidget::mkdir()
{
    QModelIndex index = treeView->currentIndex();
    if (!index.isValid()) {
        return;
    }
    QString dirName = QInputDialog::getText(this,
                                            tr("Create Directory"),
                                            tr("Directory name"));
    if (!dirName.isEmpty()) {
        if (!model->mkdir(index, dirName).isValid()) {
            QMessageBox::information(this,
                                     tr("Create Directory"),
                                     tr("Failed to create the directory"));
        }
    }
}

正如代码所示,首先我们获取选择的目录。后面这个isValid()判断很重要,因为默认情况下是没有目录被选择的,此时路径是非法的,为了避免程序出现异常,必须要有这一步判断。然后弹出对话框询问新的文件夹名字,如果创建失败会有提示,否则就是创建成功。这时候你会发现,硬盘的实际位置的确创建了新的文件夹。

下面则是rm()槽函数:

void FileSystemWidget::rm()
{
    QModelIndex index = treeView->currentIndex();
    if (!index.isValid()) {
        return;
    }
    bool ok;
    if (model->fileInfo(index).isDir()) {
        ok = model->rmdir(index);
    } else {
        ok = model->remove(index);
    }
    if (!ok) {
        QMessageBox::information(this,
                         tr("Remove"),
                         tr("Failed to remove %1").arg(model->fileName(index)));
    }
}

这里同样需要先检测路径是否合法。另外需要注意的是,目录和文件的删除不是一个函数,需要调用isDir()函数检测。这一步在代码中有很清楚的描述,这里就不再赘述了。

实际上,我们这里不需要十分担心QFileSystemModel的性能问题,因为它会启动自己的线程进行文件夹扫描,不会发生因扫描文件夹而导致的主线程阻塞的现象。另外需要注意的是,QFileSystemModel会对模型的结果进行缓存,如果你要立即刷新结果,需要通知QFileSystemWatcher类。

如果仔细查看就会发现,我们的视图不能排序不能点击列头。为此,我们可以使用下面代码:

treeView->header()->setStretchLastSection(true);
treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
treeView->header()->setSortIndicatorShown(true);
#if QT_VERSION >= 0x050000
    treeView->header()->setSectionsClickable(true);
#else
    treeView->header()->setClickable(true);
#endif

这是 Qt 中视图类常用的一种技术:如果我们要修改有关列头、行头之类的位置,我们需要从视图类获取到列头对象,然后对其进行设置。正如代码中所显示的那样。注意上面代码片段的最后一部分,我们使用一个条件判断来确定 Qt4 与 Qt5 的不同。

现在我们简单介绍了有关两个常用的模型类:QStringListModel 和 QFileSystemModel。下一章,我们将在此基础上详细介绍模型的相关细节。

40 评论

guoming0000 2013年2月22日 - 10:47

问下博主,你插入代码用的是什么插件啊?还有新主题的留言板块乱飘了~

回复
豆子 2013年2月22日 - 11:32

现在主题升级之后有点问题,不好意思哦~
代码高亮使用的是 wp-syntax,自己修改添加表头的显示。

回复
guoming0000 2013年2月22日 - 12:42

恩,谢谢啦,呵呵。都会好的~

回复
豆子 2013年2月22日 - 15:47

谢谢哦~现在弄好了~嘿嘿

回复
guoming0000 2013年2月22日 - 15:49

厉害~有时间我也把自己的网站弄成wordpress的。

回复
realymylove 2013年11月27日 - 10:51

编译时发现在Qt5.1中header()->setClickable()函数被替换为header->setSectionsClickable()。请楼主核实。

回复
豆子 2013年11月27日 - 15:27

的确是这样的,感谢指出!

回复
Aria 2014年4月23日 - 21:43

请问博主,能否在不调用编辑区控件的情况下用QT编写一个类似记事本的程序?
就是自己来写编辑区的查找,添加,删除等功能。。。求解答......

回复
豆子 2014年4月25日 - 09:17

话说这个问题已经有人问过,不清楚你们要求怎样。不过个人感觉你可以用 QTextEdit,自己实现一个类似 QTextDocument 的类应该符合要求吧

回复
muggle111 2014年9月30日 - 23:11

博主,请教一个关于目录选择的问题,可能很多人都碰到过,但是都没有一个很好的解决方案。比如这个方案其实不错:
http://blog.csdn.net/Rex237/article/details/5873492
但是它有一个,也是唯一的一个巨大缺点:界面被卡死。
所以最好还是改用CFileSystemModel,但是一来不知道怎么写,二来仍留有一个问题,就像楼下有朋友提出的问题:当使用QFileSystemModel时,由于它是异步的,在没有展开子目录的情况下,选择父目录,然后再展开,发现子目录都没有选中。界面不同步。请问该怎么解决这个问题?
博主可否帮忙解决这个问题,也许可以作为本章的深入细节?万分感谢啊。

回复
豆子 2014年10月2日 - 20:16

感谢提出这个问题,因为用这个不多,所以一直没有发现这个问题。等有时间仔细研究一下!

回复
乌合之众 2016年5月18日 - 23:40

在setData的时候,先使用QFileSystemModel::isDir(Index)判断是不是目录,如果是的话,调用 QTreeView::expand(Index); 展开目录,再递归勾选。

回复
HelloWorld 2015年10月6日 - 14:07

豆子兄你好,十分感谢您的这一系列的文章。本人小白有一个问题想请教您,QFileSystemModel 中的表头要怎样才能自由修改呢? 比如要改成中文?我使用了国际化,可以达到这样的目的。但是我用QFileSystemModel中的setHeaderData这一方法,却不能修改表头。难道要重写这一个方法吗?如果要重写,豆子兄能举一个简单的例子吗? 万分感谢!

回复
豆子 2015年10月9日 - 10:27

标准对话框的国际化 Qt 已经做好了,只需要在 main() 里面添加 translator 就可以了

回复
HelloWorld 2015年10月9日 - 12:48

谢谢豆子兄的回复,可是国际化翻译之后不太标准。比如QFileSystemModel最后一项 “修改日期” 会被翻译成为 “日期被修改” 如果想更自由地调整表头,应该怎么办呢?

回复
攻城师 2016年3月30日 - 09:34

Qt4的安装目录有qt_zh_CH.ts文件,自己重新改一下翻译就行,不过在Qt5中没找到这个。但Qt4的也能在Qt5中用,只是文件类型下面的File Folder找不到翻译,握了棵草。

回复
谢启凯 2016年1月17日 - 16:47

豆子老师您好,我加了最后那一段程序之后,可以点击列头,也显示SortIndicator,但是无论点击哪个列头,显示的文件顺序都没有变化(我自己添加了几个文件,按理说排序一定是会变化的),我用的是Qt 5.5.1(mingw),win10系统,您清楚可能会是什么原因吗?非常感谢!

回复
豆子 2016年1月18日 - 00:01

这个我也不是很清楚,应该看看代码才会知道

回复
谢启凯 2016年1月18日 - 08:50

代码都是直接copy的,感觉不应该有问题。。我再看看吧。多谢回复~

回复
谢启凯 2016年1月18日 - 08:55

刚才查了下,我电脑上加上这一句就可以了:
treeView->setSortingEnabled(true);

回复
vivo 2016年3月1日 - 12:11

谢谢这兄弟,确实需要有这句

回复
Edward 2016年7月8日 - 14:35

我加了这个setSortignEnabled(true)仍然没有用,箭头有显示可以点击,但是不会重新排序,而且更奇怪的是,我直接手动设置成DescendingOrder反向排序也没用,还是正向从A到Z的排序。。。

回复
PSY 2016年9月20日 - 11:12

根據我的測試,豆子老師的這一段不用加:

treeView->header()->setStretchLastSection(true);
treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
treeView->header()->setSortIndicatorShown(true);
#if QT_VERSION >= 0x050000
treeView->header()->setSectionsClickable(true);
#else
treeView->header()->setClickable(true);
#endif

只要加上 treeView->setSortingEnabled(true);
就可以選擇行頭排序,不過好像要一開始就在純文件的資料夾下面,如果有其他資料夾的話,好像沒有辦法,因此我有保留下面的程式碼,預設會在QtCreator 的build 資料夾,裡面都是純文件,就可以sort了。
treeView->setRootIndex(model->index(QDir::currentPath()));

ben 2016年8月16日 - 17:08

博主,为什么你的例子treeview的宽度与对话框的宽度一致,而我的例子treeview宽度太小,会出现滚动条呢?

回复
ben 2016年8月16日 - 17:10

奇怪,贴代码提交后不会显示回复,就像没提交一样。

回复
豆子 2016年8月17日 - 08:57

是不是没有设置 layout?

回复
ben 2016年8月17日 - 17:27

有设置layout,跟你的代码一样的。

我的FileSystemWidget是继承QWidget,然后在main.cpp 中定义一个QDialog指针做为FileSystemWidget变量的初始化参数,这样对吗?

class FileSystemWidget: public QWidget
{
.....
}

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QDialog * parent = new QDialog;
parent->setFixedSize(365,240);
FileSystemWidget myv(parent);
parent->show();

return app.exec();
}

回复
豆子 2016年8月18日 - 10:06

layout 是设置在 FileSystemWidget 内部的。你在 FileSystemWidget 添加到 dialog 的时候没有添加 layout,所以并不会充满整个 dialog。

回复
PSY 2016年9月20日 - 11:19

根據我的測試,豆子老師的這一段不用加:

treeView->header()->setStretchLastSection(true);
treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
treeView->header()->setSortIndicatorShown(true);
#if QT_VERSION >= 0x050000
treeView->header()->setSectionsClickable(true);
#else
treeView->header()->setClickable(true);
#endif

只要加上 treeView->setSortingEnabled(true);
並且設定 model 和 view 的 根目錄都是在相同位置:
EX:
model->setRootPath("/");
treeView->setRootIndex(model->index("/"));

就可以根據行頭排序

回复
zz 2019年8月13日 - 09:54

是的
如果这样设定需要增加headerview头文件

回复
yuhai 2018年6月20日 - 18:47

请问,能在里面直接新建文件吗?

回复
豆子 2018年6月21日 - 21:41

QFileSystemModel 可以直接创建、删除文件夹,但是没有提供直接新建文件的 API

回复
myf 2018年8月27日 - 10:24

我试了豆子老师的代码,发现无法在当前目录新建文件夹。后来将mkdir()函数里的QModelIndex index = treeView->currentIndex();代码改为QModelIndex index = treeView->currentIndex().parent();就可以了

回复
wangxiaoli 2019年6月21日 - 14:45

豆子老师,我在VS里使用QFileSystemModel和QTreeView绘制目录树,运行UI一段时间后就会出现内存错误提示。我测试代码基本确定就是目录树导致的问题,这里是VS使用QT的Model和View需要进行某些设置吗?

回复
annnn 2020年6月28日 - 18:11

为什么在Qt运行可以看到目录,在系统文件管理看不到

回复
豆子 2020年7月27日 - 22:17

这个不清楚哪些目录看不到啊?会不会是运行时的临时目录?

回复
leeprince 2020年9月26日 - 15:25

豆子老师你好,现在我也在学习用Qt,有一个问题:由于QFileSystemModel是异步载入的,使用rowcount获取文件及文件数目点击一次会显示为0,因为QFileSystemModel载入之前rowCount( )总是返回0,我要如何才能获取文件数目呢

回复
豆子 2020年9月29日 - 21:42

QFileSystemModel 在目录加载完毕之后会发出 directoryLoaded() 信号,连接这个信号就可以在对应的 slot 里面通过 rowCount() 获取文件数目了

回复
lwei2 2021年2月7日 - 16:04

楼主,请问在QTreeView自定义表头后,怎么将QFileSystemModel或QDirModel对应的元素插入到相应的列下面呢?

回复
2021年2月20日 - 00:36

豆子老师居然在2020年还在回复...豆子老师新年快乐

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2