目录
1. QFileDialog 文件选择对话框**
2. QFileInfo 文件信息类**
3. QFile 文件读写类***
4. UI与耗时操作**
5. QThread 线程类
5.1 复现阻塞
5.2 新建并启动子线程
5.3 异步刷新
5.4 停止线程
1. QFileDialog 文件选择对话框**
操作系统会提供一个统一样式的文件选择对话框,QFileDialog类可以直接调用系统自带的文件选择对话框。
QFileDialog继承自QDialog,这样的类直接使用静态成员函数调用。
// 参数1:父对象
// 参数2:标题栏
// 参数3:打开QFileDialog时所在的根目录,默认值为工作目录
// 参数4:文件格式过滤器
// 返回值:文件路径,选择失败返回空字符串
QString QFileDialog::getSaveFileName|getOpenFileName(
QWidget * parent = 0,
const QString & caption = QString(),
const QString & dir = QString(),
const QString & filter = QString()) [static]
2. QFileInfo 文件信息类**
QFileInfo类用于获取文件信息。
相关函数如下:
// 构造函数
// 参数是文件路径,如果路径无效,对象仍然会创建成功
QFileInfo::QFileInfo(const QString & file)
// 判断对应路径下的文件是否存在
bool QFileInfo::exists() const
// 创建时间
QDateTime QFileInfo::created() const
// 上次修改时间
QDateTime QFileInfo::lastModified() const
// 上次读取时间
QDateTime QFileInfo::lastRead() const
// 是否可读
bool QFileInfo::isReadable() const
// 是否可写
bool QFileInfo::isWritable() const
// 获得文件大小,单位字节,如果文件不存在返回0
qint64 QFileInfo::size() const
更多API详见文档。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QFileInfo>
#include <QtWidgets>
#include <QDialog>
#include <QButtonGroup>
#include <QDateTime>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
QButtonGroup * group;
//获得读取的路径、获得写出路径
void getReadPath();
void getWritePath();
QString readPath; //要读取的路径
QString writePath; //要存储的路径
void showFileMessage();
private slots:
//按钮组点击的槽函数
void btnsClickedSlot(int);
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
group = new QButtonGroup(this);
group->addButton(ui->pushButtonOpen,1);
group->addButton(ui->pushButtonSave,2);
group->addButton(ui->pushButtonCopy,3);
connect(group,SIGNAL(buttonClicked(int)),this,SLOT(btnsClickedSlot(int)));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::getReadPath()
{
//过滤器
QString filter = "所有格式(*.*);;C++(*.h *.cpp)";
//四个参数,见笔记
QString path = QFileDialog::getOpenFileName(this,"打开","E:/",filter);
if(path==""&& readPath==""){
//引导用户进行正确的处理
QMessageBox::warning(this,"提示","请选择有效路径!");
return;
}
readPath = path;
//展示
if(path != "")
ui->textBrowserOpen->clear();
ui->textBrowserOpen->append(readPath);
}
void Dialog::getWritePath()
{
//过滤器
QString filter = "所有格式(*.*);;C++(*.h *.cpp)";
//四个参数,见笔记
QString path = QFileDialog::getSaveFileName(this,"打开","E:/",filter);
if(path==""&& writePath==""){
//引导用户进行正确的处理
QMessageBox::warning(this,"提示","请选择有效路径!");
return;
}
writePath = path;
//展示
if(path != "")
ui->textBrowserOpen->clear();
ui->textBrowserOpen->append(writePath);
}
void Dialog::showFileMessage()
{
//创建文件信息类对象
QFileInfo info(readPath);
//获得创建时间
QString time = info.created().toString("yyyy-MM-dd hh:mm:ss");
ui->textBrowserOpen->append(time.prepend("创建时间:"));
//是否可读
bool result = info.isReadable();
if(result)
ui->textBrowserOpen->append("可读");
else
ui->textBrowserOpen->append("不可读");
//获取文件大小
qint64 size = info.size();
QString text;
text.setNum(size).prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text);
}
void Dialog::btnsClickedSlot(int id)
{
if(id==1){
getReadPath();
}else if(id==2){
getWritePath();
}else if(id==3){
}
}
3. QFile 文件读写类***
用于读写文件,需要注意的是QFile间接继承QIODevice类,QIODevice类是Qt所有IO类的基类,QIODevice类中规定了一些最基础的IO接口,本节讲解的这些接口,在后续其它IO类中也可以使用。
QFile的常用函数如下:
// 参数为文件路径
QFile::QFile(const QString & name)
// 判断对应路径下的文件是否存在
bool QFile::exists() const
// 打开数据流
// 参数为打开模式
// 返回值为打开的结果
bool QIODevice::open(OpenMode mode) [virtual]
// 读取的数据流是否位于末端
bool QIODevice::atEnd() const [virtual]
// 读取maxSize个字节到返回值的字节数组对象中
// 注意maxSize表示最大数量,不一定是实际数量
QByteArray QIODevice::read(qint64 maxSize)
// 输出到制定的位置
// 参数是输出的内容
// 返回值是实际的输出量
qint64 QIODevice::write(const QByteArray & byteArray)
// 清空输出流的缓存数据,返回值是结果
bool QFileDevice::flush()
// 关闭数据流
void QIODevice::close() [virtual]
// 获取剩余可读的数据量
qint64 QIODevice::size() const [virtual]
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QFileInfo>
#include <QtWidgets>
#include <QDialog>
#include <QButtonGroup>
#include <QDateTime>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
QButtonGroup * group;
//获得读取的路径、获得写出路径
void getReadPath();
void getWritePath();
QString readPath; //要读取的路径
QString writePath; //要存储的路径
void showFileMessage();
void copy();
private slots:
//按钮组点击的槽函数
void btnsClickedSlot(int);
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
group = new QButtonGroup(this);
group->addButton(ui->pushButtonOpen,1);
group->addButton(ui->pushButtonSave,2);
group->addButton(ui->pushButtonCopy,3);
connect(group,SIGNAL(buttonClicked(int)),
this,SLOT(btnsClickedSlot(int)));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::getReadPath()
{
// 过滤器
QString filter = "所有格式(*.*);;C++(*.h *.cpp)";
// 四个参数,见笔记
QString path = QFileDialog::getOpenFileName(this,"打开","E:/",filter);
if(path=="" && readPath=="")
{
// 引导用户进行正确的处理
QMessageBox::warning(this,"提示","请选择有效路径!");
return;
}
readPath = path;
// 展示
if(path != "")
ui->textBrowserOpen->clear();
ui->textBrowserOpen->append(readPath);
showFileMessage(); // 展示文件信息
}
void Dialog::getWritePath()
{
// 过滤器
QString filter = "所有格式(*.*);;C++(*.h *.cpp)";
// 四个参数,见笔记
QString path = QFileDialog::getSaveFileName(this,"保存","E:/",filter);
if(path=="" && writePath=="")
{
// 引导用户进行正确的处理
QMessageBox::warning(this,"提示","请选择有效路径!");
return;
}
writePath = path;
// 展示
if(path != "")
ui->textBrowserSave->clear();
ui->textBrowserSave->append(writePath);
}
void Dialog::showFileMessage()
{
// 创建文件信息类对象
QFileInfo info(readPath);
// 获得创建时间
QString time = info.created().toString("yyyy-MM-dd hh:mm:ss");
ui->textBrowserOpen->append(time.prepend("创建时间:"));
// 是否可读
bool result = info.isReadable();
if(result)
ui->textBrowserOpen->append("可读");
else
ui->textBrowserOpen->append("不可读");
// 获取文件大小
qint64 size = info.size();
QString text;
text.setNum(size).prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text);
}
void Dialog::copy()
{
// 读写的对象
QFile readFile(readPath);
QFile writeFile(writePath);
if(!readFile.exists())
{
QMessageBox::warning(this,"提示","请选择有效的读写路径!");
return;
}
// 屏蔽拷贝按钮
ui->pushButtonCopy->setEnabled(false);
ui->pushButtonCopy->setText("拷贝中");
// 使用只读模式打开数据流
readFile.open(QIODevice::ReadOnly);
writeFile.open(QIODevice::WriteOnly);
qint64 totalSize = readFile.size(); // 文件总大小
qint64 hasCopied = 0; // 已经读写的数据量
QByteArray buffer; // “小推车”
while(!readFile.atEnd()) // 循环读取
{
buffer = readFile.read(1024); // 一次读取1kb
// 输出
hasCopied += writeFile.write(buffer);
// 计算百分比
int value = hasCopied*100/totalSize;
// 更新UI显示
ui->progressBar->setValue(value);
}
// 收尾
writeFile.flush();
writeFile.close();
readFile.close();
// 屏蔽拷贝按钮
ui->pushButtonCopy->setEnabled(true);
ui->pushButtonCopy->setText("开始拷贝");
QMessageBox::information(this,"通知",writePath.prepend("文件拷贝完成,请查看"));
}
void Dialog::btnsClickedSlot(int id)
{
if(id == 1)
{
getReadPath();
}else if(id == 2)
{
getWritePath();
}else if(id == 3)
{
copy();
}
}
运行结果:
4. UI与耗时操作**
Qt项目在默认的情况下只有一个线程,这个线程负责UI显示与用户的人机交互等程序运行所需的基础操作,因此这个线程也被称为主线程。
但是如果在主线程中执行一些耗时操作(例如IO、复杂算法等),会导致主线程原本的操作阻塞,无法及时响应,因此出现程序假死的现象。
此时强行关闭程序,由于耗时操作正在处理,导致关闭的命令也无法及时执行,操作系统检测到某个应用程序关闭无效,启动操作系统的保护机制,弹出对话框窗口引导用户是否强行关闭。
解决方法是新启动一个线程,让这个线程执行耗时操作,这样的线程被称为子线程。
5. QThread 线程类
5.1 复现阻塞
QThread类是Qt的线程类,其中使用睡眠函数可以非常方便的模仿耗时操作的效果。
// 使当前线程睡眠一段时间
// 参数为睡眠时间,单位毫秒
void QThread::msleep(unsigned long msecs) [static]
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QtWidgets>
//线程类头文件
#include <QThread>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
private slots:
void btnSleepClickedSlot();
void btnBoxClickedSlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonBox,SIGNAL(clicked()),this,SLOT(btnBoxClickedSlot()));
connect(ui->pushButtonSleep,SIGNAL(clicked()),this,SLOT(btnSleepClickedSlot()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::btnSleepClickedSlot()
{
//睡眠10s
QThread::msleep(10000);
}
void Dialog::btnBoxClickedSlot()
{
QMessageBox::information(this,"随便","无所谓");
}
5.2 新建并启动子线程
操作步骤如下所示:
1. 在Qt Creator中,选中项目名称,鼠标右键,点击“添加新文件”。
2. 在弹出的窗口中根据下图所示进行操作。
3. 在弹出的窗口中,依次输入类名(大驼峰)、基类名(QThread),选择类型信息(继承自QObject)后,点击“下一步”。
4. 在项目管理界面点击“完成”。可以在项目中看到已经创建的头文件和源文件。
5. 进入到自定义线程类的头文件,声明run函数。
// 子线程执行的起始点,在子线程对象调用start函数后,此函数自动被调用
// 此函数执行完成后,子线程也执行完成
void QThread::run() [virtual protected]
6. 在自定义线程类的源文件中,定义run函数。
7. 在run函数中添加子线程要执行的耗时操作代码。
8. 在主线程中创建子线程对象,调用start函数后开始子线程开始执行。
程序代码:
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QDebug>
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
protected:
void run(); // 声明run函数 --1
signals:
public slots:
};
#endif // MYTHREAD_H
mythread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
}
// 定义run函数 --2
void MyThread::run()
{
qDebug() << "子线程开始" ;
// 睡眠10s
msleep(10000);
qDebug() << "子线程结束" ; //--3
}
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QtWidgets>
// 子线程头文件
#include "mythread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
private slots:
void btnSleepClickedSlot();
void btnBoxClickedSlot();
};
#endif // DIALOG_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
connect(ui->pushButtonBox,SIGNAL(clicked()),
this,SLOT(btnBoxClickedSlot()));
connect(ui->pushButtonSleep,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::btnSleepClickedSlot()
{
// 创建子线程对象
MyThread *mt = new MyThread(this);
// 开始执行子线程
mt->start(); //--5
}
void Dialog::btnBoxClickedSlot()
{
QMessageBox::information(this,"随便","无所谓");
}
运行结果:
5.3 异步刷新
在开发的过程中,经常遇到下面的场景:
子线程执行耗时操作,主线程显示耗时操作的信息。
此时需要子线程通过信号槽传参发送数据给主线程。需要注意的是,子线程只能执行耗时操作,主线程只能执行非耗时操作。
copythread.h
#ifndef COPYTHREAD_H
#define COPYTHREAD_H
#include <QThread>
class CopyThread : public QThread
{
Q_OBJECT
public:
explicit CopyThread(QObject *parent = 0);
//声明run函数
protected:
void run();
signals:
// 自定义信号
void valueSignal(int);
public slots:
};
#endif // COPYTHREAD_H
copythread.cpp
#include "copythread.h"
CopyThread::CopyThread(QObject *parent) :
QThread(parent)
{
}
//定义run函数
void CopyThread::run()
{
// 模拟文件拷贝的耗时操作
for(int i=0;i<=100;i++)
{
msleep(50); // 模拟拷贝消耗时间
emit valueSignal(i); // 发射进度值
}
}
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
// 自定义线程类
#include "copythread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
private slots:
void btnClickedSlot(); // 按钮点击的槽函数
void valueSlot(int); // 接收子线程信号参数的槽函数
};
#endif // DIALOG_H
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(btnClickedSlot()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::btnClickedSlot()
{
// 创建并启动子线程
CopyThread *ct = new CopyThread(this);
// 连接两个线程之间的信号槽
connect(ct,SIGNAL(valueSignal(int)),
this,SLOT(valueSlot(int)));
ct->start();
}
void Dialog::valueSlot(int value)
{
// 更新进度条
ui->progressBar->setValue(value);
}
ui界面:
运行结果:
5.4 停止线程
介绍两种停止线程运行的方式:
- 调用terminate函数,不推荐使用,因为这种方式是强制停止线程运行,可能导致占用的资源无法释放。
void QThread::terminate() [slot]
- 使用标志位,耗时操作往往伴随着循环,可以给循环体增加一个标志位,通过控制这个标志位的值使循环正常跳出,从而使run函数正常结束。
copythread.h
#ifndef COPYTHREAD_H
#define COPYTHREAD_H
#include <QThread>
class CopyThread : public QThread
{
Q_OBJECT
public:
explicit CopyThread(QObject *parent = 0);
bool isRunning = true; // 是否运行的标志位,先不封装了
protected:
void run();
signals:
// 自定义信号
void valueSignal(int);
public slots:
};
#endif // COPYTHREAD_H
copythread.cpp
#include "copythread.h"
CopyThread::CopyThread(QObject *parent) :
QThread(parent)
{
}
void CopyThread::run()
{
// 模拟文件拷贝的耗时操作
for(int i=0;i<=100 && isRunning;i++)
{
msleep(50); // 模拟拷贝消耗时间
emit valueSignal(i); // 发射进度值
}
}
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
// 自定义线程类
#include "copythread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
CopyThread *ct = NULL;
private slots:
void btnClickedSlot(); // 按钮点击的槽函数
void valueSlot(int); // 接收子线程信号参数的槽函数
void btnFClickedSlot(); // 标志位停止的槽函数
};
#endif // DIALOG_H
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(btnClickedSlot()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::btnClickedSlot()
{
// 创建并启动子线程
ct = new CopyThread(this);
// 连接两个线程之间的信号槽
connect(ct,SIGNAL(valueSignal(int)),
this,SLOT(valueSlot(int)));
// 连接强制停止的槽函数
connect(ui->pushButtonT,SIGNAL(clicked()),
ct,SLOT(terminate()));
// 连接温柔停止的槽函数
connect(ui->pushButtonF,SIGNAL(clicked()),
this,SLOT(btnFClickedSlot()));
ct->start();
}
void Dialog::valueSlot(int value)
{
// 更新进度条
ui->progressBar->setValue(value);
}
void Dialog::btnFClickedSlot()
{
if(ct != NULL)
// 温柔停止
ct->isRunning = false;
}
ui界面:
运行结果: