Spec-Zone.ru › Qt 5.6

Фреймворк Машин Состояний

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

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

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

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

Классы в фреймворке Машин Состояний

Эти классы предоставляются 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 выполняет свою собственную очередь событий. Для переходов сигналов (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:
    virtual bool eventTest(QEvent *e)
    {
        if (e->type() != QEvent::Type(QEvent::User+1)) // StringEvent
            return false;
        StringEvent *se = static_cast<StringEvent*>(e);
        return (m_value == se->value);
    }

    virtual void onTransition(QEvent *) {}

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

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

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

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

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

Spec-Zone.ru

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