状态机框架
Qt中的状态机框架为我们提供了很多的API和类,使我们能更容易的在自己的应用程序中集成状态动画。这个框架是和Qt的元对象系统机密结合在一起的。比如,各个状态之间的转换是通过信号触发的,状态可被配置为用来设置QObject对象的属性以及调用其方法。可以说Qt中的状态机就是通过Qt自身的事件系统来驱动的。同时,状态机中的状态图是分层次的。一些状态可以被嵌套到另一些状态里,当前的状态机配置是由当前活动的所有状态组成的。在一个状态机的有效配置中的所有状态具有共同的祖先。
一个简单的状态机
为了阐述Qt状态机API的核心功能,我们先从一个小的例子说起:这个状态机只有三个状态,s1,s2,s3。我们通过一个按钮的点击来控制这个状态机中状态的转换;当按钮被点击时,就会发生一次状态转换,从一个状态到另一个状态。初始情况下,状态机处于s1状态。这个状态机所对应的状态图如下:
下面,我们先来看下怎么通过Qt代码来实现这个简单的状态机。
第一步,我们创建一个状态机和需要的状态:
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();
这样,我们的状态机就开始异步的运行了,也就是说,它成为了我们应用程序事件循环的一部分了。这也对应了我们上面说的,Qt的状态机是通过Qt自身的事件机制来驱动的。
在状态转换时操作QObject对象
上面所创建的状态机,作为入门,我们仅仅进行了状态机中各个状态之间的见转换,而未进行其他的工作。其实,我们可以使用QState::assignProperty() 函数当进入某个状态时让其去修改某个QObject对象的属性。例如下面的代码,当进入各个状态时,改变QLabel的text属性,即改变QLabel上显示的文本内容:
s1->assignProperty(label, "text", "In state s1");
s2->assignProperty(label, "text", "In state s2");
s3->assignProperty(label, "text", "In state s3");
当进入任一状态时,label的文本都会发生改变。
除了操作QObject对象的属性外,我们还能通过状态的转换来调用QObject对象的函数。这是通过使用状态转换时发出的信号完成的。其中,当进入某个状态时会发出QState::enterd() 信号,当退出某个状态时会发出QState::exited() 信号。例如下面的代码,其实现的功能即为当进入s3状态时,会调用按钮的showMaximized() 函数,当退出s3状态时会调用showMinimized() 函数:
QObject::connect(s3, SIGNAL(entered()), button, SLOT(showMaximized()));
QObject::connect(s3, SIGNAL(exited()), button, SLOT(showMinimized()));
状态机的结束
我们在上面创建的状态机是永远不会结束的。为了使一个状态机在某种条件下结束,我们需要创建一个顶层的final 状态(QFinalState object) 。当状态机进入一个顶层的final 状态时,会发出finished() 信号,然后结束。所以,我们只需要为上面的状态图引入一个final 状态,并把它设置为某个过渡的目标状态即可。这样,当状态机在某种条件下转换到该状态时,整个状态机结束。
通过状态分组来共享过渡
假设我们想让用户随时通过点击退出按钮来退出整个应用程序。为了实现这个需求,我们需要创建一个final状态并使他成为和按钮的clicked()信号相关联的那个过渡的目标状态。一种办法是我们为状态s1,s2,s3分别添加一个到final状态的过渡,但这看上去有点多余,并且不利于将来的扩张。第二种方法就是将状态s1,s2,s3分成一组。我们通过创建一个新的顶层状态并使s1,s2,s3成为其孩子来完成。下面是这种方法所对应的状态转换图: