Подтвердить что ты не робот

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


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

4b9b3361

Ответ 1

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

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

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

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

Я написал пример приложения. Главное окно содержит два QCheckBoxes - 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

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

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

[EDIT]: Я уверен, что есть какой-то способ сделать это с использованием государственных машин, вы будете только возвращаться в ситуации, когда оба флажка отмечены, и вы добавили недопустимый вес, или вам также нужно будет вернуться с проверкой только одного флажка? Если это первый, вы можете настроить RestoreProperties, что позволяет вернуться к состоянию отмеченного поля. В противном случае можно каким-то образом сохранить состояние, прежде чем проверка веса будет действительна, верните все флажки, а затем восстановите состояние.

Ответ 4

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

Ответ 5

изменить

Я снова открыл этот тест, желая его использовать, добавленный в .pro

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

end edit

Я использовал этот вопрос как упражнение (первый раз на QStateMachine). Решение довольно компактно, используя защищенный переход для перехода между состоянием "включено/отключено" и лямбда-факторизация:

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