Как заставить этот спокойный конечный автомат работать?

У меня есть два виджета, которые могут быть проверены, и числовое поле записи, которое должно содержать значение, больше, чем нуль. Каждый раз, когда оба виджета были проверены, и числовое поле записи содержит значение, больше, чем нуль, кнопка должна быть включена. Я борюсь с определением надлежащего конечного автомата для этой ситуации. До сих пор у меня есть следующее:

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 остаться в том же состоянии? Есть ли другой / лучший способ, которым эта проблема может быть решена с помощью конечных автоматов?


Примечание. Существует бесчисленное (возможно лучше) способы решить эту проблему. Однако я только интересуюсь решением, которое использует конечный автомат. Я думаю, что такой простой вариант использования должен быть разрешимым использованием простого конечного автомата, правильно?

13
задан Ton van den Heuvel 27 August 2010 в 16:08
поделиться

4 ответа

Прочитав ваши требования, ответы и комментарии здесь, я думаю, что решение merula или что-то подобное - единственное чистое решение Statemachine.

Как было отмечено, для активации параллельного состояния 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;

}

Не компилировал это и даже не тестировал, но он должен продемонстрировать принцип

10
ответ дан 2 December 2019 в 00:03
поделиться

Настройте ваш виджет ввода веса так, чтобы не было возможности ввести вес меньше нуля. Тогда вам не понадобится invalidWeight()

1
ответ дан 2 December 2019 в 00:03
поделиться

Конечный автомат, который вы использовали выше, не соответствует тому, что вы описали. Использование конечного состояния неверно, потому что после ввода значения больше нуля я не вижу ничего, что мешало бы пользователю снова ввести ноль. Следовательно, действительные состояния не могут быть окончательными. Насколько я понимаю из вашего кода, пользователю разрешено изменять состояние виджетов в любом порядке. Ваша государственная машина должна обратить на это внимание.

Я бы использовал конечный автомат с четырьмя дочерними состояниями (нет действительного ввода, один действительный ввод, два действительных ввода, три действительных ввода). Очевидно, вы начинаете без действительного ввода. Каждый виджет может переключаться с «нет» на «один или обратно» (одинаковое количество для двух и трех). Когда введено три, все виджеты действительны (кнопка активирована). Для всех остальных состояний кнопка должна быть отключена при входе в состояние.

Я написал образец приложения. Главное окно содержит два QCheckBox: QSpinBox и QPushButton. В главном окне есть сигналы для облегчения записи переходов состояний. Они срабатывают при изменении состояния виджетов.

MainWindow.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

MainWindow.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
ответ дан 2 December 2019 в 00:03
поделиться

Когда мне приходится делать подобные вещи, я обычно использую сигналы и слоты. По сути, каждый из виджетов и числовое поле будет автоматически излучать сигналы при изменении своего состояния. Если вы свяжете каждый из них со слотом, который проверяет, все ли 3 объекта находятся в желаемом состоянии, и включает кнопку, если они есть, или отключает ее, если нет, то это должно упростить ситуацию.

Иногда вам также потребуется изменить состояние кнопки после ее нажатия.

[РЕДАКТИРОВАТЬ]: Я уверен, что есть способ сделать это с помощью конечных автоматов. Вы вернетесь только в том случае, если оба флажка установлены, и вы добавили недопустимый вес или вам также нужно будет вернуться, установив только один флажок? Если это первое, то вы можете настроить состояние RestoreProperties , которое позволит вам вернуться к состоянию флажка. В противном случае есть ли способ сохранить состояние перед проверкой правильности веса, вернуть все флажки, а затем восстановить состояние.

1
ответ дан 2 December 2019 в 00:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: