基于 Python Qt5 的多功能下载工具,这个工具支持多任务下载、暂停/继续、取消下载、进度显示和下载速度统计等核心功能。


QThreadPool 和 QRunnable 实现多线程下载,避免界面卡顿import sysimport osimport timeimport requestsfrom urllib.parse import urlparsefrom PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QTableWidget, QTableWidgetItem, QFileDialog, QHeaderView, QMessageBox, QLabel)from PyQt5.QtCore import ( Qt, QThread, pyqtSignal, QRunnable, QThreadPool, QObject, pyqtSlot)from PyQt5.QtGui import QIcon# 下载信号类(用于线程间通信)classDownloadSignals(QObject): progress = pyqtSignal(int, str, str, str) # 进度、速度、剩余时间、状态 finished = pyqtSignal(str) # 完成信号 error = pyqtSignal(str, str) # 错误信息# 下载任务类classDownloadTask(QRunnable):def__init__(self, url, save_path, task_id): super().__init__() self.url = url self.save_path = save_path self.task_id = task_id self.signals = DownloadSignals() self.is_paused = False self.is_cancelled = False self.downloaded_size = 0 self.file_size = 0# 暂停下载defpause(self): self.is_paused = True# 继续下载defresume(self): self.is_paused = False# 取消下载defcancel(self): self.is_cancelled = True# 计算文件大小(带单位)defformat_size(self, size): units = ['B', 'KB', 'MB', 'GB', 'TB'] unit_index = 0while size >= 1024and unit_index < len(units) - 1: size /= 1024 unit_index += 1returnf"{size:.2f}{units[unit_index]}"# 计算下载速度defformat_speed(self, bytes_per_second):return self.format_size(bytes_per_second) + "/s"# 计算剩余时间defcalculate_remaining_time(self, downloaded, total, speed):if speed == 0:return"未知" remaining_bytes = total - downloaded seconds = remaining_bytes / speedif seconds < 60:returnf"{seconds:.0f}秒"elif seconds < 3600:returnf"{seconds/60:.0f}分{seconds%60:.0f}秒"else:returnf"{seconds/3600:.0f}时{(seconds%3600)/60:.0f}分" @pyqtSlot()defrun(self):try:# 获取文件名 filename = os.path.basename(urlparse(self.url).path)ifnot filename: filename = f"download_{int(time.time())}.tmp" full_path = os.path.join(self.save_path, filename)# 检查是否支持断点续传 headers = {}if os.path.exists(full_path): self.downloaded_size = os.path.getsize(full_path) headers['Range'] = f"bytes={self.downloaded_size}-"# 发送请求 response = requests.get( self.url, headers=headers, stream=True, timeout=30 ) response.raise_for_status()# 获取文件总大小if'Content-Length'in response.headers: self.file_size = int(response.headers['Content-Length']) + self.downloaded_sizeelse: self.file_size = 0# 开始下载 chunk_size = 8192# 8KB start_time = time.time() downloaded_bytes = self.downloaded_sizewith open(full_path, 'ab') as f:for chunk in response.iter_content(chunk_size=chunk_size):# 检查是否暂停/取消if self.is_paused: self.signals.progress.emit( int(downloaded_bytes/self.file_size*100) if self.file_size else0,"0 B/s","暂停中","暂停" )while self.is_paused andnot self.is_cancelled: time.sleep(0.1)if self.is_cancelled: self.signals.progress.emit( int(downloaded_bytes/self.file_size*100) if self.file_size else0,"0 B/s","已取消","已取消" )return# 写入数据if chunk: f.write(chunk) downloaded_bytes += len(chunk)# 计算进度和速度 elapsed_time = time.time() - start_time speed = downloaded_bytes / elapsed_time if elapsed_time > 0else0 progress = int(downloaded_bytes/self.file_size*100) if self.file_size else0 remaining_time = self.calculate_remaining_time(downloaded_bytes, self.file_size, speed)# 发送进度信号 self.signals.progress.emit( progress, self.format_speed(speed), remaining_time,"下载中" )# 下载完成ifnot self.is_cancelled: self.signals.progress.emit(100, "0 B/s", "已完成", "已完成") self.signals.finished.emit(f"任务 {self.task_id} 下载完成:{full_path}")except Exception as e: self.signals.error.emit(self.task_id, str(e))# 主窗口类classDownloadManager(QMainWindow):def__init__(self): super().__init__() self.setWindowTitle("Qt5 多功能下载工具") self.setGeometry(100, 100, 900, 600)# 初始化变量 self.thread_pool = QThreadPool() self.thread_pool.setMaxThreadCount(5) # 最大并发数 self.download_tasks = {} # 存储下载任务 {task_id: DownloadTask} self.task_counter = 0 self.save_directory = os.path.join(os.path.expanduser("~"), "Downloads")# 创建界面 self.init_ui()definit_ui(self):# 中心部件 central_widget = QWidget() self.setCentralWidget(central_widget)# 主布局 main_layout = QVBoxLayout(central_widget)# 顶部控制栏 top_layout = QHBoxLayout()# URL输入框 self.url_input = QLineEdit() self.url_input.setPlaceholderText("输入下载链接") top_layout.addWidget(self.url_input)# 保存路径选择按钮 self.path_btn = QPushButton("选择保存路径") self.path_btn.clicked.connect(self.choose_save_path) top_layout.addWidget(self.path_btn)# 开始下载按钮 self.start_btn = QPushButton("开始下载") self.start_btn.clicked.connect(self.start_download) top_layout.addWidget(self.start_btn) main_layout.addLayout(top_layout)# 下载任务表格 self.task_table = QTableWidget() self.task_table.setColumnCount(7) self.task_table.setHorizontalHeaderLabels(["任务ID", "文件名", "进度", "速度", "剩余时间", "状态", "操作" ])# 设置列宽自适应 header = self.task_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.Stretch) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeToContents) header.setSectionResizeMode(4, QHeaderView.ResizeToContents) header.setSectionResizeMode(5, QHeaderView.ResizeToContents) header.setSectionResizeMode(6, QHeaderView.ResizeToContents) main_layout.addWidget(self.task_table)# 状态栏 self.status_bar = QLabel(f"保存路径:{self.save_directory} | 最大并发数:5") main_layout.addWidget(self.status_bar)# 选择保存路径defchoose_save_path(self): path = QFileDialog.getExistingDirectory(self, "选择保存路径", self.save_directory)if path: self.save_directory = path self.status_bar.setText(f"保存路径:{self.save_directory} | 最大并发数:5")# 开始下载defstart_download(self): url = self.url_input.text().strip()ifnot url: QMessageBox.warning(self, "警告", "请输入有效的下载链接!")return# 生成任务ID self.task_counter += 1 task_id = f"Task-{self.task_counter}"# 创建下载任务 task = DownloadTask(url, self.save_directory, task_id)# 连接信号 task.signals.progress.connect(self.update_progress) task.signals.finished.connect(self.on_task_finished) task.signals.error.connect(self.on_task_error)# 保存任务引用 self.download_tasks[task_id] = task# 添加到表格 row = self.task_table.rowCount() self.task_table.insertRow(row)# 获取文件名 filename = os.path.basename(urlparse(url).path) or"未知文件"# 设置表格内容 self.task_table.setItem(row, 0, QTableWidgetItem(task_id)) self.task_table.setItem(row, 1, QTableWidgetItem(filename)) self.task_table.setItem(row, 2, QTableWidgetItem("0%")) self.task_table.setItem(row, 3, QTableWidgetItem("0 B/s")) self.task_table.setItem(row, 4, QTableWidgetItem("计算中")) self.task_table.setItem(row, 5, QTableWidgetItem("等待中"))# 创建操作按钮 btn_layout = QHBoxLayout() pause_btn = QPushButton("暂停") pause_btn.clicked.connect(lambda: self.pause_task(task_id)) cancel_btn = QPushButton("取消") cancel_btn.clicked.connect(lambda: self.cancel_task(task_id)) btn_layout.addWidget(pause_btn) btn_layout.addWidget(cancel_btn) btn_widget = QWidget() btn_widget.setLayout(btn_layout) self.task_table.setCellWidget(row, 6, btn_widget)# 启动任务 self.thread_pool.start(task) self.task_table.setItem(row, 5, QTableWidgetItem("下载中")) self.url_input.clear()# 更新下载进度defupdate_progress(self, progress, speed, remaining_time, status):# 找到对应的任务行for row in range(self.task_table.rowCount()): task_id_item = self.task_table.item(row, 0)if task_id_item and task_id_item.text() in self.download_tasks: self.task_table.setItem(row, 2, QTableWidgetItem(f"{progress}%")) self.task_table.setItem(row, 3, QTableWidgetItem(speed)) self.task_table.setItem(row, 4, QTableWidgetItem(remaining_time)) self.task_table.setItem(row, 5, QTableWidgetItem(status))break# 暂停任务defpause_task(self, task_id):if task_id in self.download_tasks: self.download_tasks[task_id].pause()# 取消任务defcancel_task(self, task_id):if task_id in self.download_tasks: self.download_tasks[task_id].cancel()# 从表格中移除任务for row in range(self.task_table.rowCount()): task_id_item = self.task_table.item(row, 0)if task_id_item and task_id_item.text() == task_id: self.task_table.removeRow(row)breakdel self.download_tasks[task_id]# 任务完成回调defon_task_finished(self, message): QMessageBox.information(self, "提示", message)# 任务错误回调defon_task_error(self, task_id, error): QMessageBox.critical(self, "错误", f"任务 {task_id} 下载失败:{error}")# 从表格中移除失败任务for row in range(self.task_table.rowCount()): task_id_item = self.task_table.item(row, 0)if task_id_item and task_id_item.text() == task_id: self.task_table.setItem(row, 5, QTableWidgetItem("下载失败"))breakif __name__ == "__main__":# 确保中文显示正常if hasattr(Qt, 'AA_EnableHighDpiScaling'): QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)if hasattr(Qt, 'AA_UseHighDpiPixmaps'): QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) app = QApplication(sys.argv) window = DownloadManager() window.show() sys.exit(app.exec_())在运行代码前,需要安装以下依赖包:
pip install PyQt5 requests基础功能:
任务管理:
界面特性:
QThreadPool 实现多线程下载,避免界面卡顿,同时限制最大并发数防止资源耗尽DownloadTask 类中,通过信号与主界面通信,实时更新进度和状态页面存在不美观显示不全等示问题,并对界面进行全面美化,包括进度条样式、表格美化、颜色搭配、按钮样式等,让整个下载工具的界面更加专业和美观。
QProgressBar 替代文字进度,视觉效果更好import sysimport osimport timeimport requestsfrom urllib.parse import urlparsefrom PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QTableWidget, QTableWidgetItem, QFileDialog, QHeaderView, QMessageBox, QLabel, QProgressBar, QSizePolicy)from PyQt5.QtCore import ( Qt, QThread, pyqtSignal, QRunnable, QThreadPool, QObject, pyqtSlot, QTimer)from PyQt5.QtGui import QIcon, QFont, QColor, QBrush# 下载信号类(用于线程间通信)classDownloadSignals(QObject): progress = pyqtSignal(int, str, str, str) # 进度、速度、剩余时间、状态 finished = pyqtSignal(str) # 完成信号 error = pyqtSignal(str, str) # 错误信息# 下载任务类classDownloadTask(QRunnable):def__init__(self, url, save_path, task_id): super().__init__() self.url = url self.save_path = save_path self.task_id = task_id self.signals = DownloadSignals() self.is_paused = False self.is_cancelled = False self.downloaded_size = 0 self.file_size = 0# 暂停下载defpause(self): self.is_paused = True# 继续下载defresume(self): self.is_paused = False# 取消下载defcancel(self): self.is_cancelled = True# 计算文件大小(带单位)defformat_size(self, size): units = ['B', 'KB', 'MB', 'GB', 'TB'] unit_index = 0while size >= 1024and unit_index < len(units) - 1: size /= 1024 unit_index += 1returnf"{size:.2f}{units[unit_index]}"# 计算下载速度defformat_speed(self, bytes_per_second):return self.format_size(bytes_per_second) + "/s"# 计算剩余时间defcalculate_remaining_time(self, downloaded, total, speed):if speed == 0or total == 0:return"未知" remaining_bytes = total - downloaded seconds = remaining_bytes / speedif seconds < 60:returnf"{seconds:.0f}秒"elif seconds < 3600:returnf"{seconds/60:.0f}分{seconds%60:.0f}秒"else:returnf"{seconds/3600:.0f}时{(seconds%3600)/60:.0f}分" @pyqtSlot()defrun(self):try:# 获取文件名 filename = os.path.basename(urlparse(self.url).path)ifnot filename: filename = f"download_{int(time.time())}.tmp" full_path = os.path.join(self.save_path, filename)# 检查是否支持断点续传 headers = {}if os.path.exists(full_path): self.downloaded_size = os.path.getsize(full_path) headers['Range'] = f"bytes={self.downloaded_size}-"# 发送请求 response = requests.get( self.url, headers=headers, stream=True, timeout=30 ) response.raise_for_status()# 获取文件总大小if'Content-Length'in response.headers: self.file_size = int(response.headers['Content-Length']) + self.downloaded_sizeelse: self.file_size = 0# 开始下载 chunk_size = 8192# 8KB start_time = time.time() downloaded_bytes = self.downloaded_sizewith open(full_path, 'ab') as f:for chunk in response.iter_content(chunk_size=chunk_size):# 检查是否暂停/取消if self.is_paused: self.signals.progress.emit( int(downloaded_bytes/self.file_size*100) if self.file_size else0,"0 B/s","暂停中","暂停" )while self.is_paused andnot self.is_cancelled: time.sleep(0.1)if self.is_cancelled: self.signals.progress.emit( int(downloaded_bytes/self.file_size*100) if self.file_size else0,"0 B/s","已取消","已取消" )return# 写入数据if chunk: f.write(chunk) downloaded_bytes += len(chunk)# 计算进度和速度 elapsed_time = time.time() - start_time speed = downloaded_bytes / elapsed_time if elapsed_time > 0else0 progress = int(downloaded_bytes/self.file_size*100) if self.file_size else0 remaining_time = self.calculate_remaining_time(downloaded_bytes, self.file_size, speed)# 发送进度信号 self.signals.progress.emit( progress, self.format_speed(speed), remaining_time,"下载中" )# 下载完成ifnot self.is_cancelled: self.signals.progress.emit(100, "0 B/s", "已完成", "已完成") self.signals.finished.emit(f"任务 {self.task_id} 下载完成:{full_path}")except Exception as e: self.signals.error.emit(self.task_id, str(e))# 主窗口类classDownloadManager(QMainWindow):def__init__(self): super().__init__() self.setWindowTitle("Qt5 多功能下载工具") self.setGeometry(100, 100, 1000, 700)# 初始化变量 self.thread_pool = QThreadPool() self.thread_pool.setMaxThreadCount(5) # 最大并发数 self.download_tasks = {} # 存储下载任务 {task_id: DownloadTask} self.task_counter = 0 self.save_directory = os.path.join(os.path.expanduser("~"), "Downloads") self.task_buttons = {} # 存储任务按钮 {task_id: (pause_btn, cancel_btn)}# 创建界面 self.init_ui()definit_ui(self):# 设置全局字体 font = QFont("Microsoft YaHei", 9) QApplication.setFont(font)# 中心部件 central_widget = QWidget() self.setCentralWidget(central_widget) central_widget.setStyleSheet(""" QWidget { background-color: #f5f5f7; } """)# 主布局 main_layout = QVBoxLayout(central_widget) main_layout.setSpacing(15) main_layout.setContentsMargins(20, 20, 20, 20)# 标题 title_label = QLabel("多功能下载管理器") title_label.setAlignment(Qt.AlignCenter) title_label.setStyleSheet(""" QLabel { font-size: 18px; font-weight: bold; color: #2c3e50; padding: 10px 0; } """) main_layout.addWidget(title_label)# 顶部控制栏 top_widget = QWidget() top_widget.setStyleSheet(""" QWidget { background-color: white; border-radius: 8px; padding: 15px; } """) top_layout = QHBoxLayout(top_widget) top_layout.setSpacing(10)# URL输入框 self.url_input = QLineEdit() self.url_input.setPlaceholderText("请输入下载链接 (支持HTTP/HTTPS)") self.url_input.setStyleSheet(""" QLineEdit { padding: 10px 15px; border: 1px solid #e0e0e0; border-radius: 6px; font-size: 10px; background-color: #ffffff; } QLineEdit:focus { border-color: #3498db; outline: none; } """) self.url_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) top_layout.addWidget(self.url_input)# 保存路径选择按钮 self.path_btn = QPushButton("选择保存路径") self.path_btn.setStyleSheet(""" QPushButton { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 6px; font-size: 10px; } QPushButton:hover { background-color: #2980b9; } QPushButton:pressed { background-color: #1f618d; } """) self.path_btn.clicked.connect(self.choose_save_path) top_layout.addWidget(self.path_btn)# 开始下载按钮 self.start_btn = QPushButton("开始下载") self.start_btn.setStyleSheet(""" QPushButton { background-color: #2ecc71; color: white; border: none; padding: 10px 25px; border-radius: 6px; font-size: 10px; font-weight: bold; } QPushButton:hover { background-color: #27ae60; } QPushButton:pressed { background-color: #219653; } QPushButton:disabled { background-color: #bdc3c7; } """) self.start_btn.clicked.connect(self.start_download) top_layout.addWidget(self.start_btn) main_layout.addWidget(top_widget)# 下载任务表格 self.task_table = QTableWidget() self.task_table.setColumnCount(7) self.task_table.setHorizontalHeaderLabels(["任务ID", "文件名", "下载进度", "速度", "剩余时间", "状态", "操作" ])# 表格样式 self.task_table.setStyleSheet(""" QTableWidget { background-color: white; border: none; border-radius: 8px; gridline-color: #eeeeee; } QHeaderView::section { background-color: #34495e; color: white; padding: 10px; border: none; font-weight: bold; } QTableWidget::item { padding: 5px; border: none; } QTableWidget::item:selected { background-color: #e8f4fd; color: #2c3e50; } QScrollBar:vertical { background-color: #f5f5f7; width: 8px; border-radius: 4px; } QScrollBar::handle:vertical { background-color: #bdc3c7; border-radius: 4px; } QScrollBar::handle:vertical:hover { background-color: #95a5a6; } """)# 设置列宽 header = self.task_table.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # 任务ID header.setSectionResizeMode(1, QHeaderView.Stretch) # 文件名 header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # 进度条 header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # 速度 header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # 剩余时间 header.setSectionResizeMode(5, QHeaderView.ResizeToContents) # 状态 header.setSectionResizeMode(6, QHeaderView.ResizeToContents) # 操作# 设置行高 self.task_table.verticalHeader().setDefaultSectionSize(40) self.task_table.verticalHeader().setVisible(False) # 隐藏行号 main_layout.addWidget(self.task_table)# 状态栏 self.status_bar = QLabel(f"📁 保存路径:{self.save_directory} | 🚀 最大并发数:5 | 📊 任务数:0") self.status_bar.setStyleSheet(""" QLabel { padding: 8px 15px; background-color: white; border-radius: 6px; color: #7f8c8d; } """) main_layout.addWidget(self.status_bar)# 选择保存路径defchoose_save_path(self): path = QFileDialog.getExistingDirectory(self, "选择保存路径", self.save_directory)if path: self.save_directory = path self.update_status_bar()# 更新状态栏defupdate_status_bar(self): task_count = len(self.download_tasks) self.status_bar.setText(f"📁 保存路径:{self.save_directory} | 🚀 最大并发数:5 | 📊 任务数:{task_count}")# 开始下载defstart_download(self): url = self.url_input.text().strip()ifnot url: QMessageBox.warning(self, "警告", "请输入有效的下载链接!", QMessageBox.Ok)return# 禁用开始按钮防止重复点击 self.start_btn.setEnabled(False) QTimer.singleShot(500, lambda: self.start_btn.setEnabled(True))# 生成任务ID self.task_counter += 1 task_id = f"Task-{self.task_counter}"# 创建下载任务 task = DownloadTask(url, self.save_directory, task_id)# 连接信号 task.signals.progress.connect(self.update_progress) task.signals.finished.connect(self.on_task_finished) task.signals.error.connect(self.on_task_error)# 保存任务引用 self.download_tasks[task_id] = task# 添加到表格 row = self.task_table.rowCount() self.task_table.insertRow(row)# 获取文件名 filename = os.path.basename(urlparse(url).path) or"未知文件"if len(filename) > 30: # 文件名过长截断 filename = filename[:27] + "..."# 设置表格内容 self.task_table.setItem(row, 0, QTableWidgetItem(task_id)) self.task_table.setItem(row, 1, QTableWidgetItem(filename))# 进度条 progress_bar = QProgressBar() progress_bar.setRange(0, 100) progress_bar.setValue(0) progress_bar.setStyleSheet(""" QProgressBar { border: none; border-radius: 10px; text-align: center; height: 20px; background-color: #ecf0f1; } QProgressBar::chunk { border-radius: 10px; background-color: #3498db; } """) self.task_table.setCellWidget(row, 2, progress_bar)# 速度、剩余时间、状态 self.task_table.setItem(row, 3, QTableWidgetItem("0 B/s")) self.task_table.setItem(row, 4, QTableWidgetItem("计算中")) status_item = QTableWidgetItem("等待中") status_item.setTextAlignment(Qt.AlignCenter) status_item.setForeground(QBrush(QColor("#3498db"))) self.task_table.setItem(row, 5, status_item)# 创建操作按钮 btn_widget = QWidget() btn_layout = QHBoxLayout(btn_widget) btn_layout.setSpacing(8) btn_layout.setContentsMargins(5, 0, 5, 0) btn_layout.setAlignment(Qt.AlignCenter)# 暂停/继续按钮 pause_btn = QPushButton("暂停") pause_btn.setFixedSize(70, 30) pause_btn.setStyleSheet(""" QPushButton { background-color: #f39c12; color: white; border: none; border-radius: 4px; font-size: 9px; } QPushButton:hover { background-color: #e67e22; } """) pause_btn.clicked.connect(lambda: self.toggle_pause_resume(task_id, pause_btn))# 取消按钮 cancel_btn = QPushButton("取消") cancel_btn.setFixedSize(70, 30) cancel_btn.setStyleSheet(""" QPushButton { background-color: #e74c3c; color: white; border: none; border-radius: 4px; font-size: 9px; } QPushButton:hover { background-color: #c0392b; } """) cancel_btn.clicked.connect(lambda: self.cancel_task(task_id)) btn_layout.addWidget(pause_btn) btn_layout.addWidget(cancel_btn) self.task_table.setCellWidget(row, 6, btn_widget)# 保存按钮引用 self.task_buttons[task_id] = (pause_btn, cancel_btn)# 启动任务 self.thread_pool.start(task)# 更新状态 status_item.setText("下载中") status_item.setForeground(QBrush(QColor("#2ecc71")))# 清空输入框 self.url_input.clear()# 更新状态栏 self.update_status_bar()# 切换暂停/继续deftoggle_pause_resume(self, task_id, btn):if task_id in self.download_tasks: task = self.download_tasks[task_id]if task.is_paused: task.resume() btn.setText("暂停") btn.setStyleSheet(""" QPushButton { background-color: #f39c12; color: white; border: none; border-radius: 4px; font-size: 9px; } QPushButton:hover { background-color: #e67e22; } """)# 更新状态for row in range(self.task_table.rowCount()):if self.task_table.item(row, 0).text() == task_id: status_item = self.task_table.item(row, 5) status_item.setText("下载中") status_item.setForeground(QBrush(QColor("#2ecc71")))breakelse: task.pause() btn.setText("继续") btn.setStyleSheet(""" QPushButton { background-color: #27ae60; color: white; border: none; border-radius: 4px; font-size: 9px; } QPushButton:hover { background-color: #219653; } """)# 更新下载进度defupdate_progress(self, progress, speed, remaining_time, status):# 找到对应的任务行for row in range(self.task_table.rowCount()): task_id_item = self.task_table.item(row, 0)if task_id_item and task_id_item.text() in self.download_tasks:# 更新进度条 progress_bar = self.task_table.cellWidget(row, 2)if progress_bar: progress_bar.setValue(progress) progress_bar.setFormat(f"{progress}%")# 更新速度 speed_item = self.task_table.item(row, 3) speed_item.setText(speed)# 更新剩余时间 time_item = self.task_table.item(row, 4) time_item.setText(remaining_time)# 更新状态(带颜色) status_item = self.task_table.item(row, 5) status_item.setText(status)# 设置状态颜色if status == "下载中": status_item.setForeground(QBrush(QColor("#2ecc71"))) # 绿色elif status == "暂停": status_item.setForeground(QBrush(QColor("#f39c12"))) # 橙色elif status == "已完成": status_item.setForeground(QBrush(QColor("#27ae60"))) # 深绿elif status == "已取消": status_item.setForeground(QBrush(QColor("#95a5a6"))) # 灰色else: status_item.setForeground(QBrush(QColor("#7f8c8d"))) # 浅灰break# 取消任务defcancel_task(self, task_id):if task_id in self.download_tasks:# 确认取消 reply = QMessageBox.question(self, "确认", f"确定要取消任务 {task_id} 吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)if reply == QMessageBox.Yes: self.download_tasks[task_id].cancel()# 从表格中移除任务for row in range(self.task_table.rowCount()): task_id_item = self.task_table.item(row, 0)if task_id_item and task_id_item.text() == task_id: self.task_table.removeRow(row)break# 清理引用if task_id in self.task_buttons:del self.task_buttons[task_id]del self.download_tasks[task_id]# 更新状态栏 self.update_status_bar()# 任务完成回调defon_task_finished(self, message): QMessageBox.information(self, "下载完成", message, QMessageBox.Ok) self.update_status_bar()# 任务错误回调defon_task_error(self, task_id, error): QMessageBox.critical(self, "下载失败", f"任务 {task_id} 下载失败:\n{error}", QMessageBox.Ok)# 更新状态为失败for row in range(self.task_table.rowCount()): task_id_item = self.task_table.item(row, 0)if task_id_item and task_id_item.text() == task_id: status_item = self.task_table.item(row, 5) status_item.setText("下载失败") status_item.setForeground(QBrush(QColor("#e74c3c"))) # 红色# 禁用操作按钮if task_id in self.task_buttons: pause_btn, cancel_btn = self.task_buttons[task_id] pause_btn.setEnabled(False) pause_btn.setStyleSheet(""" QPushButton { background-color: #bdc3c7; color: white; border: none; border-radius: 4px; font-size: 9px; } """)break self.update_status_bar()if __name__ == "__main__":# 确保中文显示正常if hasattr(Qt, 'AA_EnableHighDpiScaling'): QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)if hasattr(Qt, 'AA_UseHighDpiPixmaps'): QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) app = QApplication(sys.argv)# 设置应用程序样式 app.setStyle("Fusion") window = DownloadManager() window.show() sys.exit(app.exec_())修复按钮显示问题
QWidget 包裹按钮布局,确保按钮在表格单元格中正常显示界面美化
QProgressBar 替代文字进度显示,视觉效果更直观交互体验优化
样式细节优化
pip install PyQt5 requests