如何让这个 Qt 状态机工作?
我有两个可以检查的小部件,以及一个应包含大于零的值的数字输入字段。只要两个小部件都被选中,并且数字输入字段包含大于零的值,就应该启用一个按钮。我正在努力为这种情况定义一个合适的状态机。到目前为止,我有以下问题:
QStateMachine *machine = new QStateMachine(this);
QState *buttonDisabled = new QState(QState::ParallelStates);
buttonDisabled->assignProperty(ui_->button, "enabled", false);
QState *a = new QState(buttonDisabled);
QState *aUnchecked = new QState(a);
QFinalState *aChecked = new QFinalState(a);
aUnchecked->addTransition(wa, SIGNAL(checked()), aChecked);
a->setInitialState(aUnchecked);
QState *b = new QState(buttonDisabled);
QState *bUnchecked = new QState(b);
QFinalState *bChecked = new QFinalState(b);
employeeUnchecked->addTransition(wb, SIGNAL(checked()), bChecked);
b->setInitialState(bUnchecked);
QState *weight = new QState(buttonDisabled);
QState *weightZero = new QState(weight);
QFinalState *weightGreaterThanZero = new QFinalState(weight);
weightZero->addTransition(this, SIGNAL(validWeight()), weightGreaterThanZero);
weight->setInitialState(weightZero);
QState *buttonEnabled = new QState();
buttonEnabled->assignProperty(ui_->registerButton, "enabled", true);
buttonDisabled->addTransition(buttonDisabled, SIGNAL(finished()), buttonEnabled);
buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero);
machine->addState(registerButtonDisabled);
machine->addState(registerButtonEnabled);
machine->setInitialState(registerButtonDisabled);
machine->start();
这里的问题是以下转换:
buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero);
导致 registerButtonDisabled
状态中的所有子状态恢复为其初始状态。这是不需要的行为,因为我希望 a
和 b
状态保持相同的状态。
如何确保 a
和 b
保持相同状态?是否有另一种/更好的方法可以使用状态机解决这个问题?
注意。有无数(可以说是更好)的方法来解决这个问题。但是,我只对使用状态机的解决方案感兴趣。我认为这样一个简单的用例应该可以使用简单的状态机来解决,对吧?
I have two widgets that can be checked, and a numeric entry field that should contain a value greater than zero. Whenever both widgets have been checked, and the numeric entry field contains a value greater than zero, a button should be enabled. I am struggling with defining a proper state machine for this situation. So far I have the following:
QStateMachine *machine = new QStateMachine(this);
QState *buttonDisabled = new QState(QState::ParallelStates);
buttonDisabled->assignProperty(ui_->button, "enabled", false);
QState *a = new QState(buttonDisabled);
QState *aUnchecked = new QState(a);
QFinalState *aChecked = new QFinalState(a);
aUnchecked->addTransition(wa, SIGNAL(checked()), aChecked);
a->setInitialState(aUnchecked);
QState *b = new QState(buttonDisabled);
QState *bUnchecked = new QState(b);
QFinalState *bChecked = new QFinalState(b);
employeeUnchecked->addTransition(wb, SIGNAL(checked()), bChecked);
b->setInitialState(bUnchecked);
QState *weight = new QState(buttonDisabled);
QState *weightZero = new QState(weight);
QFinalState *weightGreaterThanZero = new QFinalState(weight);
weightZero->addTransition(this, SIGNAL(validWeight()), weightGreaterThanZero);
weight->setInitialState(weightZero);
QState *buttonEnabled = new QState();
buttonEnabled->assignProperty(ui_->registerButton, "enabled", true);
buttonDisabled->addTransition(buttonDisabled, SIGNAL(finished()), buttonEnabled);
buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero);
machine->addState(registerButtonDisabled);
machine->addState(registerButtonEnabled);
machine->setInitialState(registerButtonDisabled);
machine->start();
The problem here is that the following transition:
buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero);
causes all the child states in the registerButtonDisabled
state to be reverted to their initial state. This is unwanted behaviour, as I want the a
and b
states to remain in the same state.
How do I ensure that a
and b
remain in the same state? Is there another / better way this problem can be solved using state machines?
Note. There are a countless (arguably better) ways to solve this problem. However, I am only interested in a solution that uses a state machine. I think such a simple use case should be solvable using a simple state machine, right?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
在阅读了您的要求以及此处的答案和评论后,我认为 merula 的解决方案或类似的解决方案是唯一纯粹的状态机解决方案。
正如已经指出的,要使并行状态触发
finished()
信号,所有禁用状态都必须是最终状态,但这并不是它们真正应该的样子,因为有人可以取消选中其中一个复选框并那么你就必须离开最终状态。你不能这样做,因为 FinalState 不接受任何转换。使用 FinalState 退出并行状态也会导致并行状态在重新进入时重新启动。一种解决方案可能是编写一个转换,该转换仅在所有三个状态都处于“良好”状态时触发,而第二个转换则在其中任何一个状态不处于“良好”状态时触发。然后,将禁用和启用状态添加到已有的并行状态中,并将其与上述转换连接起来。这将使按钮的启用状态与 UI 部分的所有状态保持同步。它还可以让您离开并行状态并返回到一组一致的属性设置。
没有编译这个,甚至没有测试它,但它应该演示原理
After reading your requirements and the answers and comments here I think merula's solution or something similar is the only pure Statemachine solution.
As has been noted to make the Parallel State fire the
finished()
signal all the disabled states have to be final states, but this is not really what they should be as someone could uncheck one of the checkboxes and then you would have to move away from the final state. You can't do that as FinalState does not accept any transitions. The using the FinalState to exit the parallel state also causes the parallel state to restart when it is reentered.One solution could be to code up a transition that only triggers when all three states are in the "good" state, and a second one that triggers when any of those is not. Then you add the disabled and enabled states to the parallel state you already have and connect it with the aforementioned transitions. This will keep the enabled state of the button in sync with all the states of your UI pieces. It will also let you leave the parallel state and come back to a consistent set of property settings.
Did not compile this or even test it, but it should demonstrate the principle
您上面使用的状态机与您所描述的不符。使用最终状态是不正确的,因为在输入大于零的值后,我没有看到任何阻止用户再次输入零的内容。因此,有效状态不能是最终的。据我从您的代码中看到,允许用户以任何顺序更改小部件的状态。你的状态机必须注意这一点。
我将使用具有四个子状态的状态机(无有效输入、一个有效输入、两个有效输入、三个有效输入)。显然你一开始就没有有效的输入。每个小部件都可以从无过渡到有一个(两个和三个也同样如此)。当输入三个时,所有小部件都有效(按钮启用)。对于所有其他状态,进入该状态时必须禁用该按钮。
我写了一个示例应用程序。主窗口包含两个 QCheckBox、一个 QSpinBox 和一个 QPushButton。主窗口中有信号可以轻松记录状态的转换。当小部件的状态更改时会触发。
MainWindow.h
MainWindow.cpp
这应该可以解决问题。正如您自己已经提到的,有更好的方法来实现这种行为。特别是跟踪状态之间的所有转换很容易出错。
The state machine you used above does not correspond to what you described. Using a final state is not correct because after enter a value greater zero I don't see anything that prevents the user from enter zero again. Therefore the valid states can't be final. As far as I can see from your code the user is allowed to change the state of the widgets in any order. Your state machine has to pay attention to this.
I would use a state machine with four child states (no valid input, one valid input, two valid inputs, three valid inputs). You obviously start with no valid input. Each widget can make a transition from no to one an back (same counts for two and three). When three is entered all widgets are valid (button enabled). For all other states the button has to be disabled when the state is entered.
I wrote a sample app. The main window contains two QCheckBoxes a QSpinBox and a QPushButton. There are signals in the main window the ease write down the transitions of the states. There are fired when the state of the widgets are changed.
MainWindow.h
MainWindow.cpp
This should do the trick. As you yourself mentioned already there are better ways to achive this behaviour. Especially keep track of all transistions between the state is error prone.
当我必须做这样的事情时,我通常使用信号和槽。基本上每个小部件和数字框都会在状态改变时自动发出信号。如果将它们中的每一个链接到一个插槽,该插槽检查所有 3 个对象是否处于所需状态,并在处于所需状态时启用按钮,如果不是则禁用按钮,那么这应该会简化事情。
有时,单击按钮后您还需要更改按钮状态。
[编辑]:我确信有某种方法可以使用状态机来执行此操作,您是否只在两个框都被选中并且您添加了无效权重的情况下才恢复,或者您是否也需要仅使用一个来恢复复选框已选中?如果是前者,那么您可以设置 RestoreProperties状态,允许您恢复到复选框状态。否则,有什么方法可以在检查重量是否有效之前保存状态,恢复所有复选框,然后恢复状态。
When I have to do things like this I usually use signals and slots. Basically each of the widgets and the number box will all emit signals automatically when their states change. If you link each of these to a slot that checks if all 3 objects are in the desired state and enables the button if they are or disables it if they aren't, then that should simplify things.
Sometimes you will also need to change the button state once you've clicked it.
[EDIT]: I'm sure there is some way of doing this using state machines, will you only be reverting in the situation that both boxes are checked and you've added an invalid weight or will you also need to revert with only one checkbox checked? If it's the former then you may be able to set up a RestoreProperties state that allows you to revert to the checked box state. Otherwise is there some way you can save the state before checking the weight is valid, revert all checkboxes then restore the state.
设置您的体重输入小部件,以便无法输入小于零的体重。那么你就不需要
invalidWeight()
Set up your weight input widget so that there is no way a weight less than zero can be entered. Then you don't need
invalidWeight()
编辑
我重新打开了这个测试,愿意使用它,添加到.pro中
,我发现lambda语法已经改变......捕获列表无法引用成员变量。这是更正后的代码
结束编辑
我用这个问题作为练习(第一次在 QStateMachine 上)。该解决方案相当紧凑,使用受保护的转换在“启用/禁用”状态之间移动,并使用 lambda 来分解设置:
edit
I reopened this test, willing to use it, added to .pro
and I discovered that lambda syntax has changed... The capture list cannot reference member variables. Here is the corrected code
end edit
I used this question as exercise (first time on QStateMachine). The solution is fairly compact, using a guarded transition to move between 'enabled/disabled' state, and lambda to factorize setup: