代码之家  ›  专栏  ›  技术社区  ›  Ton van den Heuvel

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

  •  12
  • Ton van den Heuvel  · 技术社区  · 15 年前

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

    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 保持相同的状态?有没有其他/更好的方法可以使用状态机来解决这个问题?


    注意

    5 回复  |  直到 14 年前
        1
  •  10
  •   Harald Scheirich    15 年前

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

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

    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;
    
    }
    

    没有编译这个,甚至没有测试它,但它应该证明的原则

        2
  •  4
  •   merula    15 年前

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

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

    主窗口.h

    #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
    

    主窗口.cpp

    #include "MainWindow.h"
    #include "ui_MainWindow.h"
    
    MainWindow::MainWindow(QWidget *parent)
      : QMainWindow(parent), ui(new Ui::MainWindow), m_editValid(false)
    {
      ui->setupUi(this);
    
      QStateMachine* stateMachine = new QStateMachine(this);
      QState* noneValid = new QState(stateMachine);
      QState* oneValid = new QState(stateMachine);
      QState* twoValid = new QState(stateMachine);
      QState* threeValid = new QState(stateMachine);
    
      noneValid->addTransition(this, SIGNAL(checkBox1Checked()), oneValid);
      oneValid->addTransition(this, SIGNAL(checkBox1Checked()), twoValid);
      twoValid->addTransition(this, SIGNAL(checkBox1Checked()), threeValid);
      threeValid->addTransition(this, SIGNAL(checkBox1Unchecked()), twoValid);
      twoValid->addTransition(this, SIGNAL(checkBox1Unchecked()), oneValid);
      oneValid->addTransition(this, SIGNAL(checkBox1Unchecked()), noneValid);
    
      noneValid->addTransition(this, SIGNAL(checkBox2Checked()), oneValid);
      oneValid->addTransition(this, SIGNAL(checkBox2Checked()), twoValid);
      twoValid->addTransition(this, SIGNAL(checkBox2Checked()), threeValid);
      threeValid->addTransition(this, SIGNAL(checkBox2Unchecked()), twoValid);
      twoValid->addTransition(this, SIGNAL(checkBox2Unchecked()), oneValid);
      oneValid->addTransition(this, SIGNAL(checkBox2Unchecked()), noneValid);
    
      noneValid->addTransition(this, SIGNAL(editValid()), oneValid);
      oneValid->addTransition(this, SIGNAL(editValid()), twoValid);
      twoValid->addTransition(this, SIGNAL(editValid()), threeValid);
      threeValid->addTransition(this, SIGNAL(editInvalid()), twoValid);
      twoValid->addTransition(this, SIGNAL(editInvalid()), oneValid);
      oneValid->addTransition(this, SIGNAL(editInvalid()), noneValid);
    
      threeValid->assignProperty(ui->pushButton, "enabled", true);
      twoValid->assignProperty(ui->pushButton, "enabled", false);
      oneValid->assignProperty(ui->pushButton, "enabled", false);
      noneValid->assignProperty(ui->pushButton, "enabled", false);
    
      stateMachine->setInitialState(noneValid);
    
      stateMachine->start();
    }
    
    MainWindow::~MainWindow()
    {
      delete ui;
    }
    
    bool MainWindow::isEditValid() const
    {
      return m_editValid;
    }
    
    void MainWindow::setEditValid(bool value)
    {
      if (value == m_editValid)
      {
        return;
      }
      m_editValid = value;
      if (value)
      {
        emit editValid();
      } else {
        emit editInvalid();
      }
    }
    
    void MainWindow::on_checkBox1_stateChanged(int state)
    {
      if (state == Qt::Checked)
      {
        emit checkBox1Checked();
      } else {
        emit checkBox1Unchecked();
      }
    }
    
    void MainWindow::on_checkBox2_stateChanged(int state)
    {
      if (state == Qt::Checked)
      {
        emit checkBox2Checked();
      } else {
        emit checkBox2Unchecked();
      }
    }
    
    void MainWindow::on_spinBox_valueChanged (int i)
    {
      setEditValid(i > 0);
    }
    

        3
  •  1
  •   Amos    15 年前

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

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

        4
  •  1
  •   PiedPiper    15 年前

    设置你的重量输入小部件,这样就不可能输入小于零的重量。那你就不需要了 invalidWeight()

        5
  •  1
  •   CapelliC    10 年前

    编辑

    我重新打开了这个测试,愿意使用它,添加到.pro

    CONFIG += C++11
    

    我发现lambda语法已经改变了。。。捕获列表不能引用成员变量。这是正确的代码

    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();
    }