PyQt5 Gui Поток к работнику Сигнал потока / слот - PullRequest
1 голос
/ 13 января 2020

Я пытаюсь установить простую связь между рабочим потоком, в данном случае он называется WorkToDo через механизм сигналов и слотов PyQt5. Я могу надежно отправить данные от Worker в поток Gui через этот механизм, но я не могу сделать то же самое в поток gui. Из моего исследования я обнаружил, что это связано с тем, что я переопределил функцию run своей собственной логикой c. Мой вопрос, есть ли способ вручную обработать выполнение сигналов в рабочем потоке? Есть ли лучший способ выполнить sh это?

РЕДАКТИРОВАТЬ :
Я на самом деле не переопределяю run, поскольку я не вижу run, перечисленных в документации для QObject. Я ошибаюсь, так как это функция по умолчанию для объекта QThread. Это более озадачивает для меня. Если только я не пойму это неправильно.

import sys
import time
import qdarkstyle

from PyQt5.QtWidgets import (
                            QWidget, QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QHBoxLayout, QTabWidget,
                            QComboBox, QProgressBar, QApplication, QLabel, QGroupBox, QFileDialog, QTextEdit,
                            QStyleFactory, QFormLayout
                            )

from PyQt5 import (Qt, QtCore)

from PyQt5.QtCore import *


class Command:
    def __init__(self, **kwargs):
        self.cmd = kwargs.get('cmd', None)
        self.args = kwargs.get('args')

class CommandSignals(QObject):
    command = pyqtSignal(Command)

class WorkToDo(QObject):
    def __init__(self):
        super().__init__()
        self.counter = 0
        self.signals = CommandSignals()

    @QtCore.pyqtSlot(Command)
    def process_command(self, command):
        """
        @brief       process update from gui thread
        @param       self     self
        @param       command  command
        @return      none
        """
        print(f'Update from GUI: {command.__dict__}')
        if command.cmd == 'add_to_counter':
            self.counter = self.counter + command.args.get('addition', 0)

    @pyqtSlot()
    def run(self):
        """
        @brief       actual thread function to run,
                     started via thread.started signal (why its decorated with a pyqtSlot)
        @param       self  obj ref
        @return      none
        """
        while True:
            QThread.sleep(1)
            # emit text change signal to main gui thread
            cmd = Command(
                cmd = 'update_text',
                args = {
                    'text': f'Hello update {self.counter}'
                }
            )
            print(f'Update from worker: {cmd.__dict__}')
            self.signals.command.emit(cmd)
            self.counter += 1


class Gui(QWidget):
    def __init__(self):
        super().__init__()
        """ make gui elements """
        layout = QFormLayout()
        self.text_line = QLineEdit()
        self.add_button = QPushButton('Add 10 To Counter')
        layout.addRow(self.text_line)
        layout.addRow(self.add_button)

        self.add_button.clicked.connect(self.issue_button_update)
        self.signals = CommandSignals()
        self.MakeThreadWorker()

        """ finalize gui """
        self.setLayout(layout)
        self.setWindowTitle('Sync Thread Command/Response test')
        self.show()


    def MakeThreadWorker(self):
        self.worker_thread = QThread()
        self.worker = WorkToDo()

        """ incoming gui update command, works """
        self.worker.signals.command.connect(self.process_command)
        """ outgoing update to worker thread, does not work """
        self.signals.command.connect(self.worker.process_command)

        """ signal to start the thread function, works """
        self.worker_thread.started.connect(self.worker.run)

        self.worker_thread.start()
        self.worker.moveToThread(self.worker_thread)


    @pyqtSlot(Command)
    def process_command(self, command):
        """
        @brief       process update from work thread
        @param       self     self
        @param       command  command object
        @return      none
        """
        if command.cmd == 'update_text':
            text_update = command.args.get('text', '')
            self.text_line.setText(text_update)

    def issue_button_update(self):
        cmd = Command(
            cmd = 'add_to_counter',
            args = {
                'addition': 10
            }
        )
        print('Button Clicked!')
        self.signals.command.emit(cmd)


if __name__ == '__main__':
    APPLICATION = QApplication([])
    APPLICATION.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
    gui = Gui()
    sys.exit(APPLICATION.exec_())

1 Ответ

0 голосов
/ 13 января 2020

Проблема в том, что с True, когда вы блокируете событие l oop вторичного потока, препятствующее получению сигналов, если вы хотите выполнить задачу periodi c, тогда используйте QTimer.

import sys
import time
import qdarkstyle

from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QThread, QTimer
from PyQt5.QtWidgets import QApplication, QFormLayout, QLineEdit, QPushButton, QWidget


class Command:
    def __init__(self, **kwargs):
        self.cmd = kwargs.get("cmd", None)
        self.args = kwargs.get("args")


class CommandSignals(QObject):
    command = pyqtSignal(Command)


class WorkToDo(QObject):
    def __init__(self):
        super().__init__()
        self.counter = 0
        self.signals = CommandSignals()
        self.timer = QTimer(self, timeout=self.run, interval=1000)

    @pyqtSlot()
    def start(self):
        self.timer.start()

    @pyqtSlot()
    def stop(self):
        self.timer.stop()

    @pyqtSlot(Command)
    def process_command(self, command):
        """
        @brief       process update from gui thread
        @param       self     self
        @param       command  command
        @return      none
        """
        print(f"Update from GUI: {command.__dict__}")
        if command.cmd == "add_to_counter":
            self.counter = self.counter + command.args.get("addition", 0)

    @pyqtSlot()
    def run(self):
        print(self.thread(), self.timer.thread())
        cmd = Command(cmd="update_text", args={"text": f"Hello update {self.counter}"})
        print(f"Update from worker: {cmd.__dict__}")
        self.signals.command.emit(cmd)
        self.counter += 1


class Gui(QWidget):
    def __init__(self):
        super().__init__()
        """ make gui elements """
        layout = QFormLayout()
        self.text_line = QLineEdit()
        self.add_button = QPushButton("Add 10 To Counter")
        layout.addRow(self.text_line)
        layout.addRow(self.add_button)

        self.add_button.clicked.connect(self.issue_button_update)
        self.signals = CommandSignals()
        self.MakeThreadWorker()

        """ finalize gui """
        self.setLayout(layout)
        self.setWindowTitle("Sync Thread Command/Response test")
        self.show()

    def MakeThreadWorker(self):
        self.worker_thread = QThread()
        self.worker = WorkToDo()

        """ incoming gui update command, works """
        self.worker.signals.command.connect(self.process_command)
        """ outgoing update to worker thread, does not work """
        self.signals.command.connect(self.worker.process_command)

        """ signal to start the thread function, works """
        self.worker_thread.started.connect(self.worker.start)

        self.worker_thread.start()
        self.worker.moveToThread(self.worker_thread)

    @pyqtSlot(Command)
    def process_command(self, command):
        """
        @brief       process update from work thread
        @param       self     self
        @param       command  command object
        @return      none
        """
        if command.cmd == "update_text":
            text_update = command.args.get("text", "")
            self.text_line.setText(text_update)

    def issue_button_update(self):
        cmd = Command(cmd="add_to_counter", args={"addition": 10})
        print("Button Clicked!")
        self.signals.command.emit(cmd)


if __name__ == "__main__":
    APPLICATION = QApplication([])
    APPLICATION.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
    gui = Gui()
    sys.exit(APPLICATION.exec_())
...