I would like to divide my GUI into processes that are run when activated by a ProcessManager.
As the diagram below shows, the MainWindow would instantiate the ProcessManager, which instantiates the Processes.
Now let's say I want to interact with components of the GUI across threads (using signals and slots) -- as an example (also in the picture below):
Process2 has a method that reads out the QLineEdit foo_line_edit.text()
and enters it to the QLabel via foo_label.setText()
.
If I have many processes, I would prefer if all the logic would be implement on the Process without having to add to much on the MainWindow, therefore:
Is there a way to define such a process without having to create any methods/slots in the MainWindow? If there isn't how can I create a process driven design with Qt?
Here is the simplest implementation that comes to my mind:
class Main(QMainWindow):
foo_line_edit = QLineEdit()
foo_label = QLabel()
def __init__(self):
self.process_manager = ProcessManager(self)
# I would like to have the logic of this line in the Process2 class
# (Not sure, but I think it needs to be defined in the main thread?)
self.process_manager.process2.change_label.connect(self.process2_specific_change_label)
# This is the functionality, I would like to define in the Process2 class
def process2_specific_change_label(text):
self.foo_label.setText(text)
class ProcessManager(QObject):
def __init__(self, main_gui):
self.main_gui = main_gui
self.process2 = Process2(self)
class Process(QObject):
def __init__(self, process_manager):
self.process_manager=process_manager
self.thread = QThread()
self.moveToThread(self.thread)
self.thread.start()
class Process2(Process):
change_label = pyqtSignal(str)
def run(self):
# I think this line works, although it already creates access across threads, right?
text = self.process_manager.main_gui.foo_line_edit.text()
# This line does not work because of threads:
self.process_manager.main_gui.foo_label.setText(text)
# This works, but requires the function to be defined in the main thread?
change_label.emit(text)
Rethinking my original requirement and processing musicamante's input, it came to my mind, that the Process Manager and the Processes should run directly in the main thread, calling processspecific workers in threads. This way, the logic is within the specific Process and not somewhere in the MainWindow. I agree, that the word process is confusing here. It was not meant in a programmatical aspect, but in its application aspect (the gui will follow along a production process). Renaming Process to Step might be better. This leaves us with the following architecture:
The signals and slots are defined in the Step and its Worker counterpart.
class Main(QMainWindow):
foo_line_edit = QLineEdit()
foo_label = QLabel()
def __init__(self):
self.step_manager = StepManager(self)
class StepManager(QObject):
def __init__(self, main_gui):
self.main_gui = main_gui
self.step2 = Step2(self)
# For example simplicity, the step is run directly:
self.step2.run()
class Step(QObject):
start_worker = pyqtSignal()
worker = Worker()
def __init__(self, step_manager):
self.step_manager=step_manager
self.start_worker.connect(self.worker.run)
def run(self):
self.start_worker.emit()
class Worker(QObject):
finished = pyqtSignal()
def __init__(self):
self.thread = QThread()
self.moveToThread(self.thread)
self.thread.start()
def run(self):
self.finished.emit()
class Step2(Step):
start_worker = pyqtSignal(str)
worker = Worker2()
def __init__(self, step):
super().__init__(step)
self.worker.finished.connect(self.change_label)
def run(self):
text = self.step_manager.main_gui.foo_line_edit.text()
self.start_worker.emit(text)
def change_label(self, text):
self.step_manager.main_gui.foo_label.setText(text)
class Worker2(Worker):
finished = pyqtSignal(str)
def run(self, text):
self.finished.emit(text)
Although 2 classes need to be managed for each step, the MainWindow is free from step specific logic.
Of course, I'm very open to suggestions.