이 페이지에서

Qt State Machine C++ 가이드

상태 머신 프레임워크는 상태 그래프를 생성하고 실행하기 위한 클래스를 제공합니다. 이 페이지에서는 C++을 사용하여 이 프레임워크의 주요 기능을 설명합니다.

상태 머신 프레임워크의 C++ 클래스

상태 기계 프레임워크에 포함된 C++ 클래스의 전체 목록은 다음을 참조하십시오. Qt State Machine C++ Classes

간단한 상태 머신

상태 머신 API의 핵심 기능을 보여주기 위해 간단한 예제를 살펴보겠습니다. s1, s2, s3 의 세 가지 상태를 가진 상태 머신입니다. 이 상태 머신은 하나의 QPushButton 에 의해 제어되며, 버튼이 클릭되면 머신이 다른 상태로 전환됩니다. 초기에는 상태 머신이 s1 상태에 있습니다. 이 머신의 상태도는 다음과 같습니다:

버튼 클릭 시 s1, s2, s3 세 가지 상태가 순환하는 전환 효과

다음 코드 조각은 이러한 상태 머신을 생성하는 데 필요한 코드를 보여줍니다. 먼저, 상태 머신과 상태들을 생성합니다:

    QStateMachine machine;
    QState *s1 = new QState();
    QState *s2 = new QState();
    QState *s3 = new QState();

그런 다음, ` QState::addTransition()` 함수를 사용하여 전환을 생성합니다:

    s1->addTransition(button, &QPushButton::clicked, s2);
    s2->addTransition(button, &QPushButton::clicked, s3);
    s3->addTransition(button, &QPushButton::clicked, s1);

다음으로, 상태를 머신에 추가하고 머신의 초기 상태를 설정합니다:

    machine.addState(s1);
    machine.addState(s2);
    machine.addState(s3);
    machine.setInitialState(s1);

마지막으로, 상태 머신을 시작합니다:

    machine.start();

상태 머신은 비동기적으로 실행되며, 즉 애플리케이션의 이벤트 루프의 일부가 됩니다.

상태 진입 및 종료 시 유용한 작업 수행

위의 상태 머신은 단순히 한 상태에서 다른 상태로 전환할 뿐, 어떠한 작업도 수행하지 않습니다. QState::assignProperty() 함수를 사용하면 상태에 진입할 때 해당 상태가 QObject 의 속성을 설정하도록 할 수 있습니다. 다음 코드 조각에서는 각 상태에 대해 QLabel 의 text 속성에 할당될 값이 지정되어 있습니다:

    s1->assignProperty(label, "text", "In state s1");
    s2->assignProperty(label, "text", "In state s2");
    s3->assignProperty(label, "text", "In state s3");

어떤 상태로 진입하든 라벨의 텍스트가 그에 따라 변경됩니다.

QState::entered() 신호는 상태에 진입할 때 발송되며, QState::exited() 신호는 상태에서 벗어날 때 발송됩니다. 다음 코드 조각에서는 상태 s3 에 진입하면 버튼의 showMaximized() 슬롯이 호출되고, s3 상태에서 벗어날 때 버튼의 showMinimized() 슬롯이 호출됩니다:

    QObject::connect(s3, &QState::entered, button, &QPushButton:showMaximized);
    QObject::connect(s3, &QState::exited, button, &QPushButton::showMinimized);

사용자 정의 상태는 QAbstractState::onEntry()와 QAbstractState::onExit()를 재구현할 수 있습니다.

종료되는 상태 머신

이전 섹션에서 정의한 상태 기계는 결코 종료되지 않습니다. 상태 기계가 종료되려면 최상위 최종 상태(QFinalState 객체)가 있어야 합니다. 상태 기계가 최상위 최종 상태에 진입하면, 기계는 QStateMachine::finished() 신호를 발산하고 정지합니다.

그래프에 최종 상태를 도입하려면 QFinalState 객체를 생성하고, 이를 하나 이상의 전환의 대상으로 지정하기만 하면 됩니다.

상태 그룹화를 통한 전환 공유

사용자가 '종료(Quit)' 버튼을 클릭하여 언제든지 애플리케이션을 종료할 수 있도록 하려고 한다고 가정해 봅시다. 이를 구현하려면 최종 상태를 생성하고, 이를 '종료(Quit)' 버튼의 clicked() 신호와 연결된 전환의 대상으로 지정해야 합니다. s1, s2, s3 각각에서 전환을 추가할 수도 있겠지만, 이는 중복된 작업으로 보일 뿐만 아니라, 향후 추가되는 모든 새로운 상태에서도 이러한 전환을 추가해야 한다는 점을 기억해야 합니다.

s1, s2 및 s3 상태를 그룹화하면 동일한 동작(즉, 상태 머신이 어떤 상태에 있든 상관없이 ‘종료’ 버튼을 클릭하면 상태 머신이 종료됨)을 구현할 수 있습니다. 이를 위해 새로운 최상위 상태를 생성하고, 기존 세 상태를 이 새로운 상태의 자식 상태로 지정합니다. 다음 다이어그램은 새로운 상태 머신을 보여줍니다.

버튼 클릭 시 전환되며, 세 개의 하위 상태 s11, s12, s13을 포함하는 상태 s1

기존의 세 가지 상태는 이제 새로운 최상위 상태인 s1 의 자식 상태임을 반영하여 s11, s12 및 s13 로 이름이 변경되었습니다. 자식 상태는 부모 상태의 전환을 암묵적으로 상속받습니다. 즉, 이제 s1 에서 최종 상태인 s2 로 가는 단일 전환을 추가하는 것만으로도 충분합니다. s1 에 추가된 새로운 상태들도 이 전환을 자동으로 상속받게 됩니다.

상태들을 그룹화하기 위해서는 상태를 생성할 때 적절한 부모 상태를 지정하기만 하면 됩니다. 또한 자식 상태 중 어느 것이 초기 상태인지(즉, 부모 상태가 전환의 대상일 때 상태 머신이 진입해야 할 자식 상태가 어느 것인지)를 지정해야 합니다.

    QState *s1 = new QState();
    QState *s11 = new QState(s1);
    QState *s12 = new QState(s1);
    QState *s13 = new QState(s1);
    s1->setInitialState(s11);
    machine.addState(s1);
    QFinalState *s2 = new QFinalState();
    s1->addTransition(quitButton, &QPushButton::clicked, s2);
    machine.addState(s2);
    machine.setInitialState(s1);

    QObject::connect(&machine, &QStateMachine::finished,
                     QCoreApplication::instance(), &QCoreApplication::quit);

이 경우, 상태 머신이 종료되면 애플리케이션도 종료되도록 하려면, 상태 머신의 ` finished()` 신호를 애플리케이션의 ` quit()` 슬롯에 연결해야 합니다.

자식 상태는 상속된 전이를 재정의할 수 있습니다. 예를 들어, 다음 코드는 상태 기계가 s12 상태에 있을 때 Quit 버튼을 무시하도록 하는 전이를 추가합니다.

    s12->addTransition(quitButton, &QPushButton::clicked, s12);

전환의 대상은 어떤 상태든 될 수 있습니다. 즉, 대상 상태가 소스 상태와 상태 계층 구조에서 같은 수준에 있을 필요는 없습니다.

히스토리 상태를 사용하여 현재 상태 저장 및 복원하기

이전 섹션에서 다룬 예제에 "중단" 메커니즘을 추가하고 싶다고 가정해 봅시다. 사용자는 버튼을 클릭하여 상태 머신이 관련 없는 작업을 수행하게 할 수 있어야 하며, 그 후 상태 머신은 이전에 수행하던 작업을 재개해야 합니다(즉, 이전 상태로 돌아가야 하며, 이 경우 s11, s12, s13 중 하나에 해당합니다).

이러한 동작은 히스토리 상태를 사용하여 쉽게 모델링할 수 있습니다. 히스토리 상태(QHistoryState 객체)는 부모 상태가 마지막으로 종료되었을 때 부모 상태가 있던 자식 상태를 나타내는 의사 상태입니다.

히스토리 상태는 현재 자식 상태를 기록하고자 하는 상태의 자식으로 생성되며, 상태 머신이 런타임에 이러한 상태의 존재를 감지하면 부모 상태를 종료할 때 현재(실제) 자식 상태를 자동으로 기록합니다. 히스토리 상태로 전환하는 것은 사실 상태 머신이 이전에 저장해 둔 자식 상태로 전환하는 것이며, 상태 머신은 이 전환을 실제 자식 상태로 자동으로 “전달”합니다.

다음 다이어그램은 인터럽트 메커니즘이 추가된 후의 상태 머신을 보여줍니다.

하위 상태와 이력 상태를 갖는 상태 s1에서, 인터럽트 버튼을 누르면 s3으로 전환되며, 작업이 완료되면 s1의 이력 상태로 돌아갑니다.

다음 코드는 이를 구현하는 방법을 보여줍니다. 이 예제에서는 ` s3 `로 진입할 때 메시지 상자를 표시한 다음, 즉시 히스토리 상태를 통해 이전 자식 상태인 ` s1 `로 되돌아갑니다.

    QHistoryState *s1h = new QHistoryState(s1);

    QState *s3 = new QState();
    s3->assignProperty(label, "text", "In s3");
    QMessageBox *mbox = new QMessageBox(mainWindow);
    mbox->addButton(QMessageBox::Ok);
    mbox->setText("Interrupted!");
    mbox->setIcon(QMessageBox::Information);
    QObject::connect(s3, &QState::entered, mbox, &QMessageBox::exec);
    s3->addTransition(s1h);
    machine.addState(s3);

    s1->addTransition(interruptButton, &QPushButton::clicked, s3);

병렬 상태를 사용하여 상태의 조합적 폭발 방지

단일 상태 기계에서 자동차의 상호 배타적인 속성 집합을 모델링하고자 한다고 가정해 봅시다. 우리가 관심 있는 속성이 ‘깨끗함 대 더러움’과 ‘움직임 대 정지’라고 합시다. 가능한 모든 조합을 표현하고 자유롭게 이동하려면 네 개의 상호 배타적인 상태와 여덟 개의 전환이 필요합니다.

네 가지 상태: 깨끗함/정지, 깨끗함/이동, 더러움/정지, 더러움/이동 (시작, 정지, 청소, 오염 전환 포함)

여기에 세 번째 속성(예: 빨간색 대 파란색)을 추가하면 총 상태 수는 두 배인 8개가 되고, 네 번째 속성(예: 쿠페 대 컨버터블)을 추가하면 총 상태 수는 다시 두 배인 16개가 됩니다.

병렬 상태를 사용하면 속성을 더 추가할 때마다 상태와 전환의 총 개수가 기하급수적으로 증가하는 대신 선형적으로 증가합니다. 또한, 병렬 상태에서 상태를 추가하거나 제거하더라도 다른 병렬 상태에는 아무런 영향을 미치지 않습니다.

영역 s11(깨끗함/더러움)과 s12(움직임/움직임 없음)을 포함하는 병렬 상태 s1

병렬 상태 그룹을 생성하려면 ` QState ` 생성자에 ` QState::ParallelStates `을 전달하십시오.

    QState *s1 = new QState(QState::ParallelStates);
    // s11 and s12 will be entered in parallel
    QState *s11 = new QState(s1);
    QState *s12 = new QState(s1);

병렬 상태 그룹에 진입하면, 그 모든 자식 상태가 동시에 진입하게 됩니다. 개별 자식 상태 내의 전환은 정상적으로 작동합니다. 그러나 자식 상태 중 어느 하나라도 부모 상태를 종료하는 전환을 수행할 수 있습니다. 이 경우, 부모 상태와 그 모든 자식 상태에서 벗어나게 됩니다.

상태 머신 프레임워크의 병렬 처리는 인터리브(interleaved) 의미론을 따릅니다. 모든 병렬 작업은 이벤트 처리의 단일 원자적 단계에서 실행되므로, 어떤 이벤트도 병렬 작업을 중단시킬 수 없습니다. 그러나 머신 자체가 단일 스레드이므로 이벤트는 여전히 순차적으로 처리됩니다. 예를 들어, 동일한 병렬 상태 그룹을 종료하는 두 개의 전환이 있고, 이들의 조건이 동시에 참이 되는 상황을 생각해 봅시다. 이 경우, 첫 번째 이벤트가 이미 머신이 병렬 상태에서 종료되도록 했기 때문에, 두 이벤트 중 마지막으로 처리되는 이벤트는 아무런 영향을 미치지 않습니다.

복합 상태의 완료 감지

자식 상태는 최종 상태( QFinalState 객체)일 수 있습니다. 최종 자식 상태에 진입하면 부모 상태는 QState::finished() 신호를 방출합니다. 다음 다이어그램은 최종 상태에 진입하기 전에 일부 처리를 수행하는 복합 상태 s1 를 보여줍니다:

s1 상태에서 처리 하위 상태를 거쳐 최종 상태로 전환되며, 이로 인해 s2로 이동이 발생한다.

s1 의 최종 상태에 진입하면, s1 는 자동으로 finished() 신호를 발산합니다. 신호 전환을 사용하여 이 이벤트가 상태 변화를 유발하도록 합니다:

  s1->addTransition(s1, &QState::finished, s2);

복합 상태에서 최종 상태를 사용하는 것은 복합 상태의 내부 세부 사항을 숨기고 싶을 때 유용합니다. 즉, 외부에서 할 수 있는 유일한 작업은 해당 상태로 진입하고, 상태가 작업을 완료했을 때 알림을 받는 것뿐이어야 합니다. 이는 복잡한(깊게 중첩된) 상태 머신을 구축할 때 매우 강력한 추상화 및 캡슐화 메커니즘입니다. (위의 예제에서, s1 의 finished() 신호에 의존하는 대신 s1 의 done 상태에서 직접 전환을 생성할 수도 있겠지만, 그럴 경우 s1 의 구현 세부 사항이 노출되고 이에 의존하게 되는 결과가 초래됩니다.)

병렬 상태 그룹의 경우, 모든 자식 상태가 최종 상태에 진입하면 QState::finished() 신호가 발생합니다.

대상 없는 전환

전환에 대상 상태가 반드시 필요한 것은 아닙니다. 대상이 없는 전환은 다른 전환과 동일한 방식으로 트리거될 수 있습니다. 차이점은 대상이 없는 전환이 트리거되어도 상태 변화가 발생하지 않는다는 점입니다. 이를 통해 머신이 특정 상태에 있을 때 해당 상태를 벗어나지 않고도 신호나 이벤트에 반응할 수 있습니다. 예시:

QStateMachine machine;
QState *s1 = new QState(&machine);

QPushButton button;
QSignalTransition *trans = new QSignalTransition(&button, &QPushButton::clicked);
s1->addTransition(trans);

QMessageBox msgBox;
msgBox.setText("The button was clicked; carry on.");
QObject::connect(trans, QSignalTransition::triggered, &msgBox, &QMessageBox::exec);

machine.setInitialState(s1);

버튼을 클릭할 때마다 메시지 상자가 표시되지만, 상태 머신은 현재 상태(s1)를 유지합니다. 그러나 대상 상태를 명시적으로 s1로 설정했다면, 매번 s1에서 빠져나왔다가 다시 진입하게 될 것입니다(예: QAbstractState::entered() 및 QAbstractState::exited() 신호가 발생함).

이벤트, 전환 및 가드

QStateMachine 는 자체 이벤트 루프를 실행합니다. 신호 전환(QSignalTransition 객체)의 경우, QStateMachine 는 해당 신호를 가로챌 때 자동으로 QStateMachine::SignalEvent 를 자체적으로 게시합니다. 마찬가지로, QObject 이벤트 전환(QEventTransition 객체)의 경우에도 QStateMachine::WrappedEvent 가 게시됩니다.

QStateMachine::postEvent()를 사용하여 상태 머신에 사용자 정의 이벤트를 게시할 수 있습니다.

상태 머신에 사용자 정의 이벤트를 게시할 때는 일반적으로 해당 유형의 이벤트에서 트리거될 수 있는 하나 이상의 사용자 정의 전환도 함께 정의합니다. 이러한 전환을 생성하려면 QAbstractTransition 을 서브클래스화하고 eventTest()를 재구현하여, 이벤트가 사용자 정의 이벤트 유형과 일치하는지(그리고 선택적으로 이벤트 객체의 속성 등 다른 기준도 충족하는지) 확인합니다.

여기서는 상태 머신에 문자열을 전송하기 위한 사용자 정의 이벤트 유형인 ` StringEvent`를 정의합니다:

struct StringEvent : public QEvent
{
    StringEvent(const QString &val)
    : QEvent(QEvent::Type(QEvent::User+1)),
      value(val) {}

    QString value;
};

다음으로, 이벤트의 문자열이 특정 문자열과 일치할 때만 트리거되는 전환( 가드된 전환)을 정의합니다:

class StringTransition : public QAbstractTransition
{
    Q_OBJECT

public:
    StringTransition(const QString &value)
        : m_value(value) {}

protected:
    bool eventTest(QEvent *e) override
    {
        if (e->type() != QEvent::Type(QEvent::User+1)) // StringEvent
            return false;
        StringEvent *se = static_cast<StringEvent*>(e);
        return (m_value == se->value);
    }

    void onTransition(QEvent *) override {}

private:
    QString m_value;
};

eventTest()의 재구현에서는 먼저 이벤트 유형이 원하는 유형인지 확인하고, 그렇다면 이벤트를 StringEvent 로 형변환한 후 문자열 비교를 수행합니다.

다음은 사용자 정의 이벤트와 전환을 사용하는 상태 차트입니다:

사용자 정의 이벤트 처리를 위한 StringTransition을 사용하는 s1 및 s2 상태

상태 차트의 구현은 다음과 같습니다:

    QStateMachine machine;
    QState *s1 = new QState();
    QState *s2 = new QState();
    QFinalState *done = new QFinalState();

    StringTransition *t1 = new StringTransition("Hello");
    t1->setTargetState(s2);
    s1->addTransition(t1);
    StringTransition *t2 = new StringTransition("world");
    t2->setTargetState(done);
    s2->addTransition(t2);

    machine.addState(s1);
    machine.addState(s2);
    machine.addState(done);
    machine.setInitialState(s1);

상태 머신이 시작되면, 이벤트들을 해당 머신에 게시할 수 있습니다.

    machine.postEvent(new StringEvent("Hello"));
    machine.postEvent(new StringEvent("world"));

관련된 어떤 전환에서도 처리되지 않는 이벤트는 상태 머신에 의해 조용히 소비됩니다. 상태를 그룹화하고 이러한 이벤트에 대한 기본 처리를 제공하는 것이 유용할 수 있습니다. 예를 들어, 다음 상태도에서 보여주는 것처럼:

s1 상태와 하위 상태 s11 및 s12, 사용자 정의 이벤트 전환, 완료 시 상태 축하 기능

깊게 중첩된 상태 차트의 경우, 가장 적절한 세분화 수준에서 이러한 "폴백" 전환을 추가할 수 있습니다.

복원 정책을 사용하여 속성을 자동으로 복원하기

일부 상태 머신에서는 상태가 더 이상 활성화되지 않았을 때 속성을 복원하는 데 집중하기보다, 상태 내에서 속성을 할당하는 데 주의를 기울이는 것이 유용할 수 있습니다. 상태 머신이 해당 속성에 명시적으로 값을 부여하지 않는 상태로 진입할 때 속성이 항상 초기값으로 복원되어야 한다는 것을 알고 있다면, 전역 복원 정책을 QStateMachine::RestoreProperties로 설정할 수 있습니다.

QStateMachine machine;
machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);

이 복원 정책이 설정되면, 머신은 모든 속성을 자동으로 복원합니다. 특정 속성이 설정되지 않은 상태로 진입하면, 먼저 상위 계층 구조를 검색하여 해당 속성이 정의되어 있는지 확인합니다. 정의되어 있다면, 해당 속성은 가장 가까운 상위 상태에서 정의된 값으로 복원됩니다. 정의되어 있지 않다면, 초기 값(즉, 상태에서 속성 할당 명령이 실행되기 전의 속성 값)으로 복원됩니다.

다음 코드를 예로 들어 보겠습니다.

    QStateMachine machine;
    machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);

    QState *s1 = new QState();
    s1->assignProperty(object, "fooBar", 1.0);
    machine.addState(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState();
    machine.addState(s2);

머신이 시작될 때 fooBar 속성의 값이 0.0이라고 가정해 봅시다. 머신이 s1 상태에 있을 때, 해당 상태가 이 값을 명시적으로 할당하므로 속성의 값은 1.0이 됩니다. 머신이 s2 상태에 있을 때, 해당 속성에 대해 명시적으로 정의된 값이 없으므로 암시적으로 0.0으로 복원됩니다.

중첩된 상태를 사용하는 경우, 부모 상태가 속성의 값을 정의하면 해당 속성에 값을 명시적으로 할당하지 않은 모든 자손 상태가 그 값을 상속받습니다.

    QStateMachine machine;
    machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);

    QState *s1 = new QState();
    s1->assignProperty(object, "fooBar", 1.0);
    machine.addState(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(s1);
    s2->assignProperty(object, "fooBar", 2.0);
    s1->setInitialState(s2);

    QState *s3 = new QState(s1);

여기서 s1 에는 s2 와 s3 라는 두 개의 자식 상태가 있습니다. s2 상태로 진입하면, 해당 상태에 대해 명시적으로 정의되어 있으므로 fooBar 속성의 값은 2.0이 됩니다. 기계가 s3 상태에 있을 때는 해당 상태에 대한 값이 정의되어 있지 않지만, s1 에서 속성을 1.0으로 정의하므로, 이 값이 fooBar 에 할당됩니다.

애니메이션과 상태 머신

상태 머신(State Machine) API는 애니메이션 프레임워크(Animation Framework )와 연동되어, 상태에서 속성이 할당될 때 자동으로 애니메이션이 적용되도록 합니다.

상태 머신은 애니메이션을 재생할 수 있는 특수한 상태를 제공합니다. 상태 전환기( QState )는 상태에 진입하거나 벗어날 때 속성을 설정할 수도 있으며, 이 특수한 애니메이션 상태는 상태 전환기( QPropertyAnimation)가 주어지면 이러한 값들 사이에서 보간을 수행합니다.

QSignalTransition 또는 QEventTransition 클래스를 사용하여 상태 간 전환에 하나 이상의 애니메이션을 연결할 수 있습니다. 이 두 클래스는 모두 QAbstractTransition 에서 파생되었으며, 이 클래스는 전환이 발생할 때 트리거되는 하나 이상의 애니메이션을 추가할 수 있게 해주는 편의 함수 addAnimation()를 정의합니다.

또한 시작값과 종료값을 직접 설정하는 대신, 속성을 상태와 연관시킬 수도 있습니다.

다음과 같은 코드가 있다고 가정해 봅시다.

    QState *s1 = new QState();
    QState *s2 = new QState();

    s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));
    s2->assignProperty(button, "geometry", QRectF(0, 0, 100, 100));

    s1->addTransition(button, &QPushButton::clicked, s2);

여기서는 사용자 인터페이스의 두 가지 상태를 정의합니다. s1 에서는 button 의 크기가 작고, s2 에서는 더 큽니다. 버튼을 클릭하여 s1 에서 s2 로 전환하면, 지정된 상태에 진입하는 즉시 버튼의 기하학적 속성이 설정됩니다. 하지만 전환을 부드럽게 만들고 싶다면, QPropertyAnimation 를 생성하여 이를 전환 객체에 추가하기만 하면 됩니다.

    QState *s1 = new QState();
    QState *s2 = new QState();

    s1->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));
    s2->assignProperty(button, "geometry", QRectF(0, 0, 100, 100));

    QSignalTransition *transition = s1->addTransition(button, &QPushButton::clicked, s2);
    transition->addAnimation(new QPropertyAnimation(button, "geometry"));

해당 속성에 애니메이션을 추가하면, 상태에 진입했을 때 속성 할당이 더 이상 즉시 적용되지 않습니다. 대신, 상태에 진입하면 애니메이션이 재생되기 시작하며 속성 할당 값이 부드럽게 변경됩니다. 애니메이션의 시작값이나 끝값을 직접 설정하지 않았으므로, 이 값들은 암시적으로 설정됩니다. 애니메이션의 시작 값은 애니메이션이 시작될 때 속성의 현재 값이 되며, 끝 값은 해당 상태에 정의된 속성 할당 내용을 기반으로 설정됩니다.

상태 머신의 전역 복원 정책이 QStateMachine::RestoreProperties로 설정된 경우, 속성 복원 과정에도 애니메이션을 추가할 수 있습니다.

상태에서 모든 속성이 설정되었는지 감지하기

애니메이션을 사용하여 속성을 할당할 경우, 상태는 더 이상 머신이 주어진 상태에 있을 때 속성이 가질 정확한 값을 정의하지 않습니다. 애니메이션이 실행되는 동안, 속성은 애니메이션에 따라 잠재적으로 어떤 값이든 가질 수 있습니다.

경우에 따라 속성에 상태에 정의된 값이 실제로 할당된 시점을 감지할 수 있다면 유용할 수 있습니다.

다음과 같은 코드가 있다고 가정해 봅시다.

    QMessageBox *messageBox = new QMessageBox(mainWindow);
    messageBox->addButton(QMessageBox::Ok);
    messageBox->setText("Button geometry has been set!");
    messageBox->setIcon(QMessageBox::Information);

    QState *s1 = new QState();

    QState *s2 = new QState();
    s2->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));
    connect(s2, &QState::entered, messageBox, SLOT(exec()));

    s1->addTransition(button, &QPushButton::clicked, s2);

button 을 클릭하면 머신은 s2 상태로 전환되며, 이 상태에서 버튼의 지오메트리가 설정된 후 지오메트리가 변경되었음을 사용자에게 알리는 메시지 상자가 표시됩니다.

애니메이션이 사용되지 않는 일반적인 경우에는 이 동작이 예상대로 이루어집니다. 하지만 s1 와 s2 사이의 전환 시점에 button 의 geometry 에 대한 애니메이션이 설정되어 있다면, s2 로 진입할 때 애니메이션이 시작되지만, 애니메이션이 완전히 끝나기 전에는 geometry 속성이 정의된 값에 실제로 도달하지 않게 됩니다. 이 경우, 버튼의 기하학적 모양이 실제로 설정되기 전에 메시지 상자가 표시됩니다.

지오메트리가 실제로 최종 값에 도달할 때까지 메시지 상자가 나타나지 않도록 하려면, 상태의 ` propertiesAssigned()` 신호를 사용할 수 있습니다. ` propertiesAssigned()` 신호는 속성에 최종 값이 할당될 때 발생하며, 이는 즉시 이루어지거나 애니메이션 재생이 끝난 후에 이루어질 수 있습니다.

    QMessageBox *messageBox = new QMessageBox(mainWindow);
    messageBox->addButton(QMessageBox::Ok);
    messageBox->setText("Button geometry has been set!");
    messageBox->setIcon(QMessageBox::Information);

    QState *s1 = new QState();

    QState *s2 = new QState();
    s2->assignProperty(button, "geometry", QRectF(0, 0, 50, 50));

    QState *s3 = new QState();
    connect(s3, &QState::entered, messageBox, SLOT(exec()));

    s1->addTransition(button, &QPushButton::clicked, s2);
    s2->addTransition(s2, &QState::propertiesAssigned, s3);

이 예제에서 button 를 클릭하면 기계는 s2 상태로 진입합니다. geometry 속성이 QRect(0, 0, 50, 50) 로 설정될 때까지 s2 상태를 유지합니다. 그런 다음 s3 상태로 전환됩니다. s3 상태에 진입하면 메시지 상자가 나타납니다. s2 상태로의 전환에 geometry 속성에 대한 애니메이션이 포함되어 있다면, 기계는 애니메이션 재생이 끝날 때까지 s2 상태를 유지합니다. 해당 애니메이션이 없는 경우, 단순히 속성을 설정하고 즉시 s3 상태로 진입합니다.

어느 쪽이든, 머신이 s3 상태에 있을 때는 geometry 속성에 정의된 값이 할당되었음을 보장합니다.

전역 복원 정책이 QStateMachine::RestoreProperties로 설정된 경우, 해당 작업이 모두 실행될 때까지 상태는 propertiesAssigned() 신호를 발산하지 않습니다.

애니메이션이 끝나기 전에 상태를 종료하면 어떻게 되는가

상태에 속성 할당이 있고, 해당 상태로 전환할 때 속성에 대한 애니메이션이 있는 경우, 속성이 상태에서 정의한 값으로 할당되기 전에 상태에서 빠져나올 가능성이 있습니다. 이는 특히 이전 섹션에서 설명한 바와 같이, propertiesAssigned() 신호에 의존하지 않는 상태에서 나가는 전환이 있을 때 해당됩니다.

상태 머신 API는 상태 머신에 의해 할당된 속성이 다음 중 하나를 충족하도록 보장합니다:

  • 해당 속성에 명시적으로 값이 할당되어 있거나,
  • 현재 속성에 명시적으로 할당된 값으로 애니메이션이 진행 중일 것.

애니메이션이 완료되기 전에 상태에서 빠져나갈 경우, 상태 머신의 동작은 전환의 대상 상태에 따라 달라집니다. 대상 상태가 속성에 값을 명시적으로 할당하는 경우, 추가 조치는 수행되지 않습니다. 속성에는 대상 상태에서 정의된 값이 할당됩니다.

대상 상태가 속성에 어떤 값도 할당하지 않는 경우, 두 가지 옵션이 있습니다: 기본적으로, 속성에는 현재 벗어나고 있는 상태에서 정의된 값(애니메이션이 끝까지 재생되었을 경우 할당되었을 값)이 할당됩니다. 그러나 전역 복원 정책이 설정된 경우, 이 정책이 우선 적용되며 속성은 평소와 같이 복원됩니다.

기본 애니메이션

앞서 설명한 바와 같이, 전환에 애니메이션을 추가하여 대상 상태에서의 속성 할당이 애니메이션으로 처리되도록 할 수 있습니다. 어떤 전환이 수행되든 상관없이 특정 속성에 특정 애니메이션을 사용하려면, 이를 상태 머신의 기본 애니메이션으로 추가할 수 있습니다. 이는 특히 상태 머신이 생성될 때 특정 상태에 의해 할당(또는 복원)되는 속성이 알려지지 않은 경우에 유용합니다.

QState *s1 = new QState();
QState *s2 = new QState();

s2->assignProperty(object, "fooBar", 2.0);
s1->addTransition(s2);

QStateMachine machine;
machine.setInitialState(s1);
machine.addDefaultAnimation(new QPropertyAnimation(object, "fooBar"));

머신이 s2 상태에 있을 때, s2 에 의해 이 속성이 할당되므로 머신은 fooBar 속성에 대한 기본 애니메이션을 재생합니다.

전환 시 명시적으로 설정된 애니메이션은 해당 속성에 대한 기본 애니메이션보다 우선 적용된다는 점에 유의하십시오.

상태 머신의 중첩

QStateMachine 는 QState 의 하위 클래스입니다. 이를 통해 한 상태 기계가 다른 기계의 자식 상태가 될 수 있습니다. QStateMachine 은 QState::onEntry()을 재구현하고 QStateMachine::start()를 호출하므로, 자식 상태 기계로 진입하면 자동으로 실행이 시작됩니다.

부모 상태 기계는 상태 기계 알고리즘 내에서 자식 상태 기계를 원자 상태로 취급합니다. 자식 상태 기계는 독립적이며, 자체 이벤트 큐와 구성을 유지합니다. 특히, 자식 상태 기계의 configuration()는 부모 상태 기계의 구성에 포함되지 않는다는 점에 유의하십시오(자식 상태 기계 자체만 포함됩니다).

자식 상태 머신의 상태는 부모 상태 머신에서 전환의 대상으로 지정할 수 없으며, 오직 자식 상태 머신 자체만이 대상으로 지정될 수 있습니다. 반대로, 부모 상태 머신의 상태는 자식 상태 머신에서 전환의 대상으로 지정할 수 없습니다. 자식 상태 머신의 finished() 신호를 사용하여 부모 머신에서 전환을 트리거할 수 있습니다.

‘Qt State Machine 개요’ 및 ‘Qt State Machine ’ QML 가이드도 참조하십시오 .

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.