七、文件IO
- QFileDialog文件对话框
与QMessageBox一样,QFileDialog也继承了QDialog类,直接使用静态成员函数弹窗,弹窗的结果(选择的文件路径)通过函数返回值返回。
// 获取一个打开或保存的文件路径
// 参数1:父对象
// 参数2:即windwTitle属性(标题)
// 参数3:在那个目录中打开,默认值表示项目的工作目录
// 参数4:文件格式过滤器
// 返回值:选择的文件路径,如果选择失败,返回空字符
QString getOpenFileName(
QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString())
QString QFileDialog::getSaveFileName(
QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString())
[static]
需要注意以下,QFileDialog只是一个窗口类,本身不具备任何IO的能力。
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{
ui->setupUi(this);connect(ui->pushButtonOpen,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));connect(ui->pushButtonSave,SIGNAL(clicked()),this,SLOT(btnClickedSlot()));}Dialog::~Dialog()
{delete ui;
}void Dialog::btnClickedSlot()
{if(ui->pushButtonOpen == sender()){QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.pro *.ui)";QString path = QFileDialog::getOpenFileName(this,"打开","D:/",filter);if(path != ""){
readPath = path;
ui->textBrowserOpen->append(path);}else if(readPath == ""){
QMessageBox::warning(this,"提示","请选择打开的文件");}}else if(ui->pushButtonSave == sender()){QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.pro *.ui)";QString path = QFileDialog::getSaveFileName(this,"保存","D:/",filter);if(path != ""){
writePath = path;
ui->textBrowserSave->append(path);}else if(writePath == ""){
QMessageBox::warning(this,"提示","请选择保存的文件");}}else if(ui->pushButtonCopy == sender()){}
}
2、QFileInfo文件信息类
只需要创建出对象后,通过各种成员函数直接获取文件信息。
// 构造函数
// 参数为文件路径,如果文件非法,仍然可以创建出QFileInfo对象
QFileInfo::QFileInfo(const QString & file)
// 判断文件是否存在
// 如果存在则返回true。否则返回false
bool QFileInfo::exists() const
// 返回基础名称,不包含后缀
QString QFileInfo::baseName() const
// 获取文件大小
// 返回为文件大小
qint64 QFileInfo::size() const
// 返回文件可读性,true可读、false不可读
bool QFileInfo::isReadable() const
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this); connect(ui->pushButtonOpen,SIGNAL(clicked()),this,
SLOT(btnClickedSlot()));
connect(ui->pushButtonSave,SIGNAL(clicked()),this,
SLOT(btnClickedSlot()));}Dialog::~Dialog()
{
delete ui;
}void Dialog::printFileInfo()
{
// 创建文件对象
QFileInfo fileInfo(readPath); // 判断文件是否存在
if(!fileInfo.exists())
{
QMessageBox::warning(this,"提示","文件路径无效");
return;
}
QString text = fileInfo.baseName();
text.prepend("文件名称:");
ui->textBrowserOpen->append(text); qint64 size = fileInfo.size();
text = QString::number(size);
text.prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text); if(fileInfo.isReadable())
{
ui->textBrowserOpen->append("文件可读");
}
else
{
ui->textBrowserOpen->append("文件不可读");
}
}void Dialog::btnClickedSlot()
{
if(ui->pushButtonOpen == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.pro *.ui)";
QString path = QFileDialog::getOpenFileName(this,"打开","D:/",filter);
if(path != "")
{
readPath = path;
ui->textBrowserOpen->append(path);
printFileInfo(); // 文件读取成功后输出信息
}
else if(readPath == "")
{
QMessageBox::warning(this,"提示","请选择打开的文件");
} }
else if(ui->pushButtonSave == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.pro *.ui)";
QString path = QFileDialog::getSaveFileName(this,"保存","D:/",filter);
if(path != "")
{
writePath = path;
ui->textBrowserSave->append(path);
}
else if(writePath == "")
{
QMessageBox::warning(this,"提示","请选择保存的文件");
}
}
else if(ui->pushButtonCopy == sender())
{ }}
3、QFile文件读写类
在Qt中所有IO类都继承自QIODevice类,QIODevice类中规定了最基础的IO相关接口,这些接口虽然在不同的派生类中可能实现有所不同,但调用方式一致。
// 构造函数
// 参数为文件路径,如果是非法路径,也能创建出QFIle对象,但是不能正常IO输入输出操作。
QFile::QFile(const QString & name)
// 判断QFile对应的文件是否存在
bool QFile::exists() const
// 打开数据流
// 参数为打开模式、只读模式、只写模式、读写模式
bool QIODevice::open(OpenMode mode)[virtual]
// 构造函数,QByteArray是qt中的常用数组
// 构造一个空子节数组
QByteArray::QByteArray()
// 是否读取到文件末尾
bool QIODevice::atEnd() const[virtual]
// 读取数据
// 参数:每次读取的大小
// 返回值QByteArray字符数组
QByteArray QIODevice::read(qint64 maxSize)
// 写入数据
// 要写入的数据
// 返回值,本次写入的大小,返回值为-1表示写入失败
qint64 QIODevice::write(const QByteArray & byteArray)
// 清空缓冲区
bool QFileDevice::flush()
// 关闭数据流
void QIODevice::close()[virtual]
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this); connect(ui->pushButtonOpen,SIGNAL(clicked()),this,
SLOT(btnClickedSlot()));
connect(ui->pushButtonSave,SIGNAL(clicked()),this,
SLOT(btnClickedSlot()));
connect(ui->pushButtonCopy,SIGNAL(clicked()),this,
SLOT(btnClickedSlot()));}Dialog::~Dialog()
{
delete ui;
}void Dialog::printFileInfo()
{
// 创建文件对象
QFileInfo fileInfo(readPath); // 判断文件是否存在
if(!fileInfo.exists())
{
QMessageBox::warning(this,"提示","文件路径无效");
return;
}
QString text = fileInfo.baseName();
text.prepend("文件名称:");
ui->textBrowserOpen->append(text); qint64 size = fileInfo.size();
text = QString::number(size);
text.prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text); if(fileInfo.isReadable())
{
ui->textBrowserOpen->append("文件可读");
}
else
{
ui->textBrowserOpen->append("文件不可读");
}
}void Dialog::copy()
{
if(readPath == "")
{
QMessageBox::warning(this,"提示","请选择要读取的文件");
return;
}
if(writePath == "")
{
QMessageBox::warning(this,"提示","请选择要写入的文件");
return;
}
// 正在拷贝时屏蔽拷贝按钮
ui->pushButtonCopy->setEnabled(false); // 创建QFile对象
QFile readFile(readPath);
QFile writeFile(writePath); // 打开文件流
readFile.open(QIODevice::ReadOnly); // 只读模式
writeFile.open(QIODevice::WriteOnly); // 只写模式 // 添加进度条进度
qint64 totalSize = readFile.size(); // 获取文件总大小
qint64 hasRead = 0; // 已经读写的大小 QByteArray array;
while(!readFile.atEnd())
{
array = readFile.read(1024); // 每次读取1kb
qint64 writeRet = writeFile.write(array); // 写入数据,返回值为本次写入的大小
if(writeRet == -1) // 如果写入数据的返回值为-1表示写入失败
{
QMessageBox::critical(this,"错误","文件拷贝失败");
return;
}
// 将写出的数据设置给进度条
hasRead += writeRet; // 获取已经写入总大小
int per = hasRead *100/totalSize; // 计算百分比
ui->progressBar->setValue(per); // 设置给进度条
} // 清空缓存区
writeFile.flush(); // 关闭数据流
readFile.close();
writeFile.close(); // 拷贝完成后解除按钮屏蔽
ui->pushButtonCopy->setEnabled(true);
// 添加拷贝完成提示框
QMessageBox::information(this,"提示","拷贝完成");
}void Dialog::btnClickedSlot()
{
if(ui->pushButtonOpen == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.pro *.ui)";
QString path = QFileDialog::getOpenFileName(this,"打开","D:/",filter);
if(path != "")
{
readPath = path;
ui->textBrowserOpen->append(path);
printFileInfo(); // 文件读取成功后输出信息
}
else if(readPath == "")
{
QMessageBox::warning(this,"提示","请选择打开的文件");
} }
else if(ui->pushButtonSave == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.pro *.ui)";
QString path = QFileDialog::getSaveFileName(this,"保存","D:/",filter);
if(path != "")
{
writePath = path;
ui->textBrowserSave->append(path);
}
else if(writePath == "")
{
QMessageBox::warning(this,"提示","请选择保存的文件");
}
}
else if(ui->pushButtonCopy == sender())
{
copy();
}}
[思考]上面的代码有没有问题?
当拷贝大文件时,会出现卡顿,如果尝试关闭,则触发:
4、UI与耗时操作
在默认情况下,Qt的项目是单线程,这个自带的线程用于处理程序的主要任务和UI交互也被称为主线程或UI线程。
如果在主线程中执行耗时操作(IO或复杂算法)会导致主线程原本执行的操作被阻塞,甚至无法关闭,形成”假死“的现象。
当操作系统发现某个进程无法被正常关闭时,会弹出程序未响应窗口引导用户选择是否强制关闭当前进程。
解决方法是使用多线程。
5、QThread线程类
5.1 复现程序未响应
QThread类是Qt的线程类,
// 强制线程休眠msecs个毫秒
void msleep(unsigned long msecs)
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonSleep,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot()));
connect(ui->pushButtonClose,SIGNAL(clicked()),
this,SLOT(close()));}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnSleepClickedSlot()
{
qDebug() << "开始睡觉" ;
QThread::msleep(15000);
qDebug() << "睡眠结束" ;
}
5.2 创建并启动一个子线程
主线程以外的线程都是子线程,子线程不能执行主线程的UI操作。只能执行耗时操作。
下面是创建并启动一个自定义子线程的步骤:
- 在Qt Creator中选中项目名称,鼠标右键,点击添加新文件。
- 在弹出的窗口中,先设置类名,然后在选择基类名称QObject,最后点击”下一步“。
- 在项目管理界面,直接点击完成,可以看到线程类的文件已经创建。
- 选择新建的头文件,把继承的QObject更改为QThread
- 选择新建的.Cpp文件,把透传构造的QObject更改为QThread
- 在自定义线程中,覆盖基类QThread的run函数。
// 此函数是子线程执行的起始点,也是子线程的结束点。
void QThread::run()[vritual protected]
7、在run函数的函数体中编写子线程要执行的耗时操作。
- 在主线程中创建子线程并启动
// 启动子线程,调用此函数后,会在子线程中自动执行run函数
// 参数欸子线程执行的优先级,默认值为创建所在的线程相同优先级
void QThread::start(Priority priority = InheritPriority)[slot]
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonSleep,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot()));
connect(ui->pushButtonClose,SIGNAL(clicked()),
this,SLOT(close()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnSleepClickedSlot()
{
// 创建子线程对象
MyThread *mt = new MyThread(this);
// 启动子线程
mt->start();
}
5.3 异步刷新
在实际的开发中,两个线程不可能毫无关系的前提下各干各的,最常见的情况是主线程分配一个耗时任务给子线程,子线程需要把耗时任务的执行情况反馈给主线程。主线程刷新子线程耗时的操作,并展示对应的UI效果。
【例子】:子线程执行文件拷贝,主线程显示拷贝的进度。
通常子线程是主线程对象的子对象,因此异步刷新就是对象通信的问题。使用信号槽解决。
咱们写一个简单的例子,一个伪拷贝的案例,使用for循环加睡眠, 模拟文件拷贝的功能。
今晚作业:真正的子线程拷贝文件。
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButton,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot()));
}Dialog::~Dialog()
{
delete ui;
}void Dialog::btnSleepClickedSlot()
{
ui->pushButton->setEnabled(false);
// 创建子线程对象
MyThread *mt = new MyThread(this);
connect(mt,SIGNAL(valueSignal(int)),this,
SLOT(valueSlot(int)));
// 启动子线程
mt->start();
}void Dialog::valueSlot(int value)
{
ui->progressBar->setValue(value);
if(value == 100)
{
ui->pushButton->setEnabled(true);
QMessageBox::warning(this,"提示","拷贝完成");
}
}
问题:频繁抖动窗口,会出现焦点抢夺问题,导致卡死。使用hide函数,来隐藏主窗口。
5.4 线程停止
子线程往往执行耗时操作,耗时操作又往往伴随着循环,因此并不建议使用粗暴的方式直接停止线程,因为强行停止线程会导致耗时操作资源无法回收等问题。
可以通过给循环设置标志位的方式使线程停止。
#include "dialog.h"
#include "ui_dialog.h"Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog)
{
ui->setupUi(this);connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(btnSleepClickedSlot()));
}Dialog::~Dialog()
{delete ui;
}void Dialog::btnSleepClickedSlot()
{if(ui->pushButton->text() == "开始拷贝"){// 创建子线程对象
mt = new MyThread(this);connect(mt,SIGNAL(valueSignal(int)),this,SLOT(valueSlot(int)));// 启动子线程
mt->start();
ui->pushButton->setText("停止拷贝");}else if(ui->pushButton->text() == "停止拷贝"){
ui->pushButton->setText("开始拷贝");
mt->setRunningState(false);}}void Dialog::valueSlot(int value)
{
ui->progressBar->setValue(value);if(value == 100){this->hide(); // 隐藏主窗口this->show(); // 显示主窗口
QMessageBox::warning(this,"提示","拷贝完成");}
}