首页 > 解决方案 > PySide2 中 QScxmlStateMachine.connectToEvent 的奇怪行为

问题描述

我正在尝试使用后端的 Python、前端的 QML 和用于创建状态机的 scxml 编写桌面应用程序。我正在使用 PySide2。我的目的是通过状态机来描述应用程序的逻辑。基于状态,QML 中的 UI 应该做出相应的反应。此外,后端应根据进入状态等执行方法。

我的项目包含 3 个文件:main.py带有后端逻辑、ui.qml带有 UI、stateMachine.scxml带有状态机。

内容main.py

from PySide2.QtScxml import QScxmlStateMachine
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QUrl, QObject, Slot
from PySide2.QtQml import QQmlApplicationEngine


class BackEnd(QObject):
    def __init__(self):
        super().__init__()

    @Slot()
    def ev_slot(self):
        '''method called on event t1'''
        print('event t1')

    @Slot(bool)
    def st_slot(self, active):
        '''method called on entering and exiting state s2'''
        if active:
            print('s2 entered')
        else:
            print('s2 exited')


app = QApplication([])
qml_url = QUrl("ui.qml")

engine = QQmlApplicationEngine()

# loading state machine
my_state_machine = QScxmlStateMachine.fromFile('stateMachine.scxml')

backend = BackEnd()

# registering state machine in QML context
engine.rootContext().setContextProperty("stateMachine", my_state_machine)
# connecting event of state machine to method of backend
conn1 = my_state_machine.connectToEvent("t1", backend, "aev_slot()")
# connecting state of state machine to method of backend
conn2 = my_state_machine.connectToState("s2", backend, "ast_slot(bool)")

my_state_machine.start()

engine.load(qml_url)

app.exec_()

UI 非常简单:它仅包含 3 个向状态机(ui.qml文件)提交事件的按钮:

import QtQuick 2.0
import QtQuick.Controls 2.2


ApplicationWindow {
    width: column.width
    height: column.height
    visible: true

    Column {
        id: column
        spacing: 10
        Button {
            id: button0
            text: qsTr("t1")
            onClicked: stateMachine.submitEvent('t1')
        }

        Button {
            id: button1
            text: qsTr("t2")
            onClicked: stateMachine.submitEvent('t2')
        }

        Button {
            id: button2
            text: qsTr("t3")
            onClicked: stateMachine.submitEvent('t3')
        }
    }

}

状态机stateMachine.scxml由 3 个状态组成:、s1和转换、s2、:s3t1t2t3

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" xmlns:qt="http://www.qt.io/2015/02/scxml-ext" name="stateMachine" initial="s1">
    <state id="s1">
        <transition type="external" event="t1" target="s2"/>
        <onentry>
            <log label="entered" expr="s1"/>
        </onentry>
    </state>
    <state id="s2">
        <transition type="external" event="t2" target="s3"/>
        <onentry>
            <log label="entered" expr="s2"/>
        </onentry>
    </state>
    <state id="s3">
        <transition type="external" event="t3" target="s1">
        </transition>
        <onentry>
            <log label="entered" expr="s3"/>
        </onentry>
    </state>
</scxml>

这里的问题是一切正常。行my_state_machine.connectToEvent("t1", backend, "aev_slot()")有一个错误:方法名称是ev_slot(),不是aev_slot()。但是,如果我将其更改为正确的名称,则会收到以下错误:

QObject::connect: No such slot BackEnd::v_slot()

不知何故,方法名称中的第一个字母被忽略了。难道我做错了什么?我对 Qt 和 PySide2 很陌生。总的来说这是一个好方法吗?我正在使用 PySide2 5.11.1a1.dev1530708810518

标签: pythonqmlpyside2scxml

解决方案


您必须使用SLOT()将方法作为字符串传递(请记住,这SLOTSlot装饰器不同)。

from PySide2 import QtCore, QtGui, QtQml, QtScxml


class BackEnd(QtCore.QObject):
    @QtCore.Slot()
    def ev_slot(self):
        '''method called on event t1'''
        print('event t1')

    @QtCore.Slot(bool)
    def st_slot(self, active):
        '''method called on entering and exiting state s2'''
        if active:
            print('s2 entered')
        else:
            print('s2 exited')


if __name__ == '__main__':
    import sys
    app = QtGui.QGuiApplication(sys.argv)
    qml_url = QtCore.QUrl.fromLocalFile("ui.qml")

    # loading state machine
    my_state_machine = QtScxml.QScxmlStateMachine.fromFile('stateMachine.scxml')

    backend = BackEnd()
    conn1 = my_state_machine.connectToEvent("t1", backend, QtCore.SLOT("ev_slot()"))
    conn2 = my_state_machine.connectToState("s2", backend, QtCore.SLOT("st_slot(bool)"))
    my_state_machine.start()

    engine = QtQml.QQmlApplicationEngine()
    engine.rootContext().setContextProperty("stateMachine", my_state_machine)
    engine.load(qml_url)

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

SLOT1作为插槽名称的前缀,而 SIGNAL 对 , 执行相同2操作,因此如果您不想使用它,只需添加前缀 1: (..., backend, "1ev_slot()"),(我不建议这样做,因为它会使代码的可读性降低)


推荐阅读