文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
推荐链接:
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:仪表盘实现了一个汽车速度表风格自定义控件,参数可设置,数据可实时更新。
1.代码分析
2.所有源码
3.效果演示
一、代码分析C++ 后端详细分析
-
构造函数与析构函数
// 默认构造函数
DownloadTool::DownloadTool(QObject* parent)
: QObject(parent) // 调用基类构造函数
{
// 简单的初始化,成员变量使用默认值
}// 参数化构造函数
DownloadTool::DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent)
: QObject(parent)
{
m_downloadUrl = downloadUrl; // 直接赋值,不触发信号
m_savePath = savePath; // 直接赋值,不触发信号
}// 析构函数
DownloadTool::~DownloadTool()
{
if (reply) {
reply->deleteLater(); // 安全删除网络回复对象
// 使用 deleteLater() 而不是 delete,避免在事件处理过程中删除
}
// file 智能指针会自动释放文件资源
} -
属性访问器函数
QString DownloadTool::downloadUrl() const
{
return m_downloadUrl; // 返回当前下载URL
}void DownloadTool::setDownloadUrl(const QString& url)
{
if (m_downloadUrl != url) { // 只有值改变时才更新
m_downloadUrl = url;
emit downloadUrlChanged(); // 通知QML属性已改变
}
}QString DownloadTool::savePath() const
{
return m_savePath; // 返回当前保存路径
}void DownloadTool::setSavePath(const QString& path)
{
if (m_savePath != path) { // 值改变检查
m_savePath = path;
emit savePathChanged(); // 通知QML属性已改变
}
}bool DownloadTool::downloading() const
{
// 检查下载状态:有回复对象且正在运行
return reply && reply->isRunning();
} -
核心下载逻辑函数
getDefaultDownloadPath()
QString DownloadTool::getDefaultDownloadPath() const
{
// 使用 QCoreApplication 获取应用程序目录
// 避免依赖 QApplication (QtWidgets模块)
return QCoreApplication::applicationDirPath() + "/downloads";
// 示例结果: "C:/Program Files/MyApp/downloads"
}
startDownload() - 主下载入口
void DownloadTool::startDownload()
{
// 1. 状态检查 - 防止重复下载
if (downloading()) {
return; // 如果正在下载,直接返回
}
// 2. URL验证和解析
const QUrl newUrl = QUrl::fromUserInput(m_downloadUrl);
// QUrl::fromUserInput() 能处理各种格式的URL输入
if (!newUrl.isValid()) {
// URL无效,发出错误信号
#ifdef DOWNLOAD_DEBUG
qDebug() << QString("Invalid URL: %1: %2").arg(m_downloadUrl, newUrl.errorString());
#endif
emit sigDownloadError("Invalid URL: " + newUrl.errorString());
return;
}
// 3. 文件名处理
QString fileName = newUrl.fileName(); // 从URL提取文件名
if (fileName.isEmpty()) {
fileName = defaultFileName; // 使用默认文件名 "download.file"
}
// 4. 保存路径处理
if (m_savePath.isEmpty()) {
m_savePath = getDefaultDownloadPath(); // 使用默认路径
}
// 5. 创建目录
if (!QFileInfo(m_savePath).isDir()) {
QDir dir;
dir.mkpath(m_savePath); // 递归创建目录
}
// 6. 构建完整文件路径
fileName.prepend(m_savePath + '/'); // 在前面添加路径
// 示例: "downloads/file.zip"
// 7. 处理已存在文件
if (QFile::exists(fileName)) {
QFile::remove(fileName); // 删除已存在的文件
}
// 8. 创建文件
file = openFileForWrite(fileName);
if (!file) {
// 文件创建失败
emit sigDownloadError("Unable to create file for writing");
return;
}
// 9. 开始网络请求
startRequest(newUrl);
// 10. 通知状态改变
emit downloadingChanged(); // downloading() 现在返回 true
}
openFileForWrite() - 文件创建
std::unique_ptr<QFile> DownloadTool::openFileForWrite(const QString& fileName)
{
// 创建 unique_ptr 管理的 QFile 对象
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
return nullptr; // 返回空指针表示失败
}
return file; // 返回文件对象
}
startRequest() - 网络请求发起
void DownloadTool::startRequest(const QUrl& requestedUrl)
{
url = requestedUrl; // 保存当前URL
httpRequestAborted = false; // 重置取消标志
// 发起GET请求
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("Downloading %1...").arg(url.toString());
#endif
}
- 网络事件处理函数
httpReadyRead() - 数据接收
void DownloadTool::httpReadyRead()
{
// 当有数据可读时,立即写入文件
if (file) {
file->write(reply->readAll()); // 读取所有可用数据并写入文件
}
// 这种实时写入方式:
// 优点:内存占用小,适合大文件
// 缺点:频繁的磁盘IO操作
}
networkReplyProgress() - 进度更新
void DownloadTool::networkReplyProgress(qint64 bytesRead, qint64 totalBytes)
{
// 计算进度百分比,避免除零错误
qreal progress = totalBytes > 0 ? qreal(bytesRead) / qreal(totalBytes) : 0;
// 发出进度信号
emit sigProgress(bytesRead, totalBytes, progress);
#ifdef DOWNLOAD_DEBUG
// 调试信息:进度百分比和MB显示
qDebug() << QString::number(progress * 100, 'f', 2) << "% "
<< bytesRead / (1024 * 1024) << "MB" << "/" << totalBytes / (1024 * 1024) << "MB";
#endif
}
httpFinished() - 请求完成处理
void DownloadTool::httpFinished()
{
// 1. 通知状态改变
emit downloadingChanged(); // downloading() 现在返回 false
// 2. 文件信息保存和清理
QFileInfo fi;
if (file) {
fi.setFile(file->fileName()); // 保存文件信息
file->close(); // 关闭文件
file.reset(); // 释放文件指针
}
// 3. 检查是否用户取消
if (httpRequestAborted) {
return; // 如果是用户取消,直接返回
}
// 4. 检查网络错误
if (reply->error()) {
// 删除不完整的文件
QFile::remove(fi.absoluteFilePath());
QString errorString = QString("Download failed: %1.").arg(reply->errorString());
#ifdef DOWNLOAD_DEBUG
qDebug() << errorString;
#endif
emit sigDownloadError(errorString);
return;
}
// 5. 处理HTTP重定向
const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (!redirectionTarget.isNull()) {
// 构建重定向URL
const QUrl redirectedUrl = url.resolved(redirectionTarget.toUrl());
// 重新打开文件(同一个文件)
file = openFileForWrite(fi.absoluteFilePath());
if (!file) {
emit sigDownloadError("Unable to create file for redirection");
return;
}
// 重新发起请求到重定向URL
startRequest(redirectedUrl);
return; // 注意这里的return,重定向时不会发出完成信号
}
// 6. 清理网络回复对象
reply->deleteLater(); // 安全删除
reply = nullptr; // 置空指针
// 7. 发出下载完成信号
emit sigDownloadFinished();
#ifdef DOWNLOAD_DEBUG
qDebug() << QString("Downloaded %1 bytes to %2 in %3")
.arg(fi.size()).arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath()));
qDebug() << "Finished";
#endif
}
cancelDownload() - 取消下载
void DownloadTool::cancelDownload()
{
// 1. 状态检查
if (!downloading()) {
return; // 如果不在下载状态,直接返回
}
// 2. 设置取消标志
httpRequestAborted = true;
// 3. 中止网络请求
reply->abort(); // 这会触发 reply->error() 和 httpFinished()
// 4. 通知状态改变
emit downloadingChanged();
}
QML 前端函数详细分析
- 工具函数
formatBytes() - 字节格式化
function formatBytes(bytes) {
if (bytes === 0) return "0 B"; // 处理零值
const k = 1024; // 使用1024进制
const sizes = ['B', 'KB', 'MB', 'GB']; // 单位数组
// 计算单位索引:log1024(bytes)
const i = Math.floor(Math.log(bytes) / Math.log(k));
// 格式化显示:值保留2位小数 + 单位
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 示例:
// formatBytes(1024) → "1.00 KB"
// formatBytes(1536) → "1.50 KB"
// formatBytes(1048576) → "1.00 MB"
updateButtonStates() - 按钮状态管理
qml
function updateButtonStates() {
// 下载按钮:有URL且不在下载状态时可用
downloadButton.enabled = urlTextField.text !== "" && !downloadTool.downloading
// 取消按钮:只在下载状态时可用
cancelButton.enabled = downloadTool.downloading
// 暂停按钮:预留功能
pauseResumeButton.enabled = downloadTool.downloading
}
- 信号处理函数
进度信号处理
onSigProgress: {
// 1. 更新进度条
progressBar.value = progress
// 2. 更新进度文本
progressText.text = formatBytes(bytesRead) + " / " + formatBytes(totalBytes) +
" (" + (progress * 100).toFixed(1) + "%)"
// 3. 计算下载速度(每秒)
if (speedTimer.running) {
var currentTime = new Date().getTime() // 当前时间戳(毫秒)
var timeDiff = (currentTime - speedTimer.lastTime) / 1000 // 转换为秒
if (timeDiff > 0) {
var bytesDiff = bytesRead - speedTimer.lastBytes // 字节差
var speed = bytesDiff / timeDiff // 速度(字节/秒)
speedText.text = "Speed: " + formatBytes(speed) + "/s"
}
// 更新计时器状态
speedTimer.lastBytes = bytesRead
speedTimer.lastTime = currentTime
}
}
状态信号处理
// 下载完成
onSigDownloadFinished: {
statusText.text = "✓ 下载完成!"
statusText.color = successColor // 绿色
updateButtonStates()
progressBar.Material.accent = successColor // 进度条变绿色
speedText.text = "" // 清空速度显示
}
// 下载错误
onSigDownloadError: {
statusText.text = "✗ Error: " + errorMessage
statusText.color = errorColor // 红色
updateButtonStates()
progressBar.value = 0 // 重置进度条
speedText.text = "" // 清空速度显示
}
// 下载状态改变
onDownloadingChanged: {
if (downloading) {
// 开始下载
statusText.text = "⏳ 下载中..."
statusText.color = primaryColor // 蓝色
progressBar.Material.accent = primaryColor
speedTimer.start() // 启动速度计时器
} else {
// 下载停止
speedTimer.stop() // 停止速度计时器
}
updateButtonStates()
}
- 用户交互函数
下载按钮点击
onClicked: {
progressBar.value = 0 // 重置进度条
progressText.text = "开始下载..." // 更新进度文本
// 重置速度计时器状态
speedTimer.lastBytes = 0
speedTimer.lastTime = new Date().getTime()
// 调用C++下载方法
downloadTool.startDownload()
}
取消按钮点击
onClicked: {
// 调用C++取消方法
downloadTool.cancelDownload()
// 更新UI状态
statusText.text = "下载取消"
statusText.color = warningColor // 橙色
}
二、所有源码
DownloadTool.h文件源码
#ifndef DOWNLOADTOOL_H
#define DOWNLOADTOOL_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QFile>
#include <QDir>
#include <QUrl>
#include <QFileInfo>
#include <memory>
// 可以取消注释下面的宏来启用调试输出
// #define DOWNLOAD_DEBUG
class DownloadTool : public QObject
{
Q_OBJECT
Q_PROPERTY(QString downloadUrl READ downloadUrl WRITE setDownloadUrl NOTIFY downloadUrlChanged)
Q_PROPERTY(QString savePath READ savePath WRITE setSavePath NOTIFY savePathChanged)
Q_PROPERTY(bool downloading READ downloading NOTIFY downloadingChanged)
public:
explicit DownloadTool(QObject* parent = nullptr);
DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent = nullptr);
~DownloadTool();
QString downloadUrl() const;
void setDownloadUrl(const QString& url);
QString savePath() const;
void setSavePath(const QString& path);
bool downloading() const;
Q_INVOKABLE void startDownload();
Q_INVOKABLE void cancelDownload();
signals:
void downloadUrlChanged();
void savePathChanged();
void downloadingChanged();
void sigProgress(qint64 bytesRead, qint64 totalBytes, qreal progress);
void sigDownloadFinished();
void sigDownloadError(const QString& errorMessage);
private slots:
void httpFinished();
void httpReadyRead();
void networkReplyProgress(qint64 bytesRead, qint64 totalBytes);
private:
void startRequest(const QUrl& requestedUrl);
std::unique_ptr<QFile> openFileForWrite(const QString& fileName);
QString getDefaultDownloadPath() const;
private:
QString m_downloadUrl;
QString m_savePath;
QNetworkAccessManager qnam;
QNetworkReply* reply = nullptr;
std::unique_ptr<QFile> file;
QUrl url;
bool httpRequestAborted = false;
const QString defaultFileName = "download.file";
};
#endif // DOWNLOADTOOL_H
DownloadTool.cpp文件源码
#include "DownloadTool.h"
#include <QCoreApplication>
#include <QDebug>
DownloadTool::DownloadTool(QObject* parent)
: QObject(parent)
{
}
DownloadTool::DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent)
: QObject(parent)
{
m_downloadUrl = downloadUrl;
m_savePath = savePath;
}
DownloadTool::~DownloadTool()
{
if (reply) {
reply->deleteLater();
}
}
QString DownloadTool::downloadUrl() const
{
return m_downloadUrl;
}
void DownloadTool::setDownloadUrl(const QString& url)
{
if (m_downloadUrl != url) {
m_downloadUrl = url;
emit downloadUrlChanged();
}
}
QString DownloadTool::savePath() const
{
return m_savePath;
}
void DownloadTool::setSavePath(const QString& path)
{
if (m_savePath != path) {
m_savePath = path;
emit savePathChanged();
}
}
bool DownloadTool::downloading() const
{
return reply && reply->isRunning();
}
QString DownloadTool::getDefaultDownloadPath() const
{
// 使用 QCoreApplication 而不是 QApplication
return QCoreApplication::applicationDirPath() + "/downloads";
}
void DownloadTool::startDownload()
{
if (downloading()) {
return;
}
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
emit sigDownloadError("Invalid URL: " + newUrl.errorString());
return;
}
QString fileName = newUrl.fileName();
if (fileName.isEmpty()) fileName = defaultFileName;
if (m_savePath.isEmpty()) {
m_savePath = getDefaultDownloadPath();
}
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) {
emit sigDownloadError("Unable to create file for writing");
return;
}
startRequest(newUrl);
emit downloadingChanged();
}
void DownloadTool::cancelDownload()
{
if (!downloading()) {
return;
}
httpRequestAborted = true;
reply->abort();
emit downloadingChanged();
}
void DownloadTool::httpFinished()
{
emit downloadingChanged();
QFileInfo fi;
if (file) {
fi.setFile(file->fileName());
file->close();
file.reset();
}
if (httpRequestAborted) {
return;
}
if (reply->error()) {
QFile::remove(fi.absoluteFilePath());
QString errorString = QString("Download failed: %1.").arg(reply->errorString());
#ifdef DOWNLOAD_DEBUG
qDebug() << errorString;
#endif // DOWNLOAD_DEBUG
emit sigDownloadError(errorString);
return;
}
const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (!redirectionTarget.isNull()) {
const QUrl redirectedUrl = url.resolved(redirectionTarget.toUrl());
file = openFileForWrite(fi.absoluteFilePath());
if (!file) {
emit sigDownloadError("Unable to create file for redirection");
return;
}
startRequest(redirectedUrl);
return;
}
reply->deleteLater();
reply = nullptr;
emit sigDownloadFinished();
#ifdef DOWNLOAD_DEBUG
qDebug() << QString("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 = totalBytes > 0 ? qreal(bytesRead) / qreal(totalBytes) : 0;
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("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;
}
main.qml文件源码
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls.Material 2.12
import DownloadTool 1.0
ApplicationWindow {
id: window
width: 700
height: 550
minimumWidth: 600
minimumHeight: 450
title: "HTTP 文件下载器"
visible: true
// 颜色定义
property color primaryColor: "#2196F3"
property color successColor: "#4CAF50"
property color errorColor: "#F44336"
property color warningColor: "#FF9800"
property color textColor: "#333333"
property color lightGray: "#F5F5F5"
property color borderColor: "#E0E0E0"
Material.theme: Material.Light
Material.accent: primaryColor
DownloadTool {
id: downloadTool
downloadUrl: urlTextField.text
savePath: pathTextField.text
onSigProgress: {
progressBar.value = progress
progressText.text = formatBytes(bytesRead) + " / " + formatBytes(totalBytes) +
" (" + (progress * 100).toFixed(1) + "%)"
// 计算下载速度
if (speedTimer.running) {
var currentTime = new Date().getTime()
var timeDiff = (currentTime - speedTimer.lastTime) / 1000 // 转换为秒
if (timeDiff > 0) {
var bytesDiff = bytesRead - speedTimer.lastBytes
var speed = bytesDiff / timeDiff
speedText.text = "Speed: " + formatBytes(speed) + "/s"
}
speedTimer.lastBytes = bytesRead
speedTimer.lastTime = currentTime
}
}
onSigDownloadFinished: {
statusText.text = "✓ 下载完成!"
statusText.color = successColor
updateButtonStates()
progressBar.Material.accent = successColor
speedText.text = ""
}
onSigDownloadError: {
statusText.text = "✗ Error: " + errorMessage
statusText.color = errorColor
updateButtonStates()
progressBar.value = 0
speedText.text = ""
}
onDownloadingChanged: {
if (downloading) {
statusText.text = "⏳ 下载中..."
statusText.color = primaryColor
progressBar.Material.accent = primaryColor
speedTimer.start()
} else {
speedTimer.stop()
}
updateButtonStates()
}
}
Timer {
id: speedTimer
interval: 1000
repeat: true
running: false
property real lastBytes: 0
property real lastTime: new Date().getTime()
onTriggered: {
// 定时更新速度显示
}
}
function formatBytes(bytes) {
if (bytes === 0) return "0 B";
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function updateButtonStates() {
downloadButton.enabled = urlTextField.text !== "" && !downloadTool.downloading
cancelButton.enabled = downloadTool.downloading
pauseResumeButton.enabled = downloadTool.downloading
}
Rectangle {
anchors.fill: parent
color: lightGray
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 25
spacing: 20
// 标题区域
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 80
color: primaryColor
radius: 12
RowLayout {
anchors.fill: parent
anchors.margins: 20
Label {
text: "📥"
font.pixelSize: 32
Layout.alignment: Qt.AlignVCenter
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Label {
text: "http 文件下载器"
font.pixelSize: 24
font.bold: true
color: "white"
Layout.fillWidth: true
}
Label {
text: "下载文件"
font.pixelSize: 14
color: "white"
opacity: 0.9
Layout.fillWidth: true
}
}
}
}
// 下载设置卡片
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 140
color: "white"
radius: 12
border.color: borderColor
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
Label {
text: "下载设置"
font.pixelSize: 16
font.bold: true
color: textColor
Layout.fillWidth: true
}
ColumnLayout {
Layout.fillWidth: true
spacing: 12
RowLayout {
Layout.fillWidth: true
spacing: 10
Label {
text: "🔗"
font.pixelSize: 16
Layout.preferredWidth: 30
}
TextField {
id: urlTextField
Layout.fillWidth: true
placeholderText: "https://dl.360safe.com/pclianmeng/n/3__3112523__3f7372633d6c6d266c733d6e33366163663466393961__68616f2e3336302e636e__0cd2.exe"
font.pixelSize: 14
selectByMouse: true
onTextChanged: updateButtonStates()
background: Rectangle {
radius: 6
border.color: urlTextField.activeFocus ? primaryColor : borderColor
border.width: 1
color: "transparent"
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Label {
text: "📁"
font.pixelSize: 16
Layout.preferredWidth: 30
}
TextField {
id: pathTextField
Layout.fillWidth: true
placeholderText: "Enter save directory path"
font.pixelSize: 14
selectByMouse: true
background: Rectangle {
radius: 6
border.color: pathTextField.activeFocus ? primaryColor : borderColor
border.width: 1
color: "transparent"
}
}
}
}
}
}
// 进度卡片
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 180
color: "white"
radius: 12
border.color: borderColor
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
Label {
text: "下载进度"
font.pixelSize: 16
font.bold: true
color: textColor
Layout.fillWidth: true
}
ColumnLayout {
Layout.fillWidth: true
spacing: 12
ProgressBar {
id: progressBar
Layout.fillWidth: true
from: 0
to: 1
value: 0
background: Rectangle {
implicitHeight: 8
color: "#e0e0e0"
radius: 4
}
contentItem: Item {
implicitHeight: 8
Rectangle {
width: progressBar.visualPosition * parent.width
height: parent.height
radius: 4
color: progressBar.Material.accent
}
}
}
RowLayout {
Layout.fillWidth: true
Label {
id: progressText
text: "准备下载"
font.pixelSize: 13
color: textColor
opacity: 0.8
Layout.fillWidth: true
}
Label {
id: speedText
text: ""
font.pixelSize: 13
color: primaryColor
font.bold: true
}
}
// 状态显示
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 50
color: "transparent"
border.color: borderColor
border.width: 1
radius: 8
Label {
id: statusText
anchors.centerIn: parent
text: "输入url 点击下载"
font.pixelSize: 14
color: textColor
opacity: 0.7
}
}
}
}
}
// 按钮区域
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 15
Button {
id: downloadButton
text: "🚀 下载"
Material.background: primaryColor
Material.foreground: "white"
font.pixelSize: 14
font.bold: true
enabled: urlTextField.text !== "" && !downloadTool.downloading
onClicked: {
progressBar.value = 0
progressText.text = "开始下载..."
speedTimer.lastBytes = 0
speedTimer.lastTime = new Date().getTime()
downloadTool.startDownload()
}
contentItem: Label {
text: downloadButton.text
font: downloadButton.font
color: downloadButton.enabled ? "white" : "#999"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 8
color: downloadButton.enabled ? primaryColor : "#E0E0E0"
}
}
Button {
id: pauseResumeButton
text: "⏸️ 暂停"
Material.background: warningColor
Material.foreground: "white"
font.pixelSize: 14
enabled: false // 暂停功能需要额外实现
visible: false // 暂时隐藏,需要额外实现暂停功能
background: Rectangle {
radius: 8
color: pauseResumeButton.enabled ? warningColor : "#E0E0E0"
}
}
Button {
id: cancelButton
text: "❌ 取消"
Material.background: errorColor
Material.foreground: "white"
font.pixelSize: 14
font.bold: true
enabled: downloadTool.downloading
onClicked: {
downloadTool.cancelDownload()
statusText.text = "下载取消"
statusText.color = warningColor
}
background: Rectangle {
radius: 8
color: cancelButton.enabled ? errorColor : "#E0E0E0"
}
}
}
// 底部信息
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
color: "transparent"
Label {
anchors.centerIn: parent
text: "v1.0"
font.pixelSize: 12
color: textColor
opacity: 0.5
}
}
}
Component.onCompleted: {
// 设置默认下载路径
if (pathTextField.text === "") {
pathTextField.text = "downloads"
}
updateButtonStates()
// 设置示例URL(可选)
// urlTextField.text = "https://www.example.com/sample-file.zip"
}
}
main.cpp文件源码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QIcon>
#include "DownloadTool.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
app.setOrganizationName("YourCompany");
app.setApplicationName("FileDownloader");
// 注册 C++ 类型到 QML
qmlRegisterType<DownloadTool>("DownloadTool", 1, 0, "DownloadTool");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
三、效果演示
输入下载文件的url,点击下载,下载开始同时显示进度条。
