C++ 如何让这个Qt状态机工作?

C++ 如何让这个Qt状态机工作?,c++,qt,state-machine,qstatemachine,C++,Qt,State Machine,Qstatemachine,我有两个可以检查的小部件,一个数字输入字段应该包含大于零的值。只要两个小部件都被选中,并且数字输入字段包含大于零的值,就应该启用一个按钮。我正在努力为这种情况定义一个合适的状态机。到目前为止,我有以下几点: QStateMachine *machine = new QStateMachine(this); QState *buttonDisabled = new QState(QState::ParallelStates); buttonDisabled->assignProperty(

我有两个可以检查的小部件,一个数字输入字段应该包含大于零的值。只要两个小部件都被选中,并且数字输入字段包含大于零的值,就应该启用一个按钮。我正在努力为这种情况定义一个合适的状态机。到目前为止,我有以下几点:

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);
使
注册表按钮禁用
状态中的所有子状态恢复为初始状态。这是不必要的行为,因为我希望
a
b
状态保持不变

如何确保
a
b
保持相同的状态?是否有其他/更好的方法可以使用状态机解决此问题



注意。有无数(可以说更好)的方法来解决这个问题。然而,我只对使用状态机的解决方案感兴趣。我认为这样一个简单的用例应该可以用一个简单的状态机来解决,对吗?

当我不得不做这样的事情时,我通常使用信号和插槽。基本上,每个小部件和数字框在状态改变时都会自动发出信号。如果您将这些对象链接到一个插槽,该插槽检查所有3个对象是否都处于所需状态,如果它们处于所需状态,则启用按钮,如果它们不处于所需状态,则禁用按钮,那么这将简化工作

有时,单击按钮后,还需要更改按钮状态


[编辑]:我确信有一些方法可以使用状态机来实现这一点,您是只在两个框都选中并且添加了无效权重的情况下恢复,还是只需要选中一个复选框就可以恢复?如果是前者,那么您可以设置一个状态,允许您恢复到复选框状态。否则,在检查重量是否有效之前,是否有办法保存状态,请还原所有复选框,然后还原状态。

设置重量输入小部件,以便无法输入小于零的重量。那么您就不需要
invalidwight()

上面使用的状态机与您描述的不一致。使用最终状态是不正确的,因为在输入大于零的值后,我没有看到任何阻止用户再次输入零的内容。因此,有效状态不能是最终状态。据我所知,从您的代码中,用户可以按任何顺序更改小部件的状态。您的状态机必须注意这一点

我将使用具有四个子状态(无有效输入、一个有效输入、两个有效输入、三个有效输入)的状态机。很明显,开始时没有有效的输入。每个小部件都可以从“否”转换为“返回”(两个和三个的计数相同)。当输入三个时,所有小部件都有效(按钮已启用)。对于所有其他状态,输入状态时必须禁用按钮

我编写了一个示例应用程序。主窗口包含两个QCheckbox—QSpinBox和QPushButton。主窗口中有信号,以便记录状态的转换。当窗口小部件的状态发生更改时,将触发

主窗口

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui>

namespace Ui
{
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    bool m_editValid;

    bool isEditValid() const;
    void setEditValid(bool value);

private slots:
    void on_checkBox1_stateChanged(int state);
    void on_checkBox2_stateChanged(int state);
    void on_spinBox_valueChanged (int i);
signals:
    void checkBox1Checked();
    void checkBox1Unchecked();
    void checkBox2Checked();
    void checkBox2Unchecked();
    void editValid();
    void editInvalid();
};

#endif // MAINWINDOW_H

这应该能奏效。正如你自己已经提到的,有更好的方法来实现这种行为。尤其是跟踪状态之间的所有转换是容易出错的。

在阅读了您的需求以及这里的答案和评论后,我认为merula的解决方案或类似的解决方案是唯一的纯状态机解决方案

正如已经注意到的那样,使并行状态触发
finished()
信号,所有禁用的状态都必须是最终状态,但这并不是它们真正应该的状态,因为有人可以取消选中其中一个复选框,然后您必须离开最终状态。您不能这样做,因为FinalState不接受任何转换。使用FinalState退出并行状态也会导致并行状态在重新输入时重新启动

一种解决方案可能是编写一个转换代码,该转换只在所有三种状态都处于“良好”状态时触发,而第二种转换则在其中任何一种状态都不处于“良好”状态时触发。然后将禁用和启用状态添加到已有的并行状态,并将其与上述转换连接起来。这将使按钮的启用状态与UI部件的所有状态保持同步。它还允许您离开并行状态并返回到一组一致的属性设置

class AndGateTransition : public QAbstractTransition
{
    Q_OBJECT

public:

    AndGateTransition(QAbstractState* sourceState) : QAbstractTransition(sourceState)
        m_isSet(false), m_triggerOnSet(true), m_triggerOnUnset(false)

    void setTriggerSet(bool val)
    {
        m_triggerSet = val;
    }

    void setTriggerOnUnset(bool val)
    {
        m_triggerOnUnset = val;
    }

    addState(QState* state)
    {
        m_states[state] = false;
        connect(m_state, SIGNAL(entered()), this, SLOT(stateActivated());
        connect(m_state, SIGNAL(exited()), this, SLOT(stateDeactivated());
    }

public slots:
    void stateActivated()
    {
        QObject sender = sender();
        if (sender == 0) return;
        m_states[sender] = true;
        checkTrigger();
    }

    void stateDeactivated()
    {
        QObject sender = sender();
        if (sender == 0) return;
        m_states[sender] = false;
        checkTrigger();
    }

    void checkTrigger()
    {
        bool set = true;
        QHashIterator<QObject*, bool> it(m_states)
        while (it.hasNext())
        {
            it.next();
            set = set&&it.value();
            if (! set) break;
        }

        if (m_triggerOnSet && set && !m_isSet)
        {
            m_isSet = set;
            emit (triggered());

        }
        elseif (m_triggerOnUnset && !set && m_isSet)
        {
            m_isSet = set;
            emit (triggered());
        }
    }

pivate:
    QHash<QObject*, bool> m_states;
    bool m_triggerOnSet;
    bool m_triggerOnUnset;
    bool m_isSet;

}
class和gatetransformation:public-QAbstractTransition
{
Q_对象
公众:
AndGateTransformation(QAbstractState*sourceState):QAbstractTransition(sourceState)
m_isSet(false)、m_triggerOnSet(true)、m_triggerOnUnset(false)
无效集合触发器集合(布尔值)
{
m_triggerSet=val;
}
void setTriggerOnUnset(布尔值)
{
m_triggerOnUnset=val;
}
addState(QState*状态)
{
m_状态[状态]=假;
连接(m_状态,信号(entered()),此,插槽(stateActivated());
连接(m_状态,信号(exited()),此,插槽(statedactivated());
}
公众时段:
无效状态已激活()
{
QObject发送方=发送方();
如果(发送方==0)返回;
m_states[发送方]=真;
checkTrigger();
}
无效状态()
{
QObject发送方=发送方();
如果(发送方==0)返回;
m_states[发送方]=假;
checkTrigger();
}
void checkTrigger()
{
布尔集=真;
QHashIt迭代器(m_状态)
while(it.hasNext())
{
it.next();
set=set&&it.value();
如果(!set)中断;
}
if(m_集&&set&&m_集)
{
m_isSet=集合;
发射(触发());
}
elseif(m_triggerOnUnset&!set)&&
class AndGateTransition : public QAbstractTransition
{
    Q_OBJECT

public:

    AndGateTransition(QAbstractState* sourceState) : QAbstractTransition(sourceState)
        m_isSet(false), m_triggerOnSet(true), m_triggerOnUnset(false)

    void setTriggerSet(bool val)
    {
        m_triggerSet = val;
    }

    void setTriggerOnUnset(bool val)
    {
        m_triggerOnUnset = val;
    }

    addState(QState* state)
    {
        m_states[state] = false;
        connect(m_state, SIGNAL(entered()), this, SLOT(stateActivated());
        connect(m_state, SIGNAL(exited()), this, SLOT(stateDeactivated());
    }

public slots:
    void stateActivated()
    {
        QObject sender = sender();
        if (sender == 0) return;
        m_states[sender] = true;
        checkTrigger();
    }

    void stateDeactivated()
    {
        QObject sender = sender();
        if (sender == 0) return;
        m_states[sender] = false;
        checkTrigger();
    }

    void checkTrigger()
    {
        bool set = true;
        QHashIterator<QObject*, bool> it(m_states)
        while (it.hasNext())
        {
            it.next();
            set = set&&it.value();
            if (! set) break;
        }

        if (m_triggerOnSet && set && !m_isSet)
        {
            m_isSet = set;
            emit (triggered());

        }
        elseif (m_triggerOnUnset && !set && m_isSet)
        {
            m_isSet = set;
            emit (triggered());
        }
    }

pivate:
    QHash<QObject*, bool> m_states;
    bool m_triggerOnSet;
    bool m_triggerOnUnset;
    bool m_isSet;

}
CONFIG += C++11
auto cs = [/*button, check1, check2, edit, */this](QState *s, QState *t, bool on_off) {
    s->assignProperty(button, "enabled", !on_off);
    s->addTransition(new QSignalTransition(check1, SIGNAL(clicked())));
    s->addTransition(new QSignalTransition(check2, SIGNAL(clicked())));
    s->addTransition(new QSignalTransition(edit, SIGNAL(textChanged(QString))));
    Transition *p = new Transition(this, on_off);
    p->setTargetState(t);
    s->addTransition(p);
};
#include "mainwindow.h"
#include <QLayout>
#include <QFrame>
#include <QSignalTransition>

struct MainWindow::Transition : QAbstractTransition {
    Transition(MainWindow *main_w, bool on_off) :
        main_w(main_w),
        on_off(on_off)
    {}

    virtual bool eventTest(QEvent *) {
        bool ok_int, ok_cond =
            main_w->check1->isChecked() &&
            main_w->check2->isChecked() &&
            main_w->edit->text().toInt(&ok_int) > 0 && ok_int;
        if (on_off)
            return ok_cond;
        else
            return !ok_cond;
    }

    virtual void onTransition(QEvent *) {}

    MainWindow *main_w;
    bool on_off;
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QFrame *f = new QFrame(this);
    QVBoxLayout *l = new QVBoxLayout;

    l->addWidget(check1 = new QCheckBox("Ok &1"));
    l->addWidget(check2 = new QCheckBox("Ok &2"));
    l->addWidget(edit = new QLineEdit());

    l->addWidget(button = new QPushButton("Enable &Me"));

    f->setLayout(l);
    setCentralWidget(f);

    QState *s1, *s2;
    sm = new QStateMachine(this);
    sm->addState(s1 = new QState());
    sm->addState(s2 = new QState());
    sm->setInitialState(s1);

    auto cs = [button, check1, check2, edit, this](QState *s, QState *t, bool on_off) {
        s->assignProperty(button, "enabled", !on_off);
        s->addTransition(new QSignalTransition(check1, SIGNAL(clicked())));
        s->addTransition(new QSignalTransition(check2, SIGNAL(clicked())));
        s->addTransition(new QSignalTransition(edit, SIGNAL(textChanged(QString))));
        Transition *tr = new Transition(this, on_off);
        tr->setTargetState(t);
        s->addTransition(tr);
    };
    cs(s1, s2, true);
    cs(s2, s1, false);

    sm->start();
}