QT学习第七课

目录

1. QFileDialog 文本对话框

2. QFileInfo 文件信息类

3. QFile 文件读写类

4. UI与耗时操作

5 QThread线程类

5.1 复现程序未响应

5.2 创建并启动一个子线程

5.3 异步刷新

5.4 线程停止


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;
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值