En esta página

Qt State Machine Guía QML

Qt State Machine Las API de QML proporcionan tipos para crear y ejecutar gráficos de estado en QML. Es similar al marco C++ State Machine basado en los Statecharts de Harel : A visual formalism for complex systems, que también es la base de los diagramas de estado UML. Al igual que su C++ counterpart, el marco proporciona una API y un modelo de ejecución basados en State Chart XML (SCXML) para incrustar los elementos y la semántica de los statecharts en las aplicaciones QML.

Para interfaces de usuario con múltiples estados visuales, independientes del estado lógico de la aplicación, considere el uso de QML States and Transitions.

Para ver la lista completa de tipos QML proporcionados por el framework para crear máquinas de estados basadas en eventos, consulte: Qt State Machine QML Types

Uso de las Importaciones QtQuick y QtQml.StateMachine

Advertencia: Si está intentando importar tanto QtQuick como QtQml. StateMachine en un único archivo QML, asegúrese de importar QtQml. StateMachine en último lugar. De esta manera, el tipo de Estado es proporcionado por el Marco Declarativo de Máquinas de Estado y no por QtQuick:

import QtQuick
import QtQml.StateMachine

StateMachine {
    State {
        // okay, is of type QtQml.StateMachine.State
    }
}

Alternativamente, puedes importar QtQml. StateMachine en un espacio de nombres separado para evitar cualquier ambigüedad con el elemento State de QtQuick:

import QtQuick
import QtQml.StateMachine as DSM

DSM.StateMachine {
    DSM.State {
        // ...
    }
}

Una máquina de estados sencilla

Para demostrar la funcionalidad básica de la API de máquinas de estado, veamos un ejemplo: Una máquina de estados con tres estados, s1, s2 y s3. La máquina de estados está controlada por un único Botón; cuando se pulsa el botón, la máquina pasa a otro estado. Inicialmente, la máquina de estados se encuentra en el estado s1. A continuación se muestra un gráfico de estados que muestra los diferentes estados de nuestro ejemplo.

Tres estados s1, s2, s3 ciclismo en button.clicked transiciones

El siguiente fragmento muestra el código necesario para crear una máquina de estados de este tipo.

    Button {
        anchors.fill: parent
        id: button

        // change the button label to the active state id
        text: s1.active ? "s1" : s2.active ? "s2" : "s3"
    }

    StateMachine {
        id: stateMachine
        // set the initial state
        initialState: s1

        // start the state machine
        running: true

        State {
            id: s1
            // create a transition from s1 to s2 when the button is clicked
            SignalTransition {
                targetState: s2
                signal: button.clicked
            }
            // do something when the state enters/exits
            onEntered: console.log("s1 entered")
            onExited: console.log("s1 exited")
        }

        State {
            id: s2
            // create a transition from s2 to s3 when the button is clicked
            SignalTransition {
                targetState: s3
                signal: button.clicked
            }
            // do something when the state enters/exits
            onEntered: console.log("s2 entered")
            onExited: console.log("s2 exited")
        }
        State {
            id: s3
            // create a transition from s3 to s1 when the button is clicked
            SignalTransition {
                targetState: s1
                signal: button.clicked
            }
            // do something when the state enters/exits
            onEntered: console.log("s3 entered")
            onExited: console.log("s3 exited")
        }
    }

La máquina de estados se ejecuta de forma asíncrona para formar parte del bucle de eventos de tu aplicación.

Máquinas de Estado que Finalizan

La máquina de estados definida en la sección anterior nunca termina. Para que una máquina de estados pueda terminar, necesita tener un estado final de nivel superior (FinalState object). Cuando la máquina de estados entra en el estado final de nivel superior, la máquina emite la señal finished y se detiene.

Todo lo que necesitas hacer para introducir un estado final en el grafo es crear un objeto FinalState y utilizarlo como objetivo de una o más transiciones.

Transiciones compartidas

Supongamos que queremos que el usuario pueda salir de la aplicación en cualquier momento pulsando el botón Salir. Para conseguirlo, necesitamos crear un estado final y convertirlo en el objetivo de una transición asociada a la señal clicked() del botón Salir. Podríamos añadir una transición para cada estado; sin embargo, esto parece redundante y además habría que acordarse de añadir dicha transición para cada nuevo estado que se añada en el futuro.

Podemos conseguir el mismo comportamiento (es decir, que al pulsar el botón Salir se salga de la máquina de estados, independientemente del estado en el que se encuentre la máquina de estados) agrupando los estados s1, s2 y s3. Esto se hace creando un nuevo estado de nivel superior y haciendo a los tres estados originales hijos del nuevo estado. El siguiente diagrama muestra la nueva máquina de estados.

Estado s1 que contiene tres subestados s11, s12, s13 que ciclan en transiciones botón.pulsado

Los tres estados originales se han renombrado s11, s12 y s13 para reflejar que ahora son hijos del nuevo estado de nivel superior, s1. Los estados hijos heredan implícitamente las transiciones de su estado padre. Esto significa que ahora basta con añadir una única transición de s1 al estado final, s2. Los nuevos estados añadidos a s1 heredarán automáticamente esta transición.

Todo lo que se necesita para agrupar estados es especificar el padre adecuado cuando se crea el estado. También es necesario especificar cuál de los estados hijos es el inicial (el estado hijo en el que debe entrar la máquina de estados cuando el estado padre es el objetivo de una transición).

    Row {
        anchors.fill: parent
        spacing: 2
        Button {
            id: button
            // change the button label to the active state id
            text: s11.active ? "s11" : s12.active ? "s12" : "s13"
        }
        Button {
            id: quitButton
            text: "quit"
        }
    }

    StateMachine {
        id: stateMachine
        // set the initial state
        initialState: s1

        // start the state machine
        running: true

        State {
            id: s1
            // set the initial state
            initialState: s11

            // create a transition from s1 to s2 when the button is clicked
            SignalTransition {
                targetState: s2
                signal: quitButton.clicked
            }
            // do something when the state enters/exits
            onEntered: console.log("s1 entered")
            onExited: console.log("s1 exited")
            State {
                id: s11
                // create a transition from s11 to s12 when the button is clicked
                SignalTransition {
                    targetState: s12
                    signal: button.clicked
                }
                // do something when the state enters/exits
                onEntered: console.log("s11 entered")
                onExited: console.log("s11 exited")
            }

            State {
                id: s12
                // create a transition from s12 to s13 when the button is clicked
                SignalTransition {
                    targetState: s13
                    signal: button.clicked
                }
                // do something when the state enters/exits
                onEntered: console.log("s12 entered")
                onExited: console.log("s12 exited")
            }
            State {
                id: s13
                // create a transition from s13 to s11 when the button is clicked
                SignalTransition {
                    targetState: s11
                    signal: button.clicked
                }
                // do something when the state enters/exits
                onEntered: console.log("s13 entered")
                onExited: console.log("s13 exited")
            }
        }
        FinalState {
            id: s2
            onEntered: console.log("s2 entered")
            onExited: console.log("s2 exited")
        }
        onFinished: Qt.quit()
    }

En este caso queremos que la aplicación salga cuando la máquina de estados haya terminado, por lo que la señal finished() de la máquina está conectada a la ranura quit() de la aplicación.

Un estado hijo puede anular una transición heredada. Por ejemplo, el siguiente código añade una transición que hace que el botón Salir sea ignorado cuando la máquina de estados está en el estado s12.

            State {
                id: s12
                // create a transition from s12 to s13 when the button is clicked
                SignalTransition {
                    targetState: s13
                    signal: button.clicked
                }

                // ignore Quit button when we are in state 12
                SignalTransition {
                    targetState: s12
                    signal: quitButton.clicked
                }

                // do something when the state enters/exits
                onEntered: console.log("s12 entered")
                onExited: console.log("s12 exited")
            }

Una transición puede tener cualquier estado como destino independientemente de dónde se encuentre el estado destino en la jerarquía de estados.

Uso de los Estados de Historia

Imagina que quisiéramos añadir un mecanismo de "interrupción" al ejemplo discutido en la sección anterior; el usuario debería ser capaz de pulsar un botón para que la máquina de estados realice alguna tarea no relacionada, después de lo cual la máquina de estados debería reanudar lo que estaba haciendo antes (es decir, volver al estado anterior, que es uno de los tres estados en este caso).

Este tipo de comportamiento puede modelarse fácilmente mediante estados históricos. Un estado histórico (objetoHistoryState ) es un pseudoestado que representa el estado hijo en el que se encontraba el estado padre antes de salir por última vez.

Un estado histórico se crea como hijo del estado para el que deseamos registrar el estado hijo actual; cuando la máquina de estados detecta la presencia de un estado de este tipo en tiempo de ejecución, registra automáticamente el estado hijo actual (real) cuando el estado padre sale. Una transición al estado histórico es, de hecho, una transición al estado hijo que la máquina de estados había guardado previamente; la máquina de estados "reenvía" automáticamente la transición al estado hijo real.

El siguiente diagrama muestra la máquina de estados después de añadir el mecanismo de interrupción.

Estado s1 con subestados y estado de historia, botón de interrupción transiciones a s3, que vuelve a historia s1 cuando termina

El siguiente código muestra cómo se puede implementar; en este ejemplo simplemente mostramos un cuadro de mensaje cuando se entra en s3, y luego volvemos inmediatamente al estado hijo anterior de s1 a través del estado historia.

    Row {
        anchors.fill: parent
        spacing: 2
        Button {
            id: button
            // change the button label to the active state id
            text: s11.active ? "s11" : s12.active ? "s12" :  s13.active ? "s13" : "s3"
        }
        Button {
            id: interruptButton
            text: s1.active ? "Interrupt" : "Resume"
        }
        Button {
            id: quitButton
            text: "quit"
        }
    }

    StateMachine {
        id: stateMachine
        // set the initial state
        initialState: s1

        // start the state machine
        running: true

        State {
            id: s1
            // set the initial state
            initialState: s11

            // create a transition from s1 to s2 when the button is clicked
            SignalTransition {
                targetState: s2
                signal: quitButton.clicked
            }
            // do something when the state enters/exits
            onEntered: console.log("s1 entered")
            onExited: console.log("s1 exited")
            State {
                id: s11
                // create a transition from s1 to s2 when the button is clicked
                SignalTransition {
                    targetState: s12
                    signal: button.clicked
                }
                // do something when the state enters/exits
                onEntered: console.log("s11 entered")
                onExited: console.log("s11 exited")
            }

            State {
                id: s12
                // create a transition from s2 to s3 when the button is clicked
                SignalTransition {
                    targetState: s13
                    signal: button.clicked
                }
                // do something when the state enters/exits
                onEntered: console.log("s12 entered")
                onExited: console.log("s12 exited")
            }
            State {
                id: s13
                // create a transition from s3 to s1 when the button is clicked
                SignalTransition {
                    targetState: s1
                    signal: button.clicked
                }
                // do something when the state enters/exits
                onEntered: console.log("s13 entered")
                onExited: console.log("s13 exited")
            }

            // create a transition from s1 to s3 when the button is clicked
            SignalTransition {
                targetState: s3
                signal: interruptButton.clicked
            }
            HistoryState {
                id: s1h
            }
        }
        FinalState {
            id: s2
            onEntered: console.log("s2 entered")
            onExited: console.log("s2 exited")
        }
        State {
            id: s3
            SignalTransition {
                targetState: s1h
                signal: interruptButton.clicked
            }
            // do something when the state enters/exits
            onEntered: console.log("s3 entered")
            onExited: console.log("s3 exited")
        }
        onFinished: Qt.quit()
    }

Uso de estados paralelos

Supongamos que queremos modelar un conjunto de propiedades mutuamente excluyentes de un coche en una única máquina de estados. Digamos que las propiedades que nos interesan son Limpio vs Sucio, y Moverse vs No moverse. Se necesitarían cuatro estados mutuamente excluyentes y ocho transiciones para representar los estados y moverse libremente entre todas las combinaciones posibles, como se muestra en el siguiente gráfico de estados.

Cuatro estados: limpio/no se mueve, limpio/se mueve, sucio/no se mueve, sucio/se mueve con transiciones iniciadas, paradas, limpias, sucias

Si añadimos una tercera propiedad (por ejemplo, Rojo frente a Azul), el número total de estados se duplicaría, hasta ocho; y si añadimos una cuarta propiedad (por ejemplo, Cerrado frente a Convertible), el número total de estados se duplicaría de nuevo, hasta 16.

Este aumento exponencial puede reducirse utilizando estados paralelos, lo que permite un crecimiento lineal del número de estados y transiciones a medida que añadimos más propiedades. Además, los estados pueden añadirse o eliminarse del estado paralelo sin afectar a ninguno de sus estados hermanos. El siguiente gráfico de estados muestra los distintos estados paralelos del ejemplo del coche.

Estado paralelo s1 con las regiones s11 (limpia/sucia) y s12 (se mueve/no se mueve)

Para crear un grupo de estados paralelos, establezca childMode en QState.ParallelStates.

State {
    id: s1
    childMode: QState.ParallelStates
    State {
        id: s11
    }
    State {
        id: s12
    }
}

Cuando se introduce un grupo de estados paralelos, se introducen simultáneamente todos sus estados secundarios. Las transiciones dentro de los estados hijos individuales funcionan normalmente. Sin embargo, cualquiera de los estados hijos puede tomar una transición que salga del estado padre. Cuando esto ocurre, se sale del estado padre y de todos sus estados hijos.

El paralelismo en el marco de la Máquina de Estado sigue una semántica intercalada. Todas las operaciones paralelas se ejecutarán en un único paso atómico del procesamiento de eventos, por lo que ningún evento puede interrumpir las operaciones paralelas. Sin embargo, los eventos seguirán siendo procesados secuencialmente, ya que la máquina en sí es de un solo hilo. Por ejemplo, considere la situación en la que hay dos transiciones que salen del mismo grupo de estados paralelos, y sus condiciones se hacen verdaderas simultáneamente. En este caso, el último evento procesado de los dos no tendrá ningún efecto.

Salida de un estado compuesto

Un estado hijo puede ser final (un objeto FinalState ); cuando se entra en un estado hijo final, el estado padre emite la señal State::finished. El siguiente diagrama muestra un estado compuesto s1 que realiza algunos procesos antes de entrar en un estado final:

Estado s1 con subestado de procesamiento que pasa a estado final, desencadenando el paso a s2

Cuando se entra en el estado final de s1, s1 emite automáticamente finished. Utilizamos una transición de señal para que este evento desencadene un cambio de estado:

State {
    id: s1
    SignalTransition {
        targetState: s2
        signal: s1.finished
    }
}

El uso de estados finales en estados compuestos es útil cuando se desea ocultar los detalles internos de un estado compuesto. El mundo exterior debe ser capaz de entrar en el estado y obtener una notificación cuando el estado ha completado su trabajo, sin necesidad de conocer los detalles internos. Este es un mecanismo de abstracción y encapsulación muy poderoso cuando se construyen máquinas de estado complejas (profundamente anidadas). (En el ejemplo anterior, se podría, por supuesto, crear una transición directamente desde el estado done de s1 en lugar de depender de la señal finished() de s1, pero con la consecuencia de que los detalles de implementación de s1 quedan expuestos y dependen de ellos).

Para grupos de estados paralelos, la señal State::finished se emite cuando todos los estados hijos han entrado en estados finales.

Transiciones sin objetivo

No es necesario que una transición tenga un estado objetivo. Una transición sin objetivo puede activarse del mismo modo que cualquier otra transición; la diferencia es que no provoca ningún cambio de estado. Esto te permite reaccionar a una señal o evento cuando tu máquina está en un determinado estado, sin tener que salir de ese estado. Por ejemplo:

Button {
    id: button
    text: "button"
    StateMachine {
        id: stateMachine
        initialState: s1
        running: true
        State {
            id: s1
            SignalTransition {
                signal: button.clicked
                onTriggered: console.log("button pressed")
            }
        }
    }
}

El mensaje "botón pulsado" se mostrará cada vez que se pulse el botón, pero la máquina de estados permanecerá en su estado actual (s1). Si el estado de destino se estableciera explícitamente en s1, se saldría de s1 y se volvería a entrar en él cada vez (se emitirían las señales QAbstractState::entered y QAbstractState::exited ).

© 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.