Spec-Zone.ru › Qt 5.15

Система конечных автоматов

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

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

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

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

Классы в системе конечных автоматов

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

QAbstractState

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

QAbstractTransition

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

QEventTransition

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

QFinalState

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

QHistoryState

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

QKeyEventTransition

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

QMouseEventTransition

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

QSignalTransition

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

QState

Универсальное состояние для QStateMachine

QStateMachine

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

QStateMachine::SignalEvent

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

QStateMachine::WrappedEvent

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

Простой конечный автомат

Чтобы продемонстрировать основные функции 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, &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 для каждого состояния:

    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, &QState::entered, button, &QPushButton:showMaximized);
    QObject::connect(s3, &QState::exited, button, &QPushButton::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, &QPushButton::clicked, s2);
    machine.addState(s2);
    machine.setInitialState(s1);

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

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

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

    s12->addTransition(quitButton, &QPushButton::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, &QState::entered, mbox, &QMessageBox::exec);
    s3->addTransition(s1h);
    machine.addState(s3);

    s1->addTransition(interruptButton, &QPushButton::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, &QState::finished, s2);

Использование конечных состояний в составных состояниях полезно, когда вы хотите скрыть внутренние детали составного состояния; т. е. единственное, что внешний мир должен иметь возможность сделать, это войти в состояние и получить уведомление о завершении работы состояния. Это очень мощный механизм абстракции и инкапсуляции при построении сложных (глубоко вложенных) машин состояний. (В приведённом выше примере вы, конечно, могли создать переход непосредственно из состояния s1 's done вместо того, чтобы полагаться на сигнал finished() s1, но с тем последствием, что реализации деталей 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 и переопределяете 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"));

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

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

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

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

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

    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, которое установит геометрию кнопки и затем отобразит сообщение пользователю, что геометрия была изменена.

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

    s1->addTransition(button, &QPushButton::clicked, s2);
    s2->addTransition(s2, &QState::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/qt-5.15/statemachine-api.html

Spec-Zone.ru

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