Spec-Zone.ru › Qt 5.13

Фреймворк конечных автоматов

Фреймворк конечных автоматов предоставляет классы для создания и выполнения графов состояний. Концепции и обозначения основаны на тех, что из Statecharts: A visual formalism for complex systems Харела, который также является основой диаграмм состояний UML. Семантика выполнения автомата основана на State Chart XML (SCXML).

Диаграммы состояний предоставляют графический способ моделирования реакции системы на стимулы. Это делается путем определения возможных состояний, в которых может находиться система, и как система может переходить из одного состояния в другое (переходы между состояниями). Ключевой характеристикой систем, управляемых событиями (таких как приложения Qt), является то, что поведение часто зависит не только от последнего или текущего события, но и от предшествующих событий. С помощью диаграмм состояний эту информацию легко выразить.

Фреймворк конечных автоматов предоставляет API и модель выполнения, которые могут быть использованы для эффективной интеграции элементов и семантики диаграмм состояний в приложения Qt. Фреймворк тесно интегрирован с метаобъектной системой Qt; например, переходы между состояниями могут быть инициированы сигналами, а состояния могут быть настроены на установку свойств и вызов методов объектов {QObject}. Для управления автоматами состояний используется система событий Qt.

Граф состояний в фреймворке конечных автоматов является иерархическим. Состояния могут быть вложены внутри других состояний, а текущая конфигурация автомата состояний состоит из набора активных состояний. Все состояния в корректной конфигурации автомата состояний будут иметь общего предка.

Классы в фреймворке конечных автоматов

Эти классы предоставляются qt для создания автоматов состояний, управляемых событиями.

QAbstractState

Базовый класс состояний QStateMachine

QAbstractTransition

Базовый класс переходов между объектами QAbstractState

QEventTransition

Переход, специфичный для объектов QObject, для событий Qt

QFinalState

Конечное состояние

QHistoryState

Способ возврата к ранее активному подсостоянию

QSignalTransition

Переход, основанный на сигнале Qt

QState

Состояние общего назначения для QStateMachine

QStateMachine

Иерархический конечный автомат

QStateMachine::SignalEvent

Представляет событие сигнала Qt

QStateMachine::WrappedEvent

Наследует QEvent и содержит клон события, связанного с QObject

QKeyEventTransition

Переход для событий клавиш

QMouseEventTransition

Переход для событий мыши

Простой автомат состояний

Чтобы продемонстрировать основную функциональность API автоматов состояний, давайте рассмотрим небольшой пример: автомат состояний с тремя состояниями, s1, s2 и s3. Автомат состояний управляется одним QPushButton; при нажатии кнопки автомат переходит в другое состояние. Изначально автомат находится в состоянии s1. Диаграмма состояний для этого автомата представлена ниже:

Следующий фрагмент кода демонстрирует код, необходимый для создания такого автомата состояний. Сначала мы создаём автомат и состояния:

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

Затем мы создаём переходы, используя функцию QState::addTransition():

    s1->addTransition(button, SIGNAL(clicked()), s2);
    s2->addTransition(button, SIGNAL(clicked()), s3);
    s3->addTransition(button, SIGNAL(clicked()), s1);

Далее, мы добавляем состояния в автомат и устанавливаем начальное состояние автомата:

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

Наконец, мы запускаем автомат состояний:

    machine.start();

Автомат состояний выполняется асинхронно, то есть становится частью цикла событий вашего приложения.

Выполнение полезной работы при входе и выходе из состояния

Вышеприведённый автомат состояний просто переходит из одного состояния в другое, не выполняя никаких операций. Функция QState::assignProperty() может быть использована для того, чтобы состояние устанавливало свойство объекта QObject при входе в состояние. В следующем фрагменте кода указано значение, которое должно быть присвоено свойству текста QLabel для каждого состояния:

    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() генерируется при выходе из состояния. В следующем фрагменте кода слот showMaximized() кнопки будет вызван при входе в состояние s3, а слот showMinimized() кнопки будет вызван при выходе из состояния s3:

    QObject::connect(s3, SIGNAL(entered()), button, SLOT(showMaximized()));
    QObject::connect(s3, SIGNAL(exited()), button, SLOT(showMinimized()));

Пользовательские состояния могут переопределять QAbstractState::onEntry() и QAbstractState::onExit().

Автоматы состояний, которые завершаются

Автомат состояний, определённый в предыдущем разделе, никогда не завершается. Для того, чтобы автомат состояний мог завершиться, он должен иметь верхнеуровневое конечное состояние (объект QFinalState). Когда автомат состояний переходит в верхнеуровневое конечное состояние, автомат генерирует сигнал QStateMachine::finished() и останавливается.

Всё, что вам нужно сделать, чтобы добавить конечное состояние в граф, — это создать объект QFinalState и использовать его в качестве назначения одного или нескольких переходов.

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

Предположим, что мы хотели бы позволить пользователю в любое время выйти из приложения, нажав кнопку «Выход». Для этого нам нужно создать конечное состояние и сделать его целевым состоянием для перехода, связанного с сигналом clicked() кнопки «Выход». Мы могли бы добавить переход из каждого из s1, s2 и s3; однако, это кажется избыточным, и также необходимо помнить о добавлении такого перехода для каждого нового состояния, которое будет добавлено в будущем.

Мы можем достичь того же поведения (т. е. нажатие кнопки «Выход» завершает автомат состояний независимо от того, в каком состоянии находится автомат состояний) путём группировки состояний s1, s2 и s3. Это делается путём создания нового верхнеуровневого состояния и размещения трёх исходных состояний в качестве дочерних состояний нового состояния. Следующая диаграмма показывает новый автомат состояний.

Три исходных состояния были переименованы в s11, s12 и s13, чтобы отразить, что они теперь являются дочерними состояниями нового верхнеуровневого состояния, s1. Дочерние состояния неявно наследуют переходы родительского состояния. Это означает, что теперь достаточно добавить единственный переход от 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, SIGNAL(clicked()), s2);
    machine.addState(s2);
    machine.setInitialState(s1);

    QObject::connect(&machine, SIGNAL(finished()), QApplication::instance(), SLOT(quit()));

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

Дочернее состояние может переопределять унаследованный переход. Например, следующий код добавляет переход, который фактически заставляет пользователя игнорировать кнопку «Выход», когда автомат находится в состоянии s12.

    s12->addTransition(quitButton, SIGNAL(clicked()), s12);

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

Использование состояний истории для сохранения и восстановления текущего состояния

Представьте, что мы хотели добавить механизм «прерывания» к примеру, обсуждаемому в предыдущем разделе; пользователь должен иметь возможность нажать кнопку, чтобы автомат состояний выполнил некоторую не связанную задачу, после чего автомат должен продолжить выполнение того, что он делал до этого (т. е. вернуться в старое состояние, которое в данном случае является одним из s11, s12 и s13).

Такое поведение легко моделируется с помощью состояний истории. Состояние истории (объект QHistoryState) — это псевдосостояние, которое представляет собой дочернее состояние, в котором находилось родительское состояние в последний раз, когда оно вышло из родительского состояния.

Состояние истории создаётся как дочернее состояние состояния, для которого мы хотим записать текущее дочернее состояние; когда автомат состояний обнаруживает такое состояние во время выполнения, он автоматически записывает текущее (реальное) дочернее состояние при выходе из родительского состояния. Переход в состояние истории фактически является переходом в дочернее состояние, которое автомат состояний ранее сохранил; автомат состояний автоматически «перенаправляет» переход на реальное дочернее состояние.

Следующая диаграмма показывает автомат состояний после добавления механизма прерывания.

Следующий код показывает, как это можно реализовать; в этом примере мы просто отображаем диалоговое окно с сообщением при входе в 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, SIGNAL(entered()), mbox, SLOT(exec()));
    s3->addTransition(s1h);
    machine.addState(s3);

    s1->addTransition(interruptButton, SIGNAL(clicked()), s3);

Использование параллельных состояний для предотвращения комбинаторного взрыва состояний

Предположим, вы хотите смоделировать набор взаимоисключающих свойств автомобиля в одной машине состояний. Скажем, свойства, которые нас интересуют, — это «Чистый» против «Загрязненный» и «Движущийся» против «Неподвижный». Для представления и свободного перехода между всеми возможными комбинациями потребовалось бы четыре взаимоисключающих состояния и восемь переходов.

Если мы добавим третье свойство (скажем, «Красный» против «Синий»), общее количество состояний удвоится до восьми; а если мы добавим четвёртое свойство (скажем, «Закрытый» против «Кабриолет»), общее количество состояний снова удвоится до 16.

Используя параллельные состояния, общее число состояний и переходов растёт линейно по мере добавления свойств, а не экспоненциально. Кроме того, состояния можно добавлять или удалять из параллельного состояния, не влияя на какие-либо его состояния-«собратья».

Для создания группы параллельных состояний передайте QState::ParallelStates в конструктор QState.

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

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

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

Обнаружение завершения составного состояния

Дочернее состояние может быть конечным (объект QFinalState); когда вводится конечное дочернее состояние, родительское состояние испускает сигнал QState::finished(). Следующая диаграмма показывает составное состояние s1, которое выполняет некоторую обработку перед переходом в конечное состояние:

Когда s1 входит в своё конечное состояние, s1 автоматически испустит finished(). Мы используем переход по сигналу, чтобы вызвать это событие, чтобы инициировать изменение состояния:

  s1->addTransition(s1, SIGNAL(finished()), s2);

Использование конечных состояний в составных состояниях полезно, когда вы хотите скрыть внутренние детали составного состояния; то есть единственное, что внешний мир должен иметь возможность сделать, это войти в состояние и получить уведомление о том, когда состояние завершило свою работу. Это очень мощный механизм абстракции и инкапсуляции при построении сложных (глубоко вложенных) машин состояний. (В приведённом выше примере, вы, конечно, можете создать переход непосредственно из состояния s1 в состояние done вместо того, чтобы полагаться на сигнал finished() s1, но с последствием, что детали реализации s1 будут раскрыты и зависеть от них).

Для групп параллельных состояний сигнал QState::finished() испускается, когда все дочерние состояния перешли в конечные состояния.

Переходы без цели

Переход необязательно должен иметь целевое состояние. Переход без цели можно инициировать так же, как и любой другой переход; разница в том, что при запуске перехода без цели он не вызывает никаких изменений состояния. Это позволяет реагировать на сигнал или событие, когда ваша машина находится в определённом состоянии, не выходя из этого состояния. Пример:

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

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

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

machine.setInitialState(s1);

Диалоговое окно сообщения будет отображаться каждый раз при нажатии кнопки, но машина состояний останется в текущем состоянии (s1). Однако, если целевое состояние явно установлено на s1, каждый раз будет происходить выход из s1 и повторный вход (например, будут испускаться сигналы QAbstractState::entered() и QAbstractState::exited()).

События, переходы и условия

QStateMachine (QStateMachine) выполняет свою собственную циклическую обработку событий. Для переходов по сигналам (QSignalTransition) QStateMachine автоматически помещает QStateMachine::SignalEvent в себя, когда получает соответствующий сигнал; аналогично, для переходов по событиям объекта QObject (QEventTransition) помещается QStateMachine::WrappedEvent.

Вы можете поместить свои собственные события в машину состояний с помощью QStateMachine::postEvent().

При размещении пользовательского события в машине состояний, обычно, у вас есть один или несколько пользовательских переходов, которые могут быть инициированы событиями этого типа. Для создания такого перехода вы наследуете QAbstractTransition и переопределяете 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 и выполняем сравнение строк.

Далее приведён диаграмму состояний, использующая пользовательское событие и переход:

Вот как выглядит реализация диаграммы состояний:

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

Событие, которое не обрабатывается каким-либо соответствующим переходом, будет бесшумно поглощено машиной состояний. Может быть полезно сгруппировать состояния и предоставить обработку таких событий по умолчанию; например, как показано на следующей диаграмме состояний:

Для глубоко вложенных диаграмм состояний вы можете добавить такие «переходы-fallback» на уровне подходящей детализации.

Использование политики восстановления для автоматического восстановления свойств

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

Анимация присваивания свойств

API машины состояний подключается к API анимации Qt, чтобы позволить автоматически анимировать свойства, когда они назначаются в состояниях.

Предположим, у нас есть следующий код:

    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, SIGNAL(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, SIGNAL(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, SIGNAL(entered()), messageBox, SLOT(exec()));

    s1->addTransition(button, SIGNAL(clicked()), s2);

При нажатии на button машина перейдёт в состояние s2, которое задаст геометрию кнопки, а затем отобразит сообщение пользователю, что геометрия изменена.

В обычном случае, когда анимации не используются, всё будет работать как ожидается. Однако, если для geometry свойства button установлена анимация при переходе между s1 и s2, анимация будет запущена при входе в 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, SIGNAL(entered()), messageBox, SLOT(exec()));

    s1->addTransition(button, SIGNAL(clicked()), s2);
    s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3);

В этом примере, при нажатии на button, машина перейдёт в состояние s2. Она останется в состоянии s2, пока свойство geometry не будет установлено в QRect(0, 0, 50, 50). Затем она перейдёт в состояние s3. При входе в состояние s2, сообщение появится. Если переход в состояние 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, машина выполнит анимацию по умолчанию для свойства fooBar, так как это свойство назначено состоянием s2.

Обратите внимание, что явно установленные анимации на переходах будут иметь приоритет над любой анимацией по умолчанию для данного свойства.

Вложенные машины состояний

QStateMachine является подклассом QState. Это позволяет машине состояний быть дочерним состоянием другой машины. QStateMachine переопределяет QState::onEntry() и вызывает QStateMachine::start(), поэтому при входе в дочернюю машину состояний она автоматически запустится.

Родительская машина состояний рассматривает дочернюю машину как атомарное состояние в алгоритме машины состояний. Дочерняя машина состояний является автономной; она поддерживает свою собственную очередь событий и конфигурацию. Обратите внимание, что конфигурация дочерней машины configuration() не является частью конфигурации родительской машины (только сама дочерняя машина).

Состояния дочерней машины состояний не могут быть указаны в качестве целей переходов в родительской машине состояний; это может сделать только сама дочерняя машина состояний. Аналогично, состояния родительской машины не могут быть указаны в качестве целей переходов в дочерней машине состояний. Сигнал finished() дочерней машины состояний может использоваться для запуска перехода в родительской машине.

См. также Фреймворк декларативных машин состояний.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.13/statemachine-api.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API