发出 `Signal` 直接使 `QStateMachine` 转换到另一个 `QState`

1 投票
1 回答
37 浏览
提问于 2025-04-13 00:48

我有一个状态机,如下图所示:

这里输入图片描述

我在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 个回答

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 是一个包含所有可能状态的列表。

撰写回答