首页 > 解决方案 > 在 PyQt5 中合并插槽

问题描述

我怎样才能减少pyqtSlot()功能的数量,这样我就不需要每个都有两个?似乎应该有更好的方法来做到这一点,但我一直无法弄清楚。

该代码需要两个文件,在不同的线程上读取每个文件,并将输出打印到不同的QPlainTextEdit对象。

import sys
import time
import traceback
import pandas as pd

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

from compare_files_gui import Ui_MainWindow


class WorkerSignals(QObject):
    """Defines signals from running worker thread."""
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(str)
    progress = pyqtSignal(str)
    bar = pyqtSignal(int)


class Worker(QRunnable):
    """Worker thread."""
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        self.kwargs['progress_callback'] = self.signals.progress
        self.kwargs['pbar'] = self.signals.bar

    @pyqtSlot()
    def run(self):
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)
        finally:
            self.signals.finished.emit()


class MainWindow(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.pushButton.clicked.connect(self.compare)
        self.ui.file1_progressBar.setValue(0)
        self.ui.file2_progressBar.setValue(0)

        self.ui.file1_lineEdit.setText('file1.csv')
        self.ui.file2_lineEdit.setText('file2.csv')
        self.file1 = self.ui.file1_lineEdit.text()
        self.file2 = self.ui.file2_lineEdit.text()

        self.threadpool = QThreadPool()

    ##### How can I consolidate the following slots so I don't need to
    ##### have 2, one for each console object?
    @pyqtSlot(str)
    def progress_fn1(self, n):
        self.ui.console1_plainTextEdit.appendPlainText(n)

    @pyqtSlot(str)
    def progress_fn2(self, n):
        self.ui.console2_plainTextEdit.appendPlainText(n)

    @pyqtSlot(str)
    def print_output1(self, s):
        self.ui.console1_plainTextEdit.appendPlainText(s)

    @pyqtSlot(str)
    def print_output2(self, s):
        self.ui.console2_plainTextEdit.appendPlainText(s)

    @pyqtSlot()
    def thread_complete1(self):
        self.ui.console1_plainTextEdit.appendPlainText('Processing complete!')

    @pyqtSlot()
    def thread_complete2(self):
        self.ui.console2_plainTextEdit.appendPlainText('Processing complete!')

    @pyqtSlot(int)
    def update_progress1(self, v):
        self.ui.file1_progressBar.setValue(v)

    @pyqtSlot(int)
    def update_progress2(self, v):
        self.ui.file2_progressBar.setValue(v)

    def compare(self):
        # files = [self.ui.file1_lineEdit.text(), self.ui.file2_lineEdit.text()]
        files = [self.file1, self.file2]

        # Start new thread for each file
        for i, file in enumerate(files, 1):
            worker = Worker(self.process_file, file)

            #### Is there a better way to do this?
            if i == 1:
                worker.signals.progress.connect(self.progress_fn1)
                worker.signals.result.connect(self.print_output1)
                worker.signals.finished.connect(self.thread_complete1)
                worker.signals.bar.connect(self.update_progress1)
            elif i == 2:
                worker.signals.progress.connect(self.progress_fn2)
                worker.signals.result.connect(self.print_output2)
                worker.signals.finished.connect(self.thread_complete2)
                worker.signals.bar.connect(self.update_progress2)
            else:
                pass

            # Execute thread
            self.threadpool.start(worker)

    def process_file(self, file, pbar, progress_callback):
        """Process file and emit signals."""
        t0 = time.time()
        progress_callback.emit(f'Processing {file}')

        df = pd.read_csv(file, header=None, names=['col'])

        num = len(df.index)

        for i, (index, row) in enumerate(df.iterrows(), 1):
            progress_callback.emit('  ' + row['col'])
            pbar.emit(int(i*100/num))
            time.sleep(0.25)

        t1 = time.time()

        return f'Time to complete: {round(t1-t0, 3)} s'


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())

标签: pythonpython-3.xpyqtpyqt5

解决方案


为了解决这个问题,我将与每个文件关联的对象QPlainTextEditQProgressBar对象传递给线程,然后从线程发出这些对象,以便我可以确定QPlainTextEdit要打印到哪个对象。从我在线阅读的内容来看,我认为将 UI 对象传递给后台线程并不是最佳实践,但它适用于这个简单的应用程序,而且我还没有找到更好的方法来做到这一点。

import sys
import time
import traceback
import pandas as pd

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

from compare_files_gui import Ui_MainWindow


class WorkerSignals(QObject):
    """Defines signals from running worker thread."""
    finished = pyqtSignal(object)
    error = pyqtSignal(tuple)
    result = pyqtSignal(object, str)
    progress = pyqtSignal(object, str)
    bar = pyqtSignal(object, int)


class Worker(QRunnable):
    """Worker thread."""
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add callbacks to kwargs
        self.kwargs['progress_callback'] = self.signals.progress
        self.kwargs['pbar_callback'] = self.signals.bar

    @pyqtSlot()
    def run(self):
        try:
            console, result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(console, result)
        finally:
            self.signals.finished.emit(console)


class MainWindow(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Initialize progress bars
        self.ui.file1_progressBar.setValue(0)
        self.ui.file2_progressBar.setValue(0)

        # Connect widgets to slots
        self.ui.pushButton.clicked.connect(self.compare)

        # Create instance of QThreadPool
        self.threadpool = QThreadPool()


    @pyqtSlot(object, str)
    def progress_fn(self, console, n):
        """Print progress string to specific QPlainTextEdit object."""
        console.appendPlainText(n)

    @pyqtSlot(object, str)
    def print_output(self, console, s):
        """Print result string to specific QPlainTextEdit object."""
        console.appendPlainText(s)

    @pyqtSlot(object)
    def thread_complete(self, console):
        """Print completion text to specific QPlainTextEdit object."""
        console.appendPlainText('Processing complete!')

    @pyqtSlot(object, int)
    def update_progress(self, pbar, v):
        """Set value of QProgressBar object."""
        pbar.setValue(v)

    def compare(self):
        """Send each file and associated UI objects to thread."""
        # Store files in list
        # files = [self.ui.file1_lineEdit.text(), self.ui.file2_lineEdit.text()]
        files = [self.file1, self.file2]

        # Store QPlainTextEdit and QProgressBar objects in list
        consoles = [self.ui.console1_plainTextEdit, self.ui.console2_plainTextEdit]
        pbars = [self.ui.file1_progressBar, self.ui.file2_progressBar]

        # Start new thread for each file
        for i, (file, console, pbar) in enumerate(zip(files, consoles, pbars), 1):
            worker = Worker(self.process_file, file, console, pbar)

            # Connect thread signals to slots in UI thread
            worker.signals.progress.connect(self.progress_fn)
            worker.signals.result.connect(self.print_output)
            worker.signals.finished.connect(self.thread_complete)
            worker.signals.bar.connect(self.update_progress)

            # Execute thread
            self.threadpool.start(worker)

    def process_file(self, file, console, pbar, pbar_callback, progress_callback):
        """Process file and emit signals."""
        t0 = time.time()

        progress_callback.emit(console, f'Processing {file}')

        # Read file into dataframe
        df = pd.read_csv(file, header=None, names=['col'])

        # Iterate over each row and emit value of column and progress
        num = len(df.index)
        for i, (index, row) in enumerate(df.iterrows(), 1):
            progress_callback.emit(console, '  ' + row['col'])
            pbar_callback.emit(pbar, int(i*100/num))

            # Slow down response
            time.sleep(0.25)

        t1 = time.time()

        # Return QPlainTextEdit object and string
        return console, f'Time to complete: {round(t1-t0, 3)} s'


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())

推荐阅读