一、简述
在上篇 Qt 之 模仿迅雷 根据Url获取文件信息——上 文章中简单介绍了一下几个版本迅雷的界面变化,同时展示了这两天模仿迅雷新建任务窗口做的一个小例子。在这一篇中将讲解如何实现迅雷新建任务窗口。
首先简单看一下效果图。
二、代码之路
看代码之前需要看一下Qt 之 自定义窗口标题栏这一篇文章,因为这里用到了这篇文章中写到的自定义标题栏。
这里新建任务窗口类NewTaskWindow 继承了BaseWindow类,所以省去了一些代码实现(主要包括顶部标题栏、鼠标按住标题栏进行拖动,窗口背景色等 ),有需要的小伙伴可以去看一下这一篇文章。
newtaskwindow.h
#include <QWidget>
#include "ui_newtaskwindow.h"
#include "basewindow.h"
class NewTaskWindow : public BaseWindow
{
Q_OBJECT
public:
NewTaskWindow(QWidget *parent = 0);
~NewTaskWindow();
private:
void initWindow();
void initTitleBar();
void initTableWidget();
void initFilePathLineEdit();
QString getDiskFreeSpace(QString filePath);
void setFileInfo(QString fileName, QString fileType, QString fileSize);
void showFileInfo(QString fileName , QString fileSize);
void hideFileInfo();
void getFileTotalSize(QString url, int tryTimes = 1);
QString transformUnit(double bytes);
void paintEvent(QPaintEvent *event);
private slots:
void onTextChanged();
void onOpenFolder();
private:
Ui::NewTaskWindow ui;
QString m_filePath;
QLabel* m_remainSpaceLabel;
};
newtaskwindow.cpp
#include "newtaskwindow.h"
#include <QPainter>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkProxy>
#include <QFileInfo>
#include <QFileDialog>
#include "windows.h"
//http://down10.zol.com.cn/fanyi/Youdao701111.zip
#define UNIT_KB 1024 //KB
#define UNIT_MB 1024*1024 //MB
#define UNIT_GB 1024*1024*1024 //GB
NewTaskWindow::NewTaskWindow(QWidget *parent)
: BaseWindow(parent)
, m_filePath("D:/")
{
ui.setupUi(this);
initWindow();
}
NewTaskWindow::~NewTaskWindow()
{
}
void NewTaskWindow::initWindow()
{
// 初始化标题栏;
initTitleBar();
// 初始化文件信息表格;
initTableWidget();
// 初始化路径栏目;
initFilePathLineEdit();
// 加载样式表;
loadStyleSheet("NewTaskWindow");
connect(ui.pButtonOpenFolder, SIGNAL(clicked()), this, SLOT(onOpenFolder()));
connect(ui.textEdit, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
}
// 初始化标题栏;
void NewTaskWindow::initTitleBar()
{
m_titleBar->move(0, 3);
m_titleBar->setBackgroundColor(255, 255, 255);
m_titleBar->setTitleContent(QStringLiteral("新建任务"));
m_titleBar->setButtonType(ONLY_CLOSE_BUTTON);
m_titleBar->setTitleWidth(this->width());
}
// 初始化文件信息表格栏目;
void NewTaskWindow::initTableWidget()
{
ui.textEdit->setPlaceholderText(QStringLiteral("添加多个下载链接时,请确保每行只有一个链接。"));
ui.tableWidget->setSortingEnabled(false); // 设置不可排序;
ui.tableWidget->setFrameShape(QFrame::NoFrame); // 设置无边框;
ui.tableWidget->setShowGrid(false); // 设置不显示格子线;
ui.tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); // 设置标题垂直居中,水平居左;
ui.tableWidget->horizontalHeader()->setHighlightSections(false);
ui.tableWidget->horizontalHeader()->resizeSection(0, 215);
ui.tableWidget->horizontalHeader()->resizeSection(1, 58);
ui.tableWidget->horizontalHeader()->resizeSection(2, 79);
// 隐藏文件信息表格栏目;
hideFileInfo();
}
// 初始化路径栏目;
void NewTaskWindow::initFilePathLineEdit()
{
// 内嵌QLabel 显示剩余空间;
m_remainSpaceLabel = new QLabel;
QString reaminSpace = QStringLiteral("剩余:%1").arg(getDiskFreeSpace(m_filePath));
m_remainSpaceLabel->setText(reaminSpace);
m_remainSpaceLabel->setStyleSheet("color:rgb(25, 125, 203);font-size:12px;");
QHBoxLayout* filePathLayout = new QHBoxLayout;
filePathLayout->addStretch();
filePathLayout->addWidget(m_remainSpaceLabel);
filePathLayout->setSpacing(0);
filePathLayout->setContentsMargins(0, 0, 10, 0);
ui.lineEdit->setLayout(filePathLayout);
ui.lineEdit->setTextMargins(6, 0, 90, 0);
ui.lineEdit->setText(m_filePath);
}
// 根据路径返回磁盘剩余控件大小;
QString NewTaskWindow::getDiskFreeSpace(QString filePath)
{
LPCWSTR lpcwstrDriver = (LPCWSTR)filePath.utf16();
ULARGE_INTEGER liFreeBytesAvailable, liTotalBytes, liTotalFreeBytes;
if (!GetDiskFreeSpaceEx(lpcwstrDriver, &liFreeBytesAvailable, &liTotalBytes, &liTotalFreeBytes))
{
qDebug() << "ERROR: Call to GetDiskFreeSpaceEx() failed.";
return 0;
}
return transformUnit((qint64)liTotalFreeBytes.QuadPart);
}
void NewTaskWindow::paintEvent(QPaintEvent *event)
{
// 绘制窗口背景色;
QPainter painter(this);
QPainterPath pathBack;
pathBack.setFillRule(Qt::WindingFill);
pathBack.addRoundedRect(QRect(0, 0, this->width(), this->height()), 3, 3);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.fillPath(pathBack, QBrush(QColor(255, 255, 255)));
// 绘制窗口上方蓝条;
QPainterPath pathHead;
pathHead.setFillRule(Qt::WindingFill);
pathHead.addRoundedRect(QRect(0, 0, this->width(), 2), 3, 3);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.fillPath(pathHead, QBrush(QColor(15, 151, 255)));
return QWidget::paintEvent(event);
}
// 根据输入下载url,请求获取文件信息l
void NewTaskWindow::getFileTotalSize(QString strUrl, int tryTimes)
{
qint64 fileSize = -1;
bool isGetResult = false;
if (tryTimes <= 0)
{
tryTimes = 1;
}
do
{
// 如果有代理需要设置代理;
// QNetworkProxy proxy;
// proxy.setType(QNetworkProxy::HttpProxy);
// proxy.setHostName("proxy.example.com");
// proxy.setPort(1080);
// proxy.setUser("username");
// proxy.setPassword("password");
// QNetworkProxy::setApplicationProxy(proxy);
QNetworkAccessManager manager;
// 事件循环,等待请求文件头信息结束;
QEventLoop loop;
// 超时,结束事件循环;
QTimer timer;
//发出请求,获取文件地址的头部信息;
QNetworkReply *reply = manager.head(QNetworkRequest(strUrl));
if (!reply)
continue;
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer.start(2000);
loop.exec();
if (reply->error() != QNetworkReply::NoError)
{
// 请求发生错误;
qDebug() << reply->errorString();
continue;
}
else if (!timer.isActive())
{
// 请求超时超时,未获取到文件信息;
qDebug() << "Request Timeout";
continue;
}
timer.stop();
isGetResult = true;
// 获取文件大小;
QVariant var = reply->header(QNetworkRequest::ContentLengthHeader);
fileSize = var.toLongLong();
// 获取文件名;
// 先根据请求返回的信息中获取文件名,因为有些文件服务器未必会返回文件名信息(包括文件名,文件大小);可使用下面方法;
// QFileInfo 会根据 url 返回 下载文件名;但也会失败,返回为空值;
QString strDisposition = reply->rawHeader("Content-Disposition");
int index = strDisposition.indexOf("filename=");
QString fileName = strDisposition.mid(index + 9);
if (fileName.isEmpty())
{
QUrl url(strUrl);
QFileInfo fileInfo(url.path());
fileName += fileInfo.fileName();
}
showFileInfo(fileName , transformUnit(fileSize));
reply->deleteLater();
break;
} while (tryTimes--);
if (!isGetResult)
{
hideFileInfo();
}
}
// 转换单位;
QString NewTaskWindow::transformUnit(double bytes)
{
QString strUnit = " B";
if (bytes <= 0)
{
bytes = 0;
}
else if (bytes < UNIT_KB)
{
}
else if (bytes < UNIT_MB)
{
bytes /= UNIT_KB;
strUnit = " KB";
}
else if (bytes < UNIT_GB)
{
bytes /= UNIT_MB;
strUnit = " MB";
}
else if (bytes > UNIT_GB)
{
bytes /= UNIT_GB;
strUnit = " GB";
}
// 保留小数点后两位;
return QString("%1%2").arg(QString::number(bytes, 'f', 2)).arg(strUnit);
}
// 设置文件信息;
void NewTaskWindow::setFileInfo(QString fileName, QString fileType, QString fileSize)
{
QTableWidgetItem *pTableItemName = new QTableWidgetItem();
pTableItemName->setText(fileName);
pTableItemName->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
ui.tableWidget->setItem(0, 0, pTableItemName);
QTableWidgetItem *pTableItemContent = new QTableWidgetItem();
pTableItemContent->setText(fileType);
pTableItemContent->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
ui.tableWidget->setItem(0, 1, pTableItemContent);
QTableWidgetItem *pTableItemTime = new QTableWidgetItem();
pTableItemTime->setText(fileSize);
pTableItemTime->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
ui.tableWidget->setItem(0, 2, pTableItemTime);
}
// 显示文件信息栏目;
void NewTaskWindow::showFileInfo(QString fileName , QString fileSize)
{
ui.pButtonBT->setVisible(false);
ui.pButtonPL->setVisible(false);
ui.lineEdit->setVisible(true);
ui.pButtonOpenFolder->setVisible(true);
ui.tableWidget->setVisible(true);
ui.tableWidget->clear();
// 设置表头样式的代码需添加在 确定表格行列数后 才能生效;
QStringList qsHeader;
qsHeader << QStringLiteral("文件名") << QStringLiteral("类型") << QStringLiteral("大小");
ui.tableWidget->setHorizontalHeaderLabels(qsHeader);
ui.tableWidget->insertRow(0);
ui.tableWidget->setRowHeight(0, 25);
QString fileType = QFileInfo(fileName).suffix();
setFileInfo(fileName, fileType, fileSize);
this->setFixedHeight(335);
}
// 隐藏文件信息栏目;
void NewTaskWindow::hideFileInfo()
{
ui.tableWidget->setVisible(false);
ui.lineEdit->setVisible(false);
ui.pButtonOpenFolder->setVisible(false);
ui.pButtonBT->setVisible(true);
ui.pButtonPL->setVisible(true);
this->setFixedHeight(250);
}
// 请求链接输入框文字发送变换,主要是用于输入链接后自动请求文件信息;
void NewTaskWindow::onTextChanged()
{
QString strUrl = ui.textEdit->toPlainText();
getFileTotalSize(strUrl);
}
// 打开文件选择框选择文件夹;
void NewTaskWindow::onOpenFolder()
{
QString filePath = QFileDialog::getExistingDirectory(this, QStringLiteral("迅雷下载"), m_filePath, QFileDialog::ShowDirsOnly);
if (!filePath.isEmpty())
{
m_filePath = filePath;
QString reaminSpace = QStringLiteral("剩余:%1").arg(getDiskFreeSpace(m_filePath));
m_remainSpaceLabel->setText(reaminSpace);
ui.lineEdit->setText(m_filePath);
}
}
界面设计图
代码分析
1、文件信息表格栏目
静态效果 (左为模仿的例子,右为迅雷窗口)
动态效果
本次界面的主要难点在于如何设置QTableWidget的样式,在网上找了半天资料,终于实现了跟迅雷一样的效果。
主要是去除去除原始边框,自己重新设置,在上面代码 initTableWidget()方法对其进行了初始化,但是样式并不满足,这就需要加载样式表。
样式表中主要是是控制鼠标对其进行操作比如鼠标选中、点击、悬浮等,不同状态下显示的样式。
/**********表头**********/
QHeaderView{
color: rgb(183 , 183 , 183);
background: transparent;
border: none;
min-height: 25px;
}
QHeaderView::section:horizontal {
background: transparent;
padding-left:6px;
border:none;
border-right: 1px solid rgb(202, 212, 219);
}
QHeaderView::section:horizontal:hover {
background: transparent;
}
QHeaderView::section:horizontal:pressed {
background: rgb(224, 224, 224);
}
/**********表格**********/
QTableView {
color:black;
border: 1px solid rgb(202, 212, 219);
border-top: transparent;
}
QTableView::item{
color:black;
background:transparent;
padding-left:5px;
}
QTableView::item:selected{
color:black;
background:transparent;
}
QTableView::item:hover{
color:black;
background:rgb(205,233,249);
}
2、文件路径栏目内嵌QLabel显示剩余空间
可以参考代码中initFilePathLineEdit()方法的实现。
3、获取文件信息
主要是通过发送HTTP请求得到文件的文件名以及文件大小,但是这个需要看文件服务器是否支持,并不是所有的Url链接都会返回文件名或者文件大小。
4、整个效果演示
下面我测试了几条下载链接,并获取到了文件的信息。有时候文件大小会返回0B,可以尝试重新输入Url,看返回结果,在代码中我设置了2s请求超时时钟,如果2s之后,服务器还是没返回结果就结束请求。这里可以根据需要自定义时长。
尾
花了近两个晚上的时间完成了迅雷新建任务窗口的模拟,外观效果基本上与迅雷一致(仍有欠缺,待完善),后期会继续模拟迅雷9的任务列表界面,以及实现真正的新建任务,并完成下载功能,敬请期待哈 O(∩_∩)O!
同时也很感谢一去、二三里群主大大博客的支持,赞一个!!!
Keep Moving 、 Keep Coding 、 啦啦啦 !!!