You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5重复文件扫描工具进度条无法实时更新问题求助

解决PyQt5重复文件扫描器进度条不更新问题

问题原因

  1. 冗余的QTimer机制:原本通过QTimer定时读取中间变量更新进度条,导致进度更新滞后,线程发送的进度值无法实时同步到UI,最终出现直接跳转到100%的现象。
  2. 浮点进度值处理:扫描线程发送的是浮点型进度值,直接传递给需要整数参数的QProgressBar,自动截断逻辑导致进度显示不连贯。
  3. 无效代码残留:代码中存在未使用的Thread类和signal_accept方法,属于复制代码冗余,干扰逻辑清晰度。

修复方案

  1. 移除QTimer,直接绑定信号到UI更新:删除定时器相关代码,将扫描线程的update_progress信号直接连接到进度条的setValue方法,实现实时更新。
  2. 统一进度值为整数:扫描线程内计算进度后转为整数再发送信号,避免浮点转整数的截断问题。
  3. 清理无效代码:删除未使用的冗余类和方法,简化代码结构。
  4. 增强用户体验:新增状态栏实时显示当前扫描的文件名和路径,添加文件访问异常处理避免扫描中断。

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 05:25:55