0、引言
我们知道 C 和 C++ 都提供了文件读写的类库,不过 Qt 也有一套自己的文件读写操作;本文主要介绍 Qt 中进行文件读写操作的类 —— QFile。
1、QFileDialog 文件对话框
一般的桌面应用程序,当我们想要打开一个文件时,通常会弹出一个文件对话框。在 Qt 中,文件对话框使用 QFileDialog 类实现。
QFileDialog 类允许用户遍历文件系统,以便选择一个或多个文件或目录。
最简单的创建 QFileDialog 的方法是使用静态函数:
fileName = QFileDialog::getOpenFileName(this,
tr("Open Image"), "/home", tr("Image Files (*.png *.jpg *.bmp)"));
tr() 是 Qt 中的一个函数,用于将字符串标记为可翻译的字符串。这个函数的作用是在运行时,根据配置在 Qt Linguist 工具中的翻译,将输入字符串翻译成适当的语言。
这意味着,如果您的应用程序支持多种语言,并且在 Qt Linguist 工具中配置了相应的翻译,那么这些字符串将在运行时被翻译成用户选择的语言。
在上面的例子中,我们使用静态方法创建了一个模态 QFileDialog(什么是 模态对话框?)。该对话框初始显示 "/home" 目录中的内容,并显示与字符串 "Image files (*.png *.jpg *.bmp)" 中给出的模式匹配的文件。文件对话框的父窗口设置为 this,窗口标题设置为 "Open Image"。
如果你想要使用多个过滤器,请用 两个分号 分隔每一个过滤器。例如:
"Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)"
如果您想要不使用任何文件过滤器,请直接传递空字符串。或者传递
"All Files (*)"
(了解更多...)
在上面的例子中我们可以尝试使用 QFileDialog 选择一个文件并将 fileName 打印到单行文本框,最终实现效果类似于下图:
2、QFile
QFileDialog 只是帮助我们完成了获取文件的绝对路径(什么是 绝对路径(以 Windows 系统为例)?)的工作;接下来才是要进行文件读写的操作。在 Qt 中,文件读写通常与 QFile 类相关。
💬 有关 QFile 的用法,还请读者参考 QFile 的官方文档,这里笔者不再赘述。下面主要介绍 QFileDialog 类配合 QFile 类的使用案例:
2.1、QFile 读操作
以下代码展示了 QFileDialog 类配合 QFile 类打开并读取一张图片文件(我们在与 QPushButton::clicked 信号关联的槽函数中读取图片文件):
//点击“打开”按钮,弹出文件选择对话框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Image"), "/home" , tr("Image Files (*.png *.jpg *.bmp *.svg)"));
//将路径放到 LineEdit 中
ui->lineEdit->setText(fileName);
//使用文件的绝对路径实例化 QFile
QFile file(fileName);
//只读模式打开文件
file.open(QIODevice::ReadOnly);
//全部读取
QByteArray bytes = file.readAll();
//生成 QImage 实例
QImage image = QImage::fromData(bytes);
//生成 QPixmap 实例并显示在 QLabel 控件上
ui->label->setPixmap(QPixmap::fromImage(image));
//关闭文件
file.close();
});
使用 QFileDialog 配合 QFile,我们可以做到在运行时选择并读取图片文件:
此外,我们还可以尝试使用 QFileDialog 配合 QFile 打开文本文件,只需略微修改上面示例代码中的槽函数即可:
- 在 Linux(Ubuntu) 下,你可以使用如下命令创建文本文件:
touch filename.txt
- 创建完成后,在其中编辑一些字符,保存后就可以使用如下命令查看该文本文件的字符编码格式:
file filename.txt # 如果文本文件中只有英文字符,则编码格式为 ASCII; # 如果带有中文字符,则编码格式为 UTF-8 Unicode。
- 使用如下命令转换文本文件的字符编码格式(参考链接):
sudo iconv -f 'utf-8' -t 'gbk' filename.txt > GbkText.txt
//点击“打开”按钮,弹出文件选择对话框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Text File"), "/home" , tr("Text Files (*.txt)"));
//将路径放到 LineEdit 中
ui->lineEdit->setText(fileName);
//使用文件的绝对路径实例化 QFile
QFile file(fileName);
//只读模式打开文件
file.open(QIODevice::ReadOnly);
//全部读取
QByteArray bytes = file.readAll();
//在 QLabel 文件上显示文本
ui->label->setText(bytes);
//关闭文件
file.close();
});
需要注意的是,QLabel::SetText() 方法接受一个 QString 类型的参数,而在上面的示例代码中我们传递了一个字节数组作为参数。
巧合的是,QString 类提供了一个接受 QByteArray 类型的构造函数,这意味着这里会发生一次隐式转换将 QByteArray 类型转换为 QString 类型。不过,这个构造函数的转换将会使用 UTF-8 格式解码字符串。
这样的隐式转换可能会导致一些问题,特别是当你的文本文件不是 UTF-8 格式编码时:
为了避免这样的问题,我们可以尝试使用一些 Qt 中专门用于编码和解码文本的类,比如 QTextCodec(在 Qt 5.15 过后已经部分弃用)和 QStringConverter 类;或者使用流来读取文件(使用流的时候,默认情况下,QTextStream 假设文件以 UTF-8 编码;但这可以使用QTextStream::setEncoding() 来改变)。
2.2、QFile 写操作
除了读操作,我们还可以使用 QFileDialog 类配合 QFile 类打开并向文件中写入数据。下面是一个打开文本文件并向其中追加新的文本的示例,同样略微修改上面示例代码中的槽函数即可:
//点击“打开”按钮,弹出文件选择对话框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Text File"), "/home" , tr("Text Files (*.txt)"));
//将路径放到 LineEdit 中
ui->lineEdit->setText(fileName);
//使用文件的绝对路径实例化 QFile
QFile file(fileName);
//以追加的模式打开文件
file.open(QIODevice::Append);
//写数据
file.write("这是我写入的文本!\n");
//关闭文件
file.close();
//重新以只读方式打开文件
file.open(QIODevice::ReadOnly);
QByteArray data;
//当没有到达文件末尾时
while(!file.atEnd()){
//按行读取
data += file.readLine();
}
ui->label->setText(data);
//关闭文件
file.close();
});
下面是运行效果:
3、QFileInfo 文件信息
QFileInfo 提供了文件在文件系统中的名称和位置(路径)、访问权限以及是目录还是符号链接等信息,还有文件的大小、最后修改/读取时间等信息。此外,QFileInfo 也可以用来获取关于 Qt 资源的信息。
QFileInfo 可以指向具有相对或绝对文件路径的文件。绝对文件路径以目录分隔符 "/" 开头(或在 Windows 上以驱动器规格开头)。相对文件名以目录名或文件名开头,并指定相对于当前工作目录的路径。绝对路径的一个例子是字符串 "/tmp/quartz"。相对路径可能看起来像 "src/fatlib"。可以使用函数 isRelative() 检查 QFileInfo 使用的是相对文件路径还是绝对文件路径。您可以调用 makeAbsolute() 函数将 QFileInfo 的相对路径转换为绝对路径。
⚠️ 注意:以冒号(:)开头的路径总是被认为是绝对路径,因为它们表示一个 QResource。
QFileInfo 处理的文件是在构造函数中设置的(或者后续使用 setFile() 设置)。使用 exists() 查看文件是否存在并使用 size() 获取其大小。
文件的类型通过 isFile()、isDir() 和 isSymLink() 获得。symLinkTarget() 函数提供了符号链接指向的文件的名称。
在 Unix(包括 macOS 和 iOS)上,该类中的属性获取器函数返回目标文件的时间和大小等属性,而不是符号链接的时间和大小等属性,这是因为 Unix 透明地处理符号链接。使用 QFile 打开符号链接可以有效地打开链接的目标。例如:
#ifdef Q_OS_UNIX
QFileInfo info1("/home/bob/bin/untabify");
info1.isSymLink(); // returns true
info1.absoluteFilePath(); // returns "/home/bob/bin/untabify"
info1.size(); // returns 56201
info1.symLinkTarget(); // returns "/opt/pretty++/bin/untabify"
QFileInfo info2(info1.symLinkTarget());
info2.isSymLink(); // returns false
info2.absoluteFilePath(); // returns "/opt/pretty++/bin/untabify"
info2.size(); // returns 56201
#endif
在 Windows 上,快捷方式(.lnk 文件)目前被视为符号链接。与 Unix 系统一样,属性获取器返回目标文件的大小,而不是 .lnk 文件本身。此行为已被弃用,可能会在 Qt 的未来版本中删除,之后 .lnk 文件将被视为常规文件。
#ifdef Q_OS_WIN
QFileInfo info1("C:\\Users\\Bob\\untabify.lnk");
info1.isSymLink(); // returns true
info1.absoluteFilePath(); // returns "C:/Users/Bob/untabify.lnk"
info1.size(); // returns 63942
info1.symLinkTarget(); // returns "C:/Pretty++/untabify"
QFileInfo info2(info1.symLinkTarget());
info2.isSymLink(); // returns false
info2.absoluteFilePath(); // returns "C:/Pretty++/untabify"
info2.size(); // returns 63942
#endif
文件名称的元素可以使用 path() 和 fileName() 提取。fileName() 的部分可以用 baseName()、suffix() 或 completeSuffix() 提取。QFileInfo 对象到由 Qt 类创建的目录将不会有尾随文件分隔符。如果您希望在自己的文件信息对象中使用尾随分隔符,只需在构造函数或 setFile() 给出的文件名后附加一个。
文件的日期由 birthTime()、lastModified()、lastRead() 和 fileTime() 返回。通过 isReadable()、isWritable() 和 isExecutable() 获取文件的访问权限信息。文件的所有权可以从 owner()、ownerId()、group() 和 groupId() 中获得。您可以使用 permission() 函数在单个语句中检查文件的权限和所有权。
⚠️ 注意:在 NTFS 文件系统上,出于性能原因,默认情况下禁用所有权和权限检查。要启用它,包括以下行:
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
然后通过对 qt_ntfs_permission_lookup 加 1 和减 1 来打开和关闭权限检查。
qt_ntfs_permission_lookup++; //启用检查
qt_ntfs_permission_lookup--; //再次关闭检查
⚠️ 注意:由于这是一个非原子全局变量,所以只有在除主线程以外的任何线程启动之前或除主线程以外的每个线程结束之后,才可以对 qt_ntfs_permission_lookup 进行递增或递减操作。
下面是一个 QFileDialog 类配合 QFileInfo 类获取文件信息的代码示例,同样略微修改 2.1 小节示例代码中的槽函数即可:
//点击“打开”按钮,弹出文件选择对话框
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open File"), "/home", tr("All Files (*)"));
//将路径放到 LineEdit 中
ui->lineEdit->setText(fileName);
QFileInfo info(fileName);
QString str = "path() = " + info.path() + "\n"
+ "filePath() = " + info.filePath() + "\n"
+ "fileName() = " + info.fileName() + "\n"
+ "baseName() = " + info.baseName() + "\n"
+ "suffix() = " + info.suffix() + "\n"
+ "completeSuffix() = " + info.completeSuffix() + "\n"
+ "birthTime() = " + info.birthTime().toString(); //toString()方法可指定日期输出格式
ui->label->setText(str);
});
💬 在打印文件的日期信息时,可以指定输出的格式,比如:
info.birthTime().toString("yyyy/MM/dd hh:mm:ss"); // 2023/08/16 21:12:34
(参考 QDateTime 的 toString() 方法了解更多)
运行效果: