发出 `Signal` 直接使 `QStateMachine` 转换到另一个 `QState`
我有一个状态机,如下图所示:
我在Python中实现了这个状态机,使用的是标准的状态模式。这个状态机在一个与PySide6应用程序主线程不同的线程中运行。我们把这个状态机的类叫做Controller
。
在图形用户界面(GUI)方面,我使用的是Qt for Python 状态机框架。(注意:链接的文档是针对PySide2/PySide5的,但我使用的是PySide6。出于某种原因,PySide6没有相应的文档。)这样可以很方便地定义当进入某个状态时,GUI会发生什么。
我想要的不是让GUI来发起状态转换,而是让我的核心状态机Controller
在另一个线程中控制这些转换。通常情况下,人们会使用<QState>.addTransition(...)
在GUI层面添加转换,但我希望GUI只是向Controller
发送命令,然后当Controller
转换状态时,我希望它能发出一个Signal
,触发PySide6的QStateMachine
进入某个特定状态,从而设置该状态下所有合适的属性。换句话说,我希望GUI能够向Controller
状态机发送命令,并且GUI能够“监听”Controller
的状态转换。
所以问题是,给定一个QState
,我该如何向它发送信号,以强制它所属的QStateMachine
转换到那个状态?还是说我需要向QStateMachine
发送信号,并提供一个QState
来进行转换?
示例程序:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import override
from PySide6.QtStateMachine import QState
class Controller:
def __init__(self, initial_state: IState, states: list[QState]) -> None:
"""Initialize the controller to the given initial state and call the `
on_entry` method for the state.
"""
self.__state = initial_state
self.__state.controller = self
self.__state.on_entry()
self.__state.states = states
def _transition_to(self, new_state: IState) -> None:
"""Transition from the current state to the given new state. This calls
the `on_exit` method on the current state and the `on_entry` of the
new state. This method should not be called by any object other than
concrete implementations of `IState`.
"""
self.__state.on_exit()
self.__state = new_state
self.__state.controller = self
self.__state.on_entry()
@property
def state(self):
"""Get the current state"""
return self.__state
def start_camera_exposure(self) -> None:
self.__state.start_camera_exposure()
def stop_camera_exposure(self) -> None:
self.__state.stop_camera_exposure()
def abort_camera_exposure(self) -> None:
self.__state.abort_camera_exposure()
class IState(ABC):
"""Serve as an interface between the controller and the explicit, individual states."""
@property
def controller(self) -> Controller:
return self.__controller
@controller.setter
def controller(self, controller: Controller) -> None:
self.__controller = controller
@property
def states(self) -> list[QState]:
return self.__states
@states.setter
def states(self, states: list[QState]):
self.__states = states
def on_entry(self) -> None:
"""Can be overridden by a state to perform an action when the state is
being entered, i.e., transitions into. It is not required to be overridden.
"""
pass
def on_exit(self) -> None:
"""Can be overridden by a state to perform an action when the state is
being exited, i.e., transitioned from. It is not required to be overridden.
"""
pass
# If a concrete implementation does not handle the called method, i.e., it is an invalid action
# in the specific state, it is enough to simply call `pass`.
@abstractmethod
def start_camera_exposure(self) -> None: ...
@abstractmethod
def stop_camera_exposure(self) -> None: ...
@abstractmethod
def abort_camera_exposure(self) -> None: ...
class Idle(IState):
@override
def on_entry(self):
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[0]`
print("Idling ...")
def start_camera_exposure(self) -> None:
self.controller._transition_to(CameraExposing())
def stop_camera_exposure(self) -> None:
pass
def abort_camera_exposure(self) -> None:
pass
class CameraExposing(IState):
@override
def on_entry(self) -> None:
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[1]`
print("Starting camera exposure ...")
@override
def on_exit(self) -> None:
print("Stopping camera exposure ...")
def start_camera_exposure(self) -> None:
pass
def stop_camera_exposure(self) -> None:
self.controller._transition_to(SavingCameraImages())
def abort_camera_exposure(self) -> None:
self.controller._transition_to(AbortingCameraExposure())
class SavingCameraImages(IState):
@override
def on_entry(self) -> None:
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[2]`
print("Saving camera images ...")
self.controller._transition_to(Idle())
def start_camera_exposure(self) -> None:
pass
def stop_camera_exposure(self) -> None:
pass
def abort_camera_exposure(self) -> None:
pass
class AbortingCameraExposure(IState):
@override
def on_entry(self) -> None:
# I want to emit a signal here to force a `QStateMachine` to go to state: `self.__states[3]`
print("Aborting camera exposure ...")
self.controller._transition_to(Idle())
def start_camera_exposure(self) -> None:
pass
def stop_camera_exposure(self) -> None:
pass
def abort_camera_exposure(self) -> None:
pass
在GUI方面,我有类似这样的代码:
from PySide6.QtStateMachine import QState, QStateMachine
class MainWindow(QWidget):
def __init__(self) -> None:
super().__init__()
self.machine = QStateMachine(parent=self)
self.state_idle = QState(self.machine)
self.state_camera_exposing = QState(self.machine)
self.state_saving_camera_images = QState(self.machine)
self.state_aborting_camera_exposure = QState(self.machine)
self.machine.setInitialState(self.state_idle)
self.states = [
self.state_idle,
self.state_camera_exposing,
self.state_saving_camera_images,
self.state_aborting_camera_exposure,
]
self.initialize()
1 个回答
到目前为止,我找到的唯一方法就是为每个状态的每个过渡信号添加所有可能的过渡。
在 class MainWindow(QWidget)
里面,定义一些类变量来表示不同的过渡信号:
transition_to_idle = Signal()
transition_to_camera_exposing = Signal()
transition_to_saving_camera_images = Signal()
transition_to_aborting_camera_exposure = Signal()
然后为每个状态添加这些信号的过渡,过渡到由信号决定的状态:
machine = self.machine
state_idle = self.state_idle
state_camera_exposing = self.state_camera_exposing
state_saving_camera_images = self.state_saving_camera_images
state_aborting_camera_exposure = self.state_aborting_camera_exposure
state_idle.addTransition(self.transition_to_idle, state_idle)
state_idle.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_idle.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_idle.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
state_camera_exposing.addTransition(self.transition_to_idle, state_idle)
state_camera_exposing.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_camera_exposing.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_camera_exposing.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
state_saving_camera_images.addTransition(self.transition_to_idle, state_idle)
state_saving_camera_images.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_saving_camera_images.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_saving_camera_images.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
state_aborting_camera_exposure.addTransition(self.transition_to_idle, state_idle)
state_aborting_camera_exposure.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state_aborting_camera_exposure.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state_aborting_camera_exposure.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
接着在另一个线程中,使用 emit
来发出信号:
transition_to_idle.emit()
transition_to_camera_exposing.emit()
transition_to_saving_camera_images.emit()
transition_to_aborting_camera_exposure.emit()
编辑: 为了更简洁地设置过渡,可以使用一个循环。例如:
for state in states:
state.addTransition(self.transition_to_idle, state_idle)
state.addTransition(self.transition_to_camera_exposing, state_camera_exposing)
state.addTransition(self.transition_to_saving_camera_images, state_saving_camera_images)
state.addTransition(self.transition_to_aborting_camera_exposure, state_aborting_camera_exposure)
这里的 states
是一个包含所有可能状态的列表。