文章的目的为了记录使用C++ 进行QT Widget 开发学习的经历。临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT Widget 开发(一)工程文件结构-CSDN博客
开源 C++ QT Widget 开发(二)基本控件应用-CSDN博客
开源 C++ QT Widget 开发(三)图表--波形显示器-CSDN博客
开源 C++ QT Widget 开发(四)文件--二进制文件查看编辑-CSDN博客
开源 C++ QT Widget 开发(五)通讯--串口调试-CSDN博客
开源 C++ QT Widget 开发(六)通讯--TCP调试-CSDN博客
开源 C++ QT Widget 开发(七)线程--多线程及通讯-CSDN博客
开源 C++ QT Widget 开发(八)网络--Http文件下载-CSDN博客
推荐链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
推荐链接:
开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
本章主要内容:基于 Qt Http 的网络文件下载器。
目录:
1.主要内容
2.核心源码分析
3.所有源码
4.显示效果
一、主要内容
使用 QNetworkAccessManager 来处理 HTTP 请求,实现了文件的下载、进度显示、取消下载以及重定向处理等功能。DownloadTool 类封装了所有下载相关的核心逻辑,而 Widget 类则提供了一个简单的界面来触发下载操作。
二、核心源码分析
2.1 头文件部分
继承关系:继承自QObject,支持Qt的信号槽机制
网络组件:使用QNetworkAccessManager和QNetworkReply处理网络请求
内存管理:使用std::unique_ptr智能指针管理文件对象
线程安全:使用QPointer防止悬挂指针问题
2.2 主要功能
// 构造函数:接收下载URL和保存路径
DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent = nullptr);
// 开始下载
void startDownload();
// 取消下载
void cancelDownload();
// 信号:进度更新和完成通知
void sigProgress(qint64 bytesRead, qint64 totalBytes, qreal progress);
void sigDownloadFinished();
2.3 实现细节分析
下载流程:
startDownload() 验证URL并准备文件路径
startRequest() 发起网络请求并连接信号槽
httpReadyRead() 实时写入接收到的数据
networkReplyProgress() 更新下载进度
httpFinished() 处理下载完成或错误
关键特性:
✅ 自动创建保存目录
✅ 支持文件重命名(使用URL中的文件名)
✅ 自动处理重定向
✅ 错误处理和资源清理
✅ 进度实时反馈
4. 使用示例
// 创建下载器
DownloadTool* downloader = new DownloadTool(
"http://example.com/file.zip",
"/path/to/save"
);
// 连接信号
connect(downloader, &DownloadTool::sigProgress, [](qint64 bytes, qint64 total, qreal progress) {
qDebug() << "Progress:" << progress * 100 << "%";
});
connect(downloader, &DownloadTool::sigDownloadFinished, []() {
qDebug() << "Download finished!";
});
// 开始下载
downloader->startDownload();
三、所有源码
3.1 .pro文件
d#-------------------------------------------------
#
# Project created by QtCreator 2024-01-19T15:33:43
#
#-------------------------------------------------
QT += core gui
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = DownloadTool
TEMPLATE = app
# The following define makes your csssompiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
widget.cpp \
downloadtool.cpp
HEADERS += \
widget.h \
downloadtool.h
FORMS += \
widget.ui
3.2 downloadTool.h
#ifndef DOWNLOADTOOL_H
#define DOWNLOADTOOL_H
#pragma once
#include <QObject> // QObject类是Qt对象模型的核心
#include <QUrl> // QUrl类提供了使用URL的便捷接口
#include <QFile> // QFile类用于对文件进行读写操作
#include <QDir> // QDir类用于操作路径名及底层文件系统
#include <QPointer> // QPointer指针引用的对象被销毁时候,会自动指向NULL,解决指针悬挂问题
#include <QApplication> // 此处用于获取当前程序绝对路径
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkAccessManager> // QNetworkAccessManager类为应用提供发送网络请求和接收答复的API接口
#include <memory> // 使用std::unique_ptr需要包含该头文件
#define DOWNLOAD_DEBUG // 是否打印输出
class DownloadTool : public QObject // 继承QObject
{
Q_OBJECT // 加入此宏,才能使用QT中的signal和slot机制
public:
// 构造函数参数: 1)http文件完整的url 2)保存的路径
explicit DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent = nullptr);
~DownloadTool();
void startDownload(); // 开始下载文件
void cancelDownload(); // 取消下载文件
Q_SIGNALS:
void sigProgress(qint64 bytesRead, qint64 totalBytes, qreal progress); // 下载进度信号
void sigDownloadFinished(); // 下载完成信号
private Q_SLOTS:
void httpFinished(); // QNetworkReply::finished对应的槽函数
void httpReadyRead(); // QIODevice::readyRead对应的槽函数
void networkReplyProgress(qint64 bytesRead, qint64 totalBytes); // QNetworkReply::downloadProgress对应的槽函数
private:
void startRequest(const QUrl& requestedUrl);
std::unique_ptr<QFile> openFileForWrite(const QString& fileName);
private:
QString m_downloadUrl; // 保存构造时传入的下载url
QString m_savePath; // 保存构造时传入的保存路径
const QString defaultFileName = "tmp"; // 默认下载到tmp文件夹
QUrl url;
QNetworkAccessManager qnam;
QPointer<QNetworkReply> reply;
std::unique_ptr<QFile> file;
bool httpRequestAborted;
};
#endif // DOWNLOADTOOL_H
3.3 downloadTool.cpp
#include "DownloadTool.h"
DownloadTool::DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent)
: QObject(parent)
{
m_downloadUrl = downloadUrl;
m_savePath = savePath;
}
DownloadTool::~DownloadTool() {}
void DownloadTool::startDownload()
{
const QUrl newUrl = QUrl::fromUserInput(m_downloadUrl);
if (!newUrl.isValid()) {
#ifdef DOWNLOAD_DEBUG
qDebug() << QString("Invalid URL: %1: %2").arg(m_downloadUrl, newUrl.errorString());
#endif // DOWNLOAD_DEBUG
return;
}
QString fileName = newUrl.fileName();
if (fileName.isEmpty()) fileName = defaultFileName;
if (m_savePath.isEmpty()) { m_savePath = QApplication::applicationDirPath() + "/tmp"; }
if (!QFileInfo(m_savePath).isDir()) {
QDir dir;
dir.mkpath(m_savePath);
}
fileName.prepend(m_savePath + '/');
if (QFile::exists(fileName)) { QFile::remove(fileName); }
file = openFileForWrite(fileName);
if (!file) return;
startRequest(newUrl);
}
void DownloadTool::cancelDownload()
{
httpRequestAborted = true;
reply->abort();
}
void DownloadTool::httpFinished()
{
QFileInfo fi;
if (file) {
fi.setFile(file->fileName());
file->close();
file.reset();
}
if (httpRequestAborted) {
return;
}
if (reply->error()) {
QFile::remove(fi.absoluteFilePath());
#ifdef DOWNLOAD_DEBUG
qDebug() << QString("Download failed: %1.").arg(reply->errorString());
#endif // DOWNLOAD_DEBUG
return;
}
const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (!redirectionTarget.isNull()) {
const QUrl redirectedUrl = url.resolved(redirectionTarget.toUrl());
file = openFileForWrite(fi.absoluteFilePath());
if (!file) { return; }
startRequest(redirectedUrl);
return;
}
Q_EMIT sigDownloadFinished();
#ifdef DOWNLOAD_DEBUG
qDebug() << QString(tr("Downloaded %1 bytes to %2 in %3")
.arg(fi.size()).arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath())));
qDebug() << "Finished";
#endif // DOWNLOAD_DEBUG
}
void DownloadTool::httpReadyRead()
{
if (file) file->write(reply->readAll());
}
void DownloadTool::networkReplyProgress(qint64 bytesRead, qint64 totalBytes)
{
qreal progress = qreal(bytesRead) / qreal(totalBytes);
Q_EMIT sigProgress(bytesRead, totalBytes, progress);
#ifdef DOWNLOAD_DEBUG
qDebug() << QString::number(progress * 100, 'f', 2) << "% "
<< bytesRead / (1024 * 1024) << "MB" << "/" << totalBytes / (1024 * 1024) << "MB";
#endif // DOWNLOAD_DEBUG
}
void DownloadTool::startRequest(const QUrl& requestedUrl)
{
url = requestedUrl;
httpRequestAborted = false;
reply = qnam.get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &DownloadTool::httpFinished);
connect(reply, &QIODevice::readyRead, this, &DownloadTool::httpReadyRead);
connect(reply, &QNetworkReply::downloadProgress, this, &DownloadTool::networkReplyProgress);
#ifdef DOWNLOAD_DEBUG
qDebug() << QString(tr("Downloading %1...").arg(url.toString()));
#endif // DOWNLOAD_DEBUG
}
std::unique_ptr<QFile> DownloadTool::openFileForWrite(const QString& fileName)
{
std::unique_ptr<QFile> file(new QFile(fileName));
if (!file->open(QIODevice::WriteOnly)) {
#ifdef DOWNLOAD_DEBUG
qDebug() << QString("Unable to save the file %1: %2.")
.arg(QDir::toNativeSeparators(fileName), file->errorString());
#endif // DOWNLOAD_DEBUG
return nullptr;
}
return file;
}
3.4 widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_btnDown_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
3.5 widget.cpp文件
#include "widget.h"
#include "ui_widget.h"
#include "DownloadTool.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btnDown_clicked()
{
DownloadTool* dT;
//dT = new DownloadTool("https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe", QApplication::applicationDirPath() + "/download");
dT = new DownloadTool("https://dl.360safe.com/pclianmeng/n/3__3112523__3f7372633d6c6d266c733d6e33366163663466393961__68616f2e3336302e636e__0cd2.exe", QApplication::applicationDirPath() + "/download");
dT->startDownload();
}
3.6 main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
四、显示效果
点击下载,下载360卫士的exe文件,同时打印下载进度。
文件下载的位置