💡 进度条显示拷贝进度(verson 1)
窗口上放置一个按钮和一个进度条部件,点击按钮,进行拷贝操作 —— 打开对话框选择源文件,然后再打开一个对话框 选择 目标文件存放位置和名称。拷贝过程中进度条显示当前进度(大文件)。不允许用QFile 的 copy 方法 —— 把源文件分解开,然后一帧一帧写入目标文件。
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QtWidgets>namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();private slots:void on_pushButton_released();private:Ui::Widget *ui;
};#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"#define KB 1024Widget::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 线程(QT 的主线程)
QT 的主线程称为 GUI 线程,负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。
使用多线程的好处
1、提高应用界面的响应速度;
这对于开发图形界面程序尤其重要,当一个操作耗时很长时(比如大批量 I/O 或大量矩阵变换等CPU密集操作),整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,从而不会影响到 主 GUI 线程,从而避免上述问题。
2、使多核心 CPU 系统更加有效;
当线程数不大于CPU核数时,操作系统可以调度不同的线程运行于不同的CPU核上。
3、改善程序结构;
一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于程序的理解和维护。
创建一个界面,界面 show 以后调用睡眠函数,观察效果。
QThread 类
相关接口
Public Functions:QThread(QObject *parent = 0); // 构造函数 // pthread_createbool isFinished() const; // 判断线程是否退出bool wait(unsigned long time = ULONG_MAX); // pthread_join(&id)// 等待某个线程结束,最多等待time ms,如果时间没有设置,那么永远等待。Public Slots:void start(Priority priority = InheritPriority) // 启动线程必须使用startvoid terminate(); // 杀死线程 // pthread_cancelStatic Public Members:Qt::HANDLE currentThreadId() [static] // 得到当前执行者线程ID,可以直接qDebugvoid 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 线程传递一个值给子线程。界面放置一个按钮,点击按钮后线程开始运行。子线程打印主线程传递的值以后睡眠任意时间,线程运行结束后通知主线程自己运行的时间,主线程把子线程睡眠的总时间显示到界面上。
mythread.h
#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
mythread.cpp
#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 循环 或 sleepemit valueSignal(16);
}
widget.h
#define WIDGET_H#include <QtWidgets>
#include "mythread.h"namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic: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
widget.cpp
#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 方法 启动子线程
}
实现效果如下:
💡 进度条显示拷贝进度(verson 2)
newthread.h
#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
newthread.cpp
#include "newthread.h"#define KB 1024NewThread::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);}
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QtWidgets>
#include "newthread.h"namespace Ui {
class Widget;
}class Widget : public QWidget
{Q_OBJECTpublic: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
widget.cpp
#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); // 每次启动线程前,将进度条的进度 置0thr->setFileNames(srcName, destName);thr->start(); // 不要忘记启动线程
}void Widget::valueSlot(int value)
{ui->progressBar->setValue(value);
}
运行结果如下:(复制文件时,拖动窗口不会产生卡顿)