PyQt5重复文件扫描工具进度条无法实时更新问题求助
解决PyQt5重复文件扫描器进度条不更新问题
问题原因
- 冗余的QTimer机制:原本通过QTimer定时读取中间变量更新进度条,导致进度更新滞后,线程发送的进度值无法实时同步到UI,最终出现直接跳转到100%的现象。
- 浮点进度值处理:扫描线程发送的是浮点型进度值,直接传递给需要整数参数的QProgressBar,自动截断逻辑导致进度显示不连贯。
- 无效代码残留:代码中存在未使用的
Thread类和signal_accept方法,属于复制代码冗余,干扰逻辑清晰度。
修复方案
- 移除QTimer,直接绑定信号到UI更新:删除定时器相关代码,将扫描线程的
update_progress信号直接连接到进度条的setValue方法,实现实时更新。 - 统一进度值为整数:扫描线程内计算进度后转为整数再发送信号,避免浮点转整数的截断问题。
- 清理无效代码:删除未使用的冗余类和方法,简化代码结构。
- 增强用户体验:新增状态栏实时显示当前扫描的文件名和路径,添加文件访问异常处理避免扫描中断。
修改后的完整代码
import sys import os from collections import defaultdict from PyQt5 import QtCore, QtWidgets from PyQt5.QtWidgets import QFileDialog, QTreeWidgetItem, QVBoxLayout, QPushButton, QProgressBar, QApplication from PyQt5.QtCore import QThread, pyqtSignal class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(800, 600) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") self.inputLayout = QtWidgets.QHBoxLayout() self.inputLayout.setObjectName("inputLayout") self.directoryLabel = QtWidgets.QLabel(self.centralwidget) self.directoryLabel.setObjectName("directoryLabel") self.inputLayout.addWidget(self.directoryLabel) self.directoryLineEdit = QtWidgets.QLineEdit(self.centralwidget) self.directoryLineEdit.setObjectName("directoryLineEdit") self.inputLayout.addWidget(self.directoryLineEdit) self.browseButton = QtWidgets.QPushButton(self.centralwidget) self.browseButton.setObjectName("browseButton") self.inputLayout.addWidget(self.browseButton) self.scanButton = QtWidgets.QPushButton(self.centralwidget) self.scanButton.setEnabled(False) self.scanButton.setObjectName("scanButton") self.inputLayout.addWidget(self.scanButton) self.stopButton = QtWidgets.QPushButton(self.centralwidget) self.stopButton.setVisible(False) self.stopButton.setObjectName("stopButton") self.inputLayout.addWidget(self.stopButton) self.verticalLayout.addLayout(self.inputLayout) self.treeWidget = QtWidgets.QTreeWidget(self.centralwidget) self.treeWidget.setObjectName("treeWidget") self.treeWidget.headerItem().setText(0, "File's name") self.treeWidget.headerItem().setText(1, "Size") self.treeWidget.headerItem().setText(2, "Folder") self.treeWidget.headerItem().setText(3, "Path") self.verticalLayout.addWidget(self.treeWidget) self.statusLayout = QtWidgets.QVBoxLayout() self.statusLayout.setObjectName("statusLayout") self.statusbar1 = QtWidgets.QLabel(self.centralwidget) self.statusbar1.setObjectName("statusbar1") self.statusLayout.addWidget(self.statusbar1) self.statusbar2 = QtWidgets.QLabel(self.centralwidget) self.statusbar2.setObjectName("statusbar2") self.statusLayout.addWidget(self.statusbar2) self.progressBar = QtWidgets.QProgressBar(self.centralwidget) self.progressBar.setVisible(False) self.statusLayout.addWidget(self.progressBar) self.verticalLayout.addLayout(self.statusLayout) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "File Duplicate Scanner")) self.directoryLabel.setText(_translate("MainWindow", "Directory:")) self.browseButton.setText(_translate("MainWindow", "Browse")) self.scanButton.setText(_translate("MainWindow", "Scan")) self.stopButton.setText(_translate("MainWindow", "Stop")) class ScanThread(QThread): update_progress = QtCore.pyqtSignal(int) update_status = QtCore.pyqtSignal(str, str) scan_complete = QtCore.pyqtSignal(defaultdict) def __init__(self, directory, parent=None): self.directory = directory self.stop_requested = False super(ScanThread, self).__init__(parent) def run(self): file_sizes = defaultdict(list) total_files = sum([len(files) for r, d, files in os.walk(self.directory) if not self.stop_requested]) if total_files == 0: self.scan_complete.emit(file_sizes) return current_file = 0 for root, dirs, files in os.walk(self.directory): if self.stop_requested: break for file in files: if self.stop_requested: break file_path = os.path.join(root, file) try: file_size = os.path.getsize(file_path) except OSError: continue file_sizes[file_size].append((file_path, os.path.basename(root), file)) current_file += 1 progress = int((current_file / total_files) * 100) self.update_progress.emit(progress) self.update_status.emit(file, file_path) if not self.stop_requested: self.scan_complete.emit(file_sizes) def stop(self): self.stop_requested = True class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, *args, **kwargs): QtWidgets.QMainWindow.__init__(self, *args, **kwargs) self.setupUi(self) self.scan_thread = None self.browseButton.clicked.connect(self.browse) self.scanButton.clicked.connect(self.scan) self.stopButton.clicked.connect(self.stop_scan) self.directoryLineEdit.textChanged.connect(self.enable_scan_button) def format_file_size(self, size): if size < 1024 * 1024: return f"{size / 1024:.2f} KB" else: return f"{size / 1024 / 1024:.2f} MB" def browse(self): directory = QFileDialog.getExistingDirectory() if directory: self.directoryLineEdit.setText(directory) def enable_scan_button(self): if self.directoryLineEdit.text(): self.scanButton.setEnabled(True) else: self.scanButton.setEnabled(False) def paste_from_clipboard(self): clipboard = QtWidgets.QApplication.clipboard().text() if os.path.isdir(clipboard): self.directoryLineEdit.setText(clipboard) def scan(self): directory = self.directoryLineEdit.text() if directory: self.treeWidget.clear() self.statusbar1.clear() self.statusbar2.clear() self.progressBar.setMaximum(100) self.progressBar.setValue(0) self.progressBar.setVisible(True) self.scanButton.setVisible(False) self.stopButton.setVisible(True) self.scan_thread = ScanThread(directory) self.scan_thread.update_progress.connect(self.progressBar.setValue) self.scan_thread.update_status.connect(self.update_status) self.scan_thread.scan_complete.connect(self.on_scan_complete) self.scan_thread.start() def stop_scan(self): if self.scan_thread: self.scan_thread.stop() self.stopButton.setVisible(False) self.scanButton.setVisible(True) self.progressBar.setVisible(False) def update_status(self, file_name, file_path): self.statusbar1.setText(f"Scanning: {file_name}") self.statusbar2.setText(f"Path: {file_path}") def on_scan_complete(self, file_sizes): self.progressBar.setValue(100) self.progressBar.setVisible(False) self.statusbar1.clear() self.statusbar2.clear() duplicates = 0 duplicates_size = 0 for size, files in file_sizes.items(): if len(files) > 1: duplicates += len(files) - 1 duplicates_size += (len(files) - 1) * size total_files = sum(len(files) for files in file_sizes.values()) duplicates_percentage = (duplicates / total_files) * 100 if total_files > 0 else 0 self.statusbar1.setText(f"Total Files: {total_files}, Duplicate Amount: {duplicates}") self.statusbar2.setText( f"Duplicate Size: {self.format_file_size(duplicates_size)}, Duplicate Percentage: {duplicates_percentage:.2f}%") for size, files in file_sizes.items(): if len(files) > 1: original = QTreeWidgetItem(self.treeWidget) original.setText(0, files[0][2]) original.setText(1, self.format_file_size(size)) original.setText(2, files[0][1]) original.setText(3, files[0][0]) for duplicate in files[1:]: dup = QTreeWidgetItem(original) dup.setText(0, duplicate[2]) dup.setText(1, self.format_file_size(size)) dup.setText(2, duplicate[1]) dup.setText(3, duplicate[0]) self.treeWidget.expandItem(original) self.treeWidget.collapseAll() self.stopButton.setVisible(False) self.scanButton.setVisible(True) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
额外优化说明
- 新增
update_status信号,实时展示当前扫描的文件信息,提升交互体验。 - 添加文件访问异常捕获,跳过无法读取的文件,避免扫描流程中断。
- 修复了拼写错误(Dublicate → Duplicate),统一术语表述。
- 处理总文件数为0的边界情况,避免除以0的运行时错误。
内容的提问来源于stack exchange,提问作者ALM
相关产品推荐
相关产品推荐

