如何在 Qt SCXML 状态图中使用条件转换
How to use conditional transitions in Qt SCXML statecharts
我目前正在尝试了解 Qts scxml 状态图,以及如何将它们正确地集成到我的应用程序中。我偶然发现的一个问题是条件转换。为了解释我在这里如何使用条件以及出了什么问题,我做了一个最小可行的例子:
初始状态 s_initial
有两次到状态 s_false
和 s_true
的转换。两个转换都由同一事件触发 t_button_clicked
。根据变量 test_var
,任何时候都只能进行一次转换。当另一个t_button_clicked
事件发生时,状态机returns变为s_initial
.
为了测试状态机,我创建了一个简单的 Qt-Widgets 应用程序,其中包含一个用于触发 t_button_clicked
的按钮和一个用于更改变量的复选框 test_var
:
(mainwindow.cpp)
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, chart(this)
{
ui->setupUi(this);
connect(ui->checkBox, &QCheckBox::clicked, [this](bool checked){
qDebug() << "> checkbox:" << checked;
chart.dataModel()->setProperty("test_var", checked);
});
connect(ui->pushButton, &QPushButton::released, [this](){
qDebug() << "> button";
chart.submitEvent("t_button_clicked");
});
chart.start();
}
(testchart.scxml)
<?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="TestChart" qt:editorversion="4.14.1" datamodel="ecmascript" initial="s_initial">
<qt:editorinfo initialGeometry="213.11;86.67;-20;-20;40;40"/>
<state id="s_initial">
<qt:editorinfo scenegeometry="213.11;233.50;153.11;183.50;120;100" geometry="213.11;233.50;-60;-50;120;100"/>
<transition type="external" event="t_button_clicked" target="s_false" cond="!test_var">
<qt:editorinfo endTargetFactors="11.79;50.87"/>
</transition>
<transition type="external" event="t_button_clicked" target="s_true" cond="test_var">
<qt:editorinfo movePoint="37.73;-3.06" endTargetFactors="19.26;54.39"/>
</transition>
<onentry>
<log expr=""s_initial""/>
</onentry>
</state>
<state id="s_false">
<qt:editorinfo scenegeometry="529.21;233.50;469.21;183.50;120;100" geometry="529.21;233.50;-60;-50;120;100"/>
<onentry>
<log expr=""s_false""/>
</onentry>
<transition type="external" event="t_button_clicked" target="s_initial">
<qt:editorinfo movePoint="3.06;9.18" endTargetFactors="88.28;64.08" startTargetFactors="13.98;61.45"/>
</transition>
</state>
<state id="s_true">
<qt:editorinfo scenegeometry="529.21;419.09;469.21;369.09;120;100" geometry="529.21;419.09;-60;-50;120;100"/>
<onentry>
<log expr=""s_true""/>
</onentry>
<transition type="external" event="t_button_clicked" target="s_initial">
<qt:editorinfo movePoint="-37.73;6.12" endTargetFactors="68.74;85.18" startTargetFactors="14.04;72.02"/>
</transition>
</state>
<datamodel>
<data id="test_var" expr="false"/>
</datamodel>
</scxml>
正如在 scxml 文件中所看到的,我向每个 state-onentry 添加了日志输出,以查看是否进入了该状态。此外,我将调试输出添加到按钮 - 和复选框点击。当我 运行 应用程序时,控制台输出不是我所期望的:
scxml.statemachine: "" : "s_initial"
> checkbox: false
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
> checkbox: true
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
> checkbox: false
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
test_var
的值是多少并不重要。状态机总是首先转换到 s_false
,而不检查我添加的条件守卫。据我所知,我在图表中使用了有效的 ecmascript 表达式,scxml 应该能够根据其 specification 选择正确的转换。我做错了什么?
您应该使用 setScxmlProperty 而不是 setProperty
chart.dataModel()->setScxmlProperty("test_var", checked, "");
并且您可以简单地使用 _event.data 来传递复选框当前值。
chart.submitEvent("t_button_clicked", ui->checkBox->checked() ? 1:0 );
<scxml datamodel="ecmascript" initial="s_initial" name="TestChart" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="s_initial">
<onentry>
<log expr="'s_initial'"/>
</onentry>
<transition cond="_event.data==1" event="t_button_clicked" target="s_true"/>
<transition event="t_button_clicked" target="s_false"/>
</state>
<state id="s_false">
<onentry>
<log expr="'s_false'"/>
</onentry>
<transition cond="_event.data==1" event="t_button_clicked" target="s_true"/>
</state>
<state id="s_true">
<onentry>
<log expr="'s_true'"/>
</onentry>
<transition cond="! (_event.data==1)" event="t_button_clicked" target="s_false"/>
</state>
</scxml>
statechart
P.S。您可以使用下一个参考资料来更好地理解 SCXML(我正在推广我自己的网站)
我目前正在尝试了解 Qts scxml 状态图,以及如何将它们正确地集成到我的应用程序中。我偶然发现的一个问题是条件转换。为了解释我在这里如何使用条件以及出了什么问题,我做了一个最小可行的例子:
初始状态 s_initial
有两次到状态 s_false
和 s_true
的转换。两个转换都由同一事件触发 t_button_clicked
。根据变量 test_var
,任何时候都只能进行一次转换。当另一个t_button_clicked
事件发生时,状态机returns变为s_initial
.
为了测试状态机,我创建了一个简单的 Qt-Widgets 应用程序,其中包含一个用于触发 t_button_clicked
的按钮和一个用于更改变量的复选框 test_var
:
(mainwindow.cpp)
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, chart(this)
{
ui->setupUi(this);
connect(ui->checkBox, &QCheckBox::clicked, [this](bool checked){
qDebug() << "> checkbox:" << checked;
chart.dataModel()->setProperty("test_var", checked);
});
connect(ui->pushButton, &QPushButton::released, [this](){
qDebug() << "> button";
chart.submitEvent("t_button_clicked");
});
chart.start();
}
(testchart.scxml)
<?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="TestChart" qt:editorversion="4.14.1" datamodel="ecmascript" initial="s_initial">
<qt:editorinfo initialGeometry="213.11;86.67;-20;-20;40;40"/>
<state id="s_initial">
<qt:editorinfo scenegeometry="213.11;233.50;153.11;183.50;120;100" geometry="213.11;233.50;-60;-50;120;100"/>
<transition type="external" event="t_button_clicked" target="s_false" cond="!test_var">
<qt:editorinfo endTargetFactors="11.79;50.87"/>
</transition>
<transition type="external" event="t_button_clicked" target="s_true" cond="test_var">
<qt:editorinfo movePoint="37.73;-3.06" endTargetFactors="19.26;54.39"/>
</transition>
<onentry>
<log expr=""s_initial""/>
</onentry>
</state>
<state id="s_false">
<qt:editorinfo scenegeometry="529.21;233.50;469.21;183.50;120;100" geometry="529.21;233.50;-60;-50;120;100"/>
<onentry>
<log expr=""s_false""/>
</onentry>
<transition type="external" event="t_button_clicked" target="s_initial">
<qt:editorinfo movePoint="3.06;9.18" endTargetFactors="88.28;64.08" startTargetFactors="13.98;61.45"/>
</transition>
</state>
<state id="s_true">
<qt:editorinfo scenegeometry="529.21;419.09;469.21;369.09;120;100" geometry="529.21;419.09;-60;-50;120;100"/>
<onentry>
<log expr=""s_true""/>
</onentry>
<transition type="external" event="t_button_clicked" target="s_initial">
<qt:editorinfo movePoint="-37.73;6.12" endTargetFactors="68.74;85.18" startTargetFactors="14.04;72.02"/>
</transition>
</state>
<datamodel>
<data id="test_var" expr="false"/>
</datamodel>
</scxml>
正如在 scxml 文件中所看到的,我向每个 state-onentry 添加了日志输出,以查看是否进入了该状态。此外,我将调试输出添加到按钮 - 和复选框点击。当我 运行 应用程序时,控制台输出不是我所期望的:
scxml.statemachine: "" : "s_initial"
> checkbox: false
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
> checkbox: true
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
> checkbox: false
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
test_var
的值是多少并不重要。状态机总是首先转换到 s_false
,而不检查我添加的条件守卫。据我所知,我在图表中使用了有效的 ecmascript 表达式,scxml 应该能够根据其 specification 选择正确的转换。我做错了什么?
您应该使用 setScxmlProperty 而不是 setProperty
chart.dataModel()->setScxmlProperty("test_var", checked, "");
并且您可以简单地使用 _event.data 来传递复选框当前值。
chart.submitEvent("t_button_clicked", ui->checkBox->checked() ? 1:0 );
<scxml datamodel="ecmascript" initial="s_initial" name="TestChart" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
<state id="s_initial">
<onentry>
<log expr="'s_initial'"/>
</onentry>
<transition cond="_event.data==1" event="t_button_clicked" target="s_true"/>
<transition event="t_button_clicked" target="s_false"/>
</state>
<state id="s_false">
<onentry>
<log expr="'s_false'"/>
</onentry>
<transition cond="_event.data==1" event="t_button_clicked" target="s_true"/>
</state>
<state id="s_true">
<onentry>
<log expr="'s_true'"/>
</onentry>
<transition cond="! (_event.data==1)" event="t_button_clicked" target="s_false"/>
</state>
</scxml>
statechart
P.S。您可以使用下一个参考资料来更好地理解 SCXML(我正在推广我自己的网站)