目录
1. QFileDialog 文本对话框
与QMessageBox一样的,QFileDialog也继承了QDialog类,直接使用静态成员函数弹窗,弹窗的结果(选择文件的路径)通过函数的返回值获取。
// 获取一个打开或保存文件的路径
// 参数1 父对象
// 参数2 即windowTitle(界面标题)
// 参数3 在哪个路径下打开,默认值为项目的工作路径
// 参数4 设置文件过滤器
// 返回值 选择文件的路径 如果选择失败,返回空字符
QString QFileDialog::getOpenFileName|getSaveFileName(
QWidget * parent = 0,
const QString & caption = QString(),
const QString & dir = QString(),
const QString & filter = QString())
[static]
2. QFileInfo 文件信息类
只需要使用构造函数创建出对象后,通过各种成员函数来直接获取文件信息。
部分函数如下:
// 构造函数
// 参数为文件路径,如果文件非法,仍然可以创建出QFileInfo对象
QFileInfo::QFileInfo(const QString & file)
// 判断文件或文件夹是否存在
// 如果存在返回true 否则返回false
bool QFileInfo::exists() const
// 返回文件大小,单位字节
qint64 QFileInfo::size() const
// 返回基础文件名称
QString QFileInfo::baseName() const
// 返回最后修改的日期和时间
QDateTime QFileInfo::lastModified() const
// 返回可读性
bool QFileInfo::isReadable() const
3. QFile 文件读写类
在Qt中所有IO类都继承自QIODevice类,QIODevice类规定了最基础的IO相关接口,这些接口虽然在不同的派生类中可能实现有所区别,但是调用方式一致。
// 构造函数
// 参数为文件路径,如果是非法路径,也能创建出对象,但是不能IO
QFile::QFile(const QString & name)
// 判断QFile对应的文件是否存在
bool QFile::exists() const
// 打开数据流
// 参数为打开的模式
// 返回值为打开的结果
bool QIODevice::open(OpenMode mode)[virtual]
// 返回是否读到数据尾部
bool QIODevice::atEnd() const [virtual]
// 读取最大长度maxSize个的字节到返回值中
QByteArray QIODevice::read(qint64 maxSize)
// 写出数据
// 参数为写出的内容
// 返回值为实际写出的字节数,错误返回-1
qint64 QIODevice::write(const QByteArray & byteArray)
// 关闭文件流
void QIODevice::close()[virtual]
// 清空缓存区
bool QFileDevice::flush()
// 返回输入流的大小,单位字节
qint64 QIODevice::size() const
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QFileDialog> // 文件对话框
#include <QMessageBox>
#include <QFileInfo> // 文件信息类
#include <QDateTime>
#include <QFile>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
QString readPath;// 读路径
QString writePath; // 写路径
void printFileInfo(); // 打印文件信息
void copy(); // 拷贝
private slots:
void btnsClickedSlot(); // 多对一信号槽
};
#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->pushButtonOpen,SIGNAL(clicked()),
this,SLOT(btnsClickedSlot()));
connect(ui->pushButtonSave,SIGNAL(clicked()),
this,SLOT(btnsClickedSlot()));
connect(ui->pushButtonCopy,SIGNAL(clicked()),
this,SLOT(btnsClickedSlot()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::printFileInfo()
{
// 创建文本信息类对象
QFileInfo info(readPath);
if(!info.exists())
return;
// 获取文件大小
qint64 size = info.size();
QString text = QString::number(size);
text.prepend("文件大小:").append("字节");
ui->textBrowserOpen->append(text);
// 获取基础文件名称
text = info.baseName();
text.prepend("文件名称:");
ui->textBrowserOpen->append(text);
// 获取时间和日期
text = info.lastModified().toString("最后修改日期: yyyy-MM-dd hh:mm:ss");
ui->textBrowserOpen->append(text);
if(info.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);// 只写
// 字节数组
QByteArray buffer;
// 获取文件的总大小
qint64 totalSize = readFile.size();
// 已经读写的大小
qint64 hasRead = 0;
while(!readFile.atEnd())
{
// 读取数据
buffer = readFile.read(1024);
// 写出数据
hasRead += writeFile.write(buffer);
// 算百分比
int per = 100*hasRead/totalSize;
// 设置给进度条
ui->progressBar->setValue(per);
}
// 清空缓存区
writeFile.flush();
// 关闭文件流
readFile.close();
writeFile.close();
// 恢复拷贝按钮
ui->pushButtonCopy->setEnabled(true);
QMessageBox::information(this,"提示","拷贝完成");
}
void Dialog::btnsClickedSlot()
{
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,"提示","请选择要打开的文件!");
return;
}
}else if (ui->pushButtonSave == sender())
{
QString filter = "所有文件(*.*);;Qt(*.cpp *.h *.ui *.pro)";
QString path = QFileDialog::getSaveFileName(this,"打开","E:/",filter);
if(path != "")
{
ui->textBrowserSave->append(path);
writePath = path;
}
else if(writePath == "")
{
QMessageBox::warning(this,"提示","请选择要保存的文件!");
return;
}
}else if(ui->pushButtonCopy == sender())
{
copy();
}
else
{
}
}
4. UI与耗时操作
在默认情况下,Qt项目是单线程的,这个自带的线程用于处理程序的主要任务和UI交互,也被称为主线程或UI线程。
如果在主线程中执行耗时操作(IO或复杂算法)会导致主线程原本的执行操作被阻塞,甚至无法关闭,形成了“假死"现象。
当操作系统发现某个进程无法被正常关闭时,会弹出程序未响应窗口引导用户选择是否强行关闭当前进程。
解决的方法:多线程!
5 QThread线程类
5.1 复现程序未响应
QThread类是Qt的线程类,可以使用以下函数来模拟耗时操作:
// 强制线程睡眠msecs个毫秒
void QThread::msleep(unsigned long msecs)[static]
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QThread>
#include <QDebug>
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();
};
#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->pushButtonSleep,SIGNAL(clicked()),
this,SLOT(btnSleepClickedSlot()));
connect(ui->pushButtonClose,SIGNAL(clicked()),
this,SLOT(close()));
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::btnSleepClickedSlot()
{
qDebug() << "开始睡眠";
QThread::msleep(20000);
qDebug() << "结束睡眠";
}
5.2 创建并启动一个子线程
主线程以外的线程都是子线程,子线程不能执行主线程的ui操作,只能用于耗时操作。
下面是创建并启动一个自定义子线程的步骤。
1. 在Qt Creator中选择项目名称,鼠标右键,点击“添加新文件”。
2. 在弹出的窗口中,先设置类名,然后填写基类名称QObject,最后点击下一步。
3. 在项目管理界面,直接点击完成。可以看到线程类的文件已经创建
4. 选择新创建的头文件,把继承的QObject更改为QThread
5. 选择新建的.cpp文件,把透传构造的QObject改为QThread
6. 在自定义线程类中,覆盖基类QThread类的run函数
// 此函数是子线程执行的起始点,也是子线程的结束点
void QThread::run() [virtual protected]
7. 在run函数中编写子线程要执行的耗时操作
8. 创建自定义线程对象,并调用start函数启动。
// 启动子线程,调用此函数后,会在子线程中执行run函数
// 参数为子线程执行的优先级,默认值为创建时所在线程的优先级
void QThread::start(Priority priority = InheritPriority)
5.3 异步刷新
在实际开发中,主线程和子线程不可能毫无关系的各干各的,最常见的情况是主线程分配一个耗时任务给子线程,子线程需要把耗时任务的执行情况反馈给主线程。主线程刷新子线程耗时操作对应的UI效果。
例如,子线程执行文件拷贝,主线程显示拷贝进度。
通常子线程是主线程对象的子对象,因此异步刷新就是对象的通信问题,使用信号槽解决。
写一个简单的例子,一个伪拷贝案例,使用for循环加休眠,模拟文件拷贝的功能。
进度条到100时,输出提示框(问题:频繁抖动窗口,会出现焦点抢夺问题,导致程序卡死)。ESC可以关闭弹窗,初步解决问题。解决方式:使用hide函数,先隐藏窗口,使窗口失去焦点,放置QMessageBox弹窗抢夺焦点,再显示窗口。
5.4 线程停止
子线程往往执行耗时操作,耗时操作又往往伴随着循环,因此并不建议使用粗暴的方式直接停止线程,因为强行停止线程会导致耗时操作的资源无法回收等问题。
可以通过给循环设置标志位的方式使线程停止。
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include "mythread.h"
#include <QMessageBox>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
private:
Ui::Dialog *ui;
MyThread *ct;
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()
{
if(ui->pushButton->text() == "开始拷贝")
{
// 创建子线程 并启动
ct = new MyThread(this);
connect(ct,SIGNAL(valueSignal(int)),
this,SLOT(valueSlot(int)));
ct->start();
ui->pushButton->setText("停止拷贝");
ct->setRunningState(true);
}else if(ui->pushButton->text() == "停止拷贝")
{
ui->pushButton->setText("开始拷贝");
ct->setRunningState(false);
}
}
void Dialog::valueSlot(int value)
{
ui->progressBar->setValue(value);
if(value == 100)
{
this->hide(); // 隐藏主窗口,只是看不到,并不是关闭了
this->show(); // 显示主窗口
QMessageBox::information(this,"通知","拷贝完成!");
}
}
MyThread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
~MyThread();
bool getRunningState() const;
void setRunningState(bool value);
protected:
void run();
signals:
void valueSignal(int);
private:
bool runningState; // 状态标记
public slots:
};
#endif // MYTHREAD_H
MyThread.cpp
#include "mythread.h"
MyThread::MyThread(QObject *parent) : QThread(parent)
{
}
MyThread::~MyThread()
{
}
void MyThread::run()
{
for(int i = 0 ; i <= 100 && runningState ; i++)
{
QThread::msleep(100);
emit valueSignal(i);
}
}
bool MyThread::getRunningState() const
{
return runningState;
}
void MyThread::setRunningState(bool value)
{
runningState = value;
}
大小单位转换
qreal size = info.size();
QList<QString> unit;
unit<<"字节"<<"KB"<<"MB"<<"GB";
int i=0;
QString text;
while(size/1024>1)
{
size /=1024;
i++;
}
text = QString::number(size,'f',2);
text.prepend("文件大小:").append(unit[i]);
限定信号发射次数
int lastPer = 0; // 上一次的百分比
if(per != lastPer)
{
// 发送进度给主线程
emit valueSignal(per);
lastPer = per;
}