2010-03-28 23 views
12

Tôi có hai tiện ích con có thể được kiểm tra và trường nhập số phải chứa một giá trị lớn hơn 0. Bất cứ khi nào cả hai tiện ích đã được chọn và trường nhập số chứa giá trị lớn hơn 0, nút phải được bật. Tôi đang đấu tranh với việc xác định một máy trạng thái thích hợp cho tình huống này. Cho đến nay tôi có như sau:Làm cách nào để máy trạng thái Qt này hoạt động?

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

Vấn đề ở đây là quá trình chuyển đổi sau:

buttonEnabled->addTransition(this, SIGNAL(invalidWeight()), weightZero); 

gây ra tất cả các trạng thái con ở bang registerButtonDisabled cần được hoàn nguyên trạng thái ban đầu của họ. Đây là hành vi không mong muốn, vì tôi muốn các trạng thái ab duy trì trạng thái tương tự.

Làm cách nào để đảm bảo rằng ab vẫn ở trong cùng một trạng thái? Có cách nào khác/tốt hơn vấn đề này có thể được giải quyết bằng cách sử dụng máy nhà nước?


Lưu ý. Có vô số cách (được cho là tốt hơn) để giải quyết vấn đề này. Tuy nhiên, tôi chỉ quan tâm đến một giải pháp sử dụng một máy nhà nước. Tôi nghĩ rằng một trường hợp sử dụng đơn giản như vậy nên được giải quyết bằng cách sử dụng một máy trạng thái đơn giản, phải không?

Trả lời

10

Sau khi đọc yêu cầu của bạn và câu trả lời và nhận xét ở đây tôi nghĩ rằng giải pháp của merula hoặc một cái gì đó tương tự là giải pháp Statemachine thuần túy duy nhất.

Như đã được ghi nhận để làm cho trạng thái song song kích hoạt tín hiệu finished() tất cả các trạng thái bị vô hiệu hóa phải là trạng thái cuối cùng, nhưng điều này không thực sự là điều gì mà họ có thể bỏ chọn một trong các hộp kiểm và sau đó bạn phải di chuyển ra khỏi trạng thái cuối cùng. Bạn không thể làm điều đó vì FinalState không chấp nhận bất kỳ chuyển tiếp nào. Việc sử dụng FinalState để thoát khỏi trạng thái song song cũng làm cho trạng thái song song khởi động lại khi nó được nhập lại.

Một giải pháp có thể là mã hóa một quá trình chuyển đổi chỉ kích hoạt khi cả ba trạng thái ở trạng thái "tốt" và trạng thái thứ hai kích hoạt khi bất kỳ trạng thái nào không hoạt động. Sau đó, bạn thêm các trạng thái bị vô hiệu hóa và được kích hoạt vào trạng thái song song mà bạn đã có và kết nối nó với các chuyển tiếp nói trên. Điều này sẽ giữ trạng thái cho phép của nút đồng bộ với tất cả các trạng thái của các phần giao diện người dùng của bạn. Nó cũng sẽ cho phép bạn rời khỏi trạng thái song song và trở lại một tập hợp các cài đặt thuộc tính nhất quán.

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; 

} 

Không biên dịch này hoặc thậm chí kiểm tra nó, nhưng nó phải chứng minh nguyên tắc

+0

Có vẻ như mã hóa một quá trình chuyển đổi tùy chỉnh thực sự là con đường để đi đến đây. Tôi là một chút thất vọng rằng đối với một trường hợp sử dụng dường như tầm thường như vậy thực hiện phức tạp là cần thiết :( –

1

Khi tôi phải làm những việc như thế này, tôi thường sử dụng tín hiệu và khe. Về cơ bản, mỗi widget và hộp số sẽ tự động phát ra tín hiệu khi trạng thái của chúng thay đổi. Nếu bạn liên kết từng cái này với một vị trí kiểm tra xem tất cả 3 đối tượng có ở trạng thái mong muốn hay không và bật nút nếu chúng là hoặc vô hiệu hóa nếu chúng không có, thì điều đó sẽ đơn giản hóa mọi thứ.

Đôi khi bạn cũng sẽ cần phải thay đổi trạng thái nút khi bạn đã nhấp vào nút đó.

[EDIT]: Tôi chắc chắn có một số cách để làm điều này bằng cách sử dụng máy trạng thái, bạn sẽ chỉ hoàn nguyên trong trường hợp cả hai hộp được chọn và bạn đã thêm trọng số không hợp lệ hoặc bạn cũng sẽ cần hoàn nguyên chỉ với một hộp kiểm được chọn? Nếu đó là phiên bản cũ thì bạn có thể thiết lập trạng thái RestoreProperties cho phép bạn hoàn nguyên về trạng thái hộp đã chọn. Nếu không, có một số cách bạn có thể lưu trạng thái trước khi kiểm tra trọng số hợp lệ, hoàn nguyên tất cả các hộp kiểm, sau đó khôi phục trạng thái.

+0

Tôi thực sự muốn xem giải pháp bằng máy trạng thái. Tôi không thể tin rằng một trường hợp sử dụng tương đối đơn giản không thể được giải quyết thuận tiện bằng cách sử dụng một máy nhà nước. Trong trường hợp một giải pháp sử dụng máy nhà nước là không thể, tôi có thể sẽ sử dụng giải pháp mà bạn đề xuất. –

+0

Để trả lời câu hỏi của bạn, bạn có quyền giả định rằng tôi cần hoàn nguyên trong trường hợp chỉ chọn một trong các hộp kiểm. Nói cách khác, tôi muốn hoàn nguyên trong trường hợp một trong các điều kiện tiên quyết cho phép nút không còn hài lòng nữa. Khung máy trạng thái không hỗ trợ các trạng thái lịch sử ('QHistoryState'), nhưng tôi không thấy cách tôi có thể làm cho chúng hoạt động cho vấn đề của tôi. –

+0

Có thể thiết lập một số loại trạng thái lỗi không, như vậy máy nhà nước có thể hoàn nguyên về trạng thái thích hợp về lỗi.Theo cách tương tự với Bộ dò tín hiệu kết nối với khe cắm tùy thuộc vào nơi tín hiệu bắt nguồn. Tôi nghi ngờ rằng một giải pháp như vậy có thể trở nên rất mệt mỏi rất nhanh khi đối mặt với rất nhiều trạng thái thất bại. – Amos

1

Thiết lập tiện ích con đầu vào trọng lượng của bạn để không có cách nào có thể nhập trọng lượng dưới 0. Sau đó, bạn không cần invalidWeight()

+0

Tôi đã sử dụng một ví dụ đơn giản ở đây. Trong ứng dụng của tôi, tôi có một bàn phím số đầu vào nơi người dùng phải nhập một giá trị. Chỉ trong trường hợp người dùng đã nhập giá trị, nút trong ví dụ sẽ được bật. Một lần nữa, tôi không thực sự quan tâm đến các giải pháp thay thế, tôi quan tâm đến một giải pháp sử dụng các máy trạng thái. –

3

Máy trạng thái bạn đã sử dụng ở trên không tương ứng với những gì bạn đã mô tả. Sử dụng trạng thái cuối cùng không chính xác vì sau khi nhập giá trị lớn hơn 0, tôi không thấy bất kỳ điều gì ngăn người dùng nhập lại 0. Do đó các trạng thái hợp lệ không thể là cuối cùng. Theo như tôi thấy từ mã của bạn, người dùng được phép thay đổi trạng thái của các tiện ích theo bất kỳ thứ tự nào. Máy nhà nước của bạn phải chú ý đến điều này.

Tôi sẽ sử dụng máy trạng thái có bốn trạng thái con (không có đầu vào hợp lệ, một đầu vào hợp lệ, hai đầu vào hợp lệ, ba đầu vào hợp lệ). Bạn rõ ràng bắt đầu với không có đầu vào hợp lệ. Mỗi widget có thể thực hiện một quá trình chuyển đổi từ không có một trở lại (cùng một số cho hai và ba). Khi ba được nhập vào tất cả các vật dụng là hợp lệ (nút kích hoạt). Đối với tất cả các trạng thái khác, nút phải được tắt khi trạng thái được nhập.

Tôi đã viết một ứng dụng mẫu. Cửa sổ chính chứa hai QCheckBoxes một QSpinBox và một QPushButton. Có các tín hiệu trong cửa sổ chính dễ dàng ghi lại các chuyển tiếp của các trạng thái.Có được kích hoạt khi trạng thái của các widget được thay đổi.

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

này nên làm các trick. Như bạn đã đề cập, có những cách tốt hơn để đạt được hành vi này. Đặc biệt là theo dõi tất cả các transistions giữa nhà nước là dễ bị lỗi.

+0

Bạn nói rằng trạng thái gốc được để lại trong trường hợp một trong các trạng thái con đi vào trạng thái cuối cùng. Điều này là không chính xác mặc dù như tôi sử dụng một trạng thái cha mẹ song song. Trạng thái gốc song song chỉ vào trạng thái cuối cùng của nó trong trường hợp tất cả các trạng thái con đã nhập trạng thái cuối cùng của chúng. Từ tài liệu Qt: "Đối với các nhóm trạng thái song song, tín hiệu QState :: finished() được phát ra khi tất cả các trạng thái con đã nhập vào trạng thái cuối cùng.". –

+0

Tôi đã nghĩ về một giải pháp tương tự như của bạn. Tuy nhiên, nó không phải là âm thanh. Giả sử nút đăng ký được bật và người dùng nhập số không hợp lệ hai lần, người dùng phải nhập lại một số hợp lệ hai lần để bật lại nút. –

+0

Tôn, nhờ làm rõ mối quan hệ của các trạng thái cuối cùng và trạng thái song song. Tôi không biết điều này. Nhưng ngay cả khi trạng thái cuối cùng đạt được thì không có cách nào quay lại trạng thái không hợp lệ. Các trạng thái lịch sử không giúp ích gì khi chúng bảo toàn trạng thái khi chúng ở trạng thái gốc. Không có gì đảm bảo rằng người dùng sẽ nhập lại trạng thái không hợp lệ giống như cách anh ta để lại nó. Tôi đề xuất sửa chữa lỗi mà bạn đề cập trong bình luận thứ hai của bạn (xem mã ở trên) và để đảm bảo rằng tín hiệu hộp kéo chỉ được kích hoạt trên các thay đổi trạng thái thực. – merula

1

chỉnh sửa

Tôi mở cửa trở lại thử nghiệm này, sẵn sàng để sử dụng nó, thêm vào .pro

CONFIG += C++11 

và tôi phát hiện ra rằng cú pháp lambda đã thay đổi ... Danh sách chụp không thể tham chiếu các biến thành viên. Đây là mã sửa

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

cuối chỉnh sửa

tôi đã sử dụng câu hỏi này như tập thể dục (lần đầu tiên trên QStateMachine). Giải pháp khá nhỏ gọn, sử dụng chuyển tiếp được bảo vệ để di chuyển giữa trạng thái 'đã bật/tắt' và lambda để thiết lập hệ số:

#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(); 
} 
Các vấn đề liên quan