如何通过Signal触发QStateMachine直接切换至目标QState?
问题描述
我基于标准状态模式在Python中实现了状态机类Controller,它运行在PySide6应用主线程之外的独立线程中。GUI端使用Qt for Python状态机框架,期望实现:
- GUI仅向
Controller下发指令 - 当
Controller完成状态切换时,通过发射Signal触发PySide6的QStateMachine切换到对应状态,从而设置该状态下的GUI属性
现有疑问:给定一个QState,如何发送Signal迫使所属QStateMachine切换到该状态?或是需要向QStateMachine发送携带目标QState的Signal?
解决方案
核心思路是给QStateMachine提前配置好基于信号的状态转换规则,让Controller在完成状态切换后发射带有目标状态标识的信号,触发QStateMachine自动切换到对应状态。不建议直接传递QState对象(跨线程操作Qt对象存在线程安全风险),更推荐用统一的状态标识(比如字符串、枚举)来关联两端状态。
具体实现步骤
- 给
Controller定义一个携带状态标识的信号,确保GUI和Controller端的状态标识统一 - 在GUI端的
QStateMachine初始化时,为每个状态添加从任意状态到自身的转换规则,触发条件为Controller发射的对应状态标识信号 - 在
Controller各状态类的on_entry方法中,发射携带当前状态标识的信号
代码修改示例
1. 修改Controller及状态类(添加状态切换信号)
from PySide6.QtCore import QObject, Signal from abc import ABC, abstractmethod # 统一两端的状态标识枚举 class StateID: IDLE = "idle" CAMERA_EXPOSING = "camera_exposing" class IState(ABC): @abstractmethod def on_entry(self, controller): pass class Idle(IState): def on_entry(self, controller): # 触发GUI切换到Idle状态 controller.state_changed.emit(StateID.IDLE) # 其他状态逻辑... class CameraExposing(IState): def on_entry(self, controller): # 触发GUI切换到拍摄状态 controller.state_changed.emit(StateID.CAMERA_EXPOSING) # 其他状态逻辑... class Controller(QObject): # 定义状态切换信号,携带状态标识字符串 state_changed = Signal(str) def __init__(self): super().__init__() self.current_state = None # 初始化默认状态等逻辑... def switch_state(self, new_state: IState): self.current_state = new_state self.current_state.on_entry(self)
2. 修改GUI端QStateMachine初始化代码(添加信号触发的转换)
from PySide6.QtCore import QStateMachine, QState from PySide6.QtWidgets import QWidget class MainWindow(QWidget): def __init__(self): super().__init__() self.controller = Controller() # 启动Controller线程(此处省略线程初始化及启动代码) self.init_gui_state_machine() def init_gui_state_machine(self): self.gui_state_machine = QStateMachine() # 创建GUI侧的状态实例 idle_state = QState(self.gui_state_machine) exposing_state = QState(self.gui_state_machine) # 给状态绑定GUI属性设置逻辑 idle_state.assignProperty(self.shoot_btn, "enabled", True) exposing_state.assignProperty(self.shoot_btn, "enabled", False) # 添加通用转换规则:任意状态 -> 目标状态,触发条件为对应状态标识信号 idle_state.addTransition( self.controller.state_changed, StateID.IDLE, idle_state ) exposing_state.addTransition( self.controller.state_changed, StateID.CAMERA_EXPOSING, exposing_state ) # 设置初始状态并启动状态机 self.gui_state_machine.setInitialState(idle_state) self.gui_state_machine.start()
补充说明
- 禁止跨线程传递
QState对象:Qt的QObject子类(包括QState)属于创建它的线程,跨线程传递或直接操作会引发线程安全问题,用状态标识是更稳妥的方案 - 若需要更灵活的转换逻辑,可以给
QStateMachine添加UniversalTransition(任意状态)的转换规则,根据信号参数匹配目标状态
内容的提问来源于stack exchange,提问作者bmitc
相关产品推荐
相关产品推荐

