13

チェックできる 2 つのウィジェットと、ゼロより大きい値を含む数値入力フィールドがあります。両方のウィジェットがチェックされ、数値入力フィールドにゼロより大きい値が含まれている場合は常に、ボタンを有効にする必要があります。この状況に適したステート マシンを定義するのに苦労しています。これまでのところ、次のものがあります。

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ステートマシンを使用してこの問題を解決できる別の/より良い方法はありますか?


. この問題を解決するには、無数の (おそらくより良い) 方法があります。ただし、ステート マシンを使用するソリューションにのみ関心があります。このような単純なユースケースは、単純なステートマシンを使用して解決できるはずだと思いますよね?

4

5 に答える 5

10

ここで要件と回答およびコメントを読んだ後、merulaのソリューションまたは同様のものが唯一の純粋なステートマシンソリューションだと思います。

Parallel Stateがfinished()信号を発するように注意されているように、すべての無効な状態は最終状態である必要がありますが、誰かがチェックボックスの1つをオフにすると、最終状態から離れる必要があるため、これは実際にはそうではありません。州。FinalStateは遷移を受け入れないため、これを行うことはできません。FinalStateを使用して並列状態を終了すると、並列状態が再起動したときにも再起動します。

1つの解決策は、3つの状態すべてが「良好」状態のときにのみトリガーされる遷移と、それらのいずれかがそうでないときにトリガーされる遷移をコード化することです。次に、無効状態と有効状態を既存の並列状態に追加し、前述の遷移に接続します。これにより、ボタンの有効な状態が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;

}

これをコンパイルしたり、テストしたりしませんでしたが、原則を実証する必要があります

于 2010-04-04T12:21:34.820 に答える
4

上で使用したステート マシンは、説明したものと一致しません。ゼロより大きい値を入力した後、ユーザーが再びゼロを入力するのを妨げるものは何も表示されないため、最終状態の使用は正しくありません。したがって、有効な状態を最終的なものにすることはできません。コードからわかる限り、ユーザーはウィジェットの状態を任意の順序で変更できます。ステート マシンはこれに注意を払う必要があります。

4 つの子ステート (有効な入力なし、1 つの有効な入力、2 つの有効な入力、3 つの有効な入力) を持つステート マシンを使用します。明らかに、有効な入力なしで開始します。各ウィジェットは、「いいえ」から「1 つ戻る」への遷移を行うことができます (2 つと 3 つでも同じカウント)。3 つ入力すると、すべてのウィジェットが有効になります (ボタンが有効になります)。他のすべての状態では、状態に入ったときにボタンを無効にする必要があります。

サンプルアプリを書きました。メイン ウィンドウには、QSpinBox と QPushButton の 2 つの QCheckBox があります。メインウィンドウには信号があり、状態の遷移を簡単に書き留めることができます。ウィジェットの状態が変更されたときに発生します。

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

これでうまくいくはずです。あなた自身がすでに述べたように、この動作を達成するためのより良い方法があります。特に、状態間のすべての遷移を追跡すると、エラーが発生しやすくなります。

于 2010-03-31T10:25:36.687 に答える
1

ゼロ未満の体重を入力できないように、体重入力ウィジェットを設定します。その後、あなたは必要ありませんinvalidWeight()

于 2010-03-29T10:06:33.007 に答える
1

このようなことをしなければならないときは、通常、シグナルとスロットを使用します。基本的に、各ウィジェットとナンバーボックスは、状態が変化すると自動的にシグナルを発します。これらのそれぞれをスロットにリンクして、3 つのオブジェクトすべてが目的の状態にあるかどうかを確認し、そうでない場合はボタンを有効にし、そうでない場合はボタンを無効にすると、物事が簡素化されます。

クリックしたボタンの状態を変更する必要がある場合もあります。

[編集]: ステート マシンを使用してこれを行う方法があると確信しています。両方のボックスがチェックされていて無効な重みを追加した場合にのみ元に戻すのでしょうか、それとも 1 つだけで元に戻す必要がありますか?チェックボックスはチェックされていますか?前者の場合は、チェック ボックスの状態に戻すことができるRestoreProperties状態を設定できる場合があります。それ以外の場合は、重みが有効であることを確認する前に状態を保存し、すべてのチェックボックスを元に戻してから状態を復元する方法があります。

于 2010-03-28T14:40:39.607 に答える
1

編集

このテストを再開し、喜んで使用し、.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);
};

編集を終了

この質問を演習として使用しました(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();
}
于 2013-03-17T20:57:13.850 に答える