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