窗口上放置一个按钮和一个进度条部件,点击按钮,进行拷贝操作 —— 打开对话框选择源文件,然后再打开一个对话框 选择 目标文件存放位置和名称。拷贝过程中进度条显示当前进度(大文件)。不允许用QFile 的 copy 方法 —— 把源文件分解开,然后一帧一帧写入目标文件。
#ifndef WIDGET_H
#define WIDGET_H
#include <QtWidgets>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_released();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#define KB 1024
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->progressBar->setValue(0);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_released()
{
// 1.选定源文件
QString srcName = QFileDialog::getOpenFileName(this, tr("选择文件"), \
"D:/Packages_", tr("all (*.*)"));
if (srcName.isEmpty())
return ;
qDebug() << "src: " << srcName;
// 2.选定目标文件
QString destName = QFileDialog::getSaveFileName(this, tr("保存文件"), \
"e:", tr("all (*.*)"));
if (destName.isEmpty())
return ;
qDebug() << "dest: " << destName;
QFile srcFile(srcName);
if (!srcFile.open(QIODevice::ReadOnly))
{
qDebug() << "打开文件失败";
return ;
}
QFile destFile(destName);
if (!destFile.open(QIODevice::ReadWrite | QIODevice::Truncate))
{
qDebug() << "复制文件失败";
return ;
}
ui->progressBar->setValue(0); // 确保每次点击按钮时,进度条都是从 0 开始
qint64 totalSize = srcFile.size();
qint64 currentSize = 0;
QByteArray buffer;
// 3.开始拷贝--不断读取,然后写入
while (!srcFile.atEnd())
{
buffer.clear();
buffer = srcFile.read(KB);
destFile.write(buffer);
// 4.更新进度值
currentSize += buffer.size();
ui->progressBar->setValue(100 * currentSize / totalSize);
}
}
运行结果如下:(复制文件时,拖动窗口会产生卡顿)
QT 的主线程称为 GUI 线程,负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。
这对于开发图形界面程序尤其重要,当一个操作耗时很长时(比如大批量 I/O 或大量矩阵变换等CPU密集操作),整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而不会影响到 主 GUI 线程,从而避免上述问题。
当线程数不大于CPU核数时,操作系统可以调度不同的线程运行于不同的CPU核上。
一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于程序的理解和维护。
创建一个界面,界面 show 以后调用睡眠函数,观察效果。
Public Functions:
QThread(QObject *parent = 0); // 构造函数 // pthread_create
bool isFinished() const; // 判断线程是否退出
bool wait(unsigned long time = ULONG_MAX); // pthread_join(&id)
// 等待某个线程结束,最多等待time ms,如果时间没有设置,那么永远等待。
Public Slots:
void start(Priority priority = InheritPriority) // 启动线程必须使用start
void terminate(); // 杀死线程 // pthread_cancel
Static Public Members:
Qt::HANDLE currentThreadId() [static] // 得到当前执行者线程ID,可以直接qDebug
void sleep(unsigned long secs) [static]
void msleep(unsigned long msecs) [static]
void usleep(unsigned long usecs) [static]
睡眠函数不能在主线程调用,会造成界面卡死。
Protected Functions:
virtual void run(); // 启动新线程不能直接调用run,需要调用 start 接口,
// start 会启动新线程,然后执行run里的代码块。
1)子类化 QThread(重写一个类,继承自 QThread);
2)重写 run 函数,执行耗时操作(run 函数内有一个 while / for 循环 或 sleep);
3)子线程类实现 公共方法,供主线程传参(主线程给子线程传参);
4)主线程内 定义并实例化子线程的类对象;
5)主线程调用 start 方法 启动子线程;
6)设置必要的 信号和槽 做连接 ——> 子线程给主线程传参;
7)设置一个标记 来控制循环的退出,或者父线程调用 terminate 停止子线程。
注意:子线程内不允许操作界面上的任何部件,所有界面操作都应该由 GUI 主线程来进行。
重写一个线程子类,GUI 线程传递一个值给子线程。界面放置一个按钮,点击按钮后线程开始运行。子线程打印主线程传递的值以后睡眠任意时间,线程运行结束后通知主线程自己运行的时间,主线程把子线程睡眠的总时间显示到界面上。
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <qdebug.h>
class MyThread : public QThread // 1)重写一个类,继承自 QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
void setValue(int num) {this->val = num;} // 3)子线程类实现 公共方法,供主线程传参
signals:
void valueSignal(int); // 6)子线程发信号给主线程
public slots:
private:
void run(); // 2)重写 run 函数
int val;
};
#endif // MYTHREAD_H
#include "mythread.h"
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
}
void MyThread::run() // 2)重写 run 函数
{
qDebug() << "From parent thread: val = " << val;
QThread::sleep(16); // 2)run 函数内有一个 while/for 循环 或 sleep
emit valueSignal(16);
}
#define WIDGET_H
#include <QtWidgets>
#include "mythread.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
MyThread *myth; // 4)主线程内 定义子线程的类对象
public slots:
void valueSlot(int); // 6)主线程的槽接收来自子线程的信号
private slots:
void on_pushButton_clicked();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
// 4)主线程内 实例化子线程的类对象
myth = new MyThread(this); // 使用 this 的目的仅是方便回收
myth->setValue(98); // 4.5)主线程传参给子线程
// 6)设置必要的 信号和槽 做连接
QObject::connect(myth, SIGNAL(valueSignal(int)), this, SLOT(valueSlot(int)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::valueSlot(int num) // 6)子线程给主线程传参的方式;信号与槽
{
ui->label->setText("From child thread: val = " + QString::number(num));
}
void Widget::on_pushButton_clicked()
{
myth->start(); // 5)主线程调用 start 方法 启动子线程
}
实现效果如下:
#ifndef NEWTHREAD_H
#define NEWTHREAD_H
#include <QThread>
#include <QtWidgets>
class NewThread : public QThread
{
Q_OBJECT
public:
explicit NewThread(QObject *parent = 0);
void setFileNames(QString src, QString dest)
{
srcName = src;
destName = dest;
}
signals:
void valueSignal(int);
public slots:
protected:
void run();
QString srcName;
QString destName;
int percentage;
};
#endif // NEWTHREAD_H
#include "newthread.h"
#define KB 1024
NewThread::NewThread(QObject *parent) :
QThread(parent)
{
}
void NewThread::run()
{
QFile srcFile(srcName);
if (!srcFile.open(QIODevice::ReadOnly))
{
qDebug() << "打开文件失败";
return ;
}
QFile destFile(destName);
if (!destFile.open(QIODevice::ReadWrite | QIODevice::Truncate))
{
qDebug() << "复制文件失败";
return ;
}
qint64 totalSize = srcFile.size();
qint64 currentSize = 0;
QByteArray buffer;
while (!srcFile.atEnd())
{
buffer.clear();
buffer = srcFile.read(KB);
destFile.write(buffer);
currentSize += buffer.size();
percentage = 100 * currentSize / totalSize;
emit valueSignal(percentage);
}
}
#ifndef WIDGET_H
#define WIDGET_H
#include <QtWidgets>
#include "newthread.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_released();
private:
Ui::Widget *ui;
NewThread *thr;
public slots:
void valueSlot(int);
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->progressBar->setValue(0);
thr = new NewThread(this);
QObject::connect(thr, SIGNAL(valueSignal(int)), this, SLOT(valueSlot(int)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_released()
{
QString srcName = QFileDialog::getOpenFileName(this, tr("选择文件"), \
"D:/Packages_", tr("all (*.*)"));
if (srcName.isEmpty())
return ;
qDebug() << "src: " << srcName;
QString destName = QFileDialog::getSaveFileName(this, tr("保存文件"), \
"e:", tr("all (*.*)"));
if (destName.isEmpty())
return ;
qDebug() << "dest: " << destName;
ui->progressBar->setValue(0); // 每次启动线程前,将进度条的进度 置0
thr->setFileNames(srcName, destName);
thr->start(); // 不要忘记启动线程
}
void Widget::valueSlot(int value)
{
ui->progressBar->setValue(value);
}
运行结果如下:(复制文件时,拖动窗口不会产生卡顿)