Странное поведение QScxmlStateMachine.connectToEvent в PySide2

Я пытаюсь написать настольное приложение, используя Python на бэкэнде, QML на внешнем интерфейсе и scxml для создания конечного автомата. Я использую PySide2. Я намерен описать логику приложения с помощью конечного автомата. Основываясь на состоянии, пользовательский интерфейс в QML должен реагировать соответствующим образом. Кроме того, бэкэнд должен выполнять методы, основанные на вводе состояний и т. Д.

Мой проект состоит из 3 файлов: main.py с внутренней логикой, ui.qml с пользовательским интерфейсом, 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_()

Пользовательский интерфейс очень прост: он состоит только из 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, s3 и переходы t1, t2, t3:

<?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

1 ответ

Решение

Вы должны использовать SLOT() передать метод в виде строки (имейте в виду, что SLOT отличается от Slot декоратор).

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_())

SLOT только префиксы 1 на имя слота, и СИГНАЛ делает то же самое с 2, так что если вы не хотите использовать его просто префикс 1: (..., backend, "1ev_slot()")(Я не рекомендую делать это, так как это делает код менее читабельным)

Другие вопросы по тегам