关于Qt状态机的记录

QState类为QStateMachine类提供状态。一个QState对象可以有子状态,也可以有转向其他状态的转换。状态的ChildMode属性用来设置子状态之间的关系。属性值有ExclusiveStates和ParallelStates,若为ExclusiveStates,表示子状态之间是互斥的,必须调用setInitialState()函数设置初始状态,当转换的目标是父状态时,状态机还需知道要转换到哪个子状态中。若值为ParallelStates,各个子状态之间是平行的关系,当父状态进入到一个状态,所有的子状态都进入到这一状态,当进入到一个最终子状态时,状态发出finished()信号[9]。   一个状态机的状态图如图1所示,s1,s2和s3分别表示状态机的3个状态,黑点指示的状态s1为初始状态,当接收到button.clicked信号,即用户点击按钮时,状态发生转换,并在这3个状态中循环切换。
  2.2 QStateMachine类
  QStateMachine类建立在状态图概念的基础上,提供了一个分层的有限状态机。状态机体系的整体继承关系主要分为3部分,分别是:
  (1)负责存储状态的QAbstractState接口;
  (2)负责对信号进行处理的QAbstractTransition接口;
  (3)为状态机类提供信号事件的QEvent接口。
  状态机管理一组继承自QAbstractState类的状态和继承自QAbstractTransition类的转换,这些状态和转换确定一个状态图。状态图建立后,状态机即可执行它。
  为状态机添加状态使用addState()函数,移除状态使用removeState()函数。重载onEntry()和onExit()函数可以使状态机在进入或退出某状态时进行指定的操作。
  2.3 转换(Transition)
  由一个状态到另一个状态变更的这一动作称为转换,每个状态都包含一个转换的集合。转换定义了如何对事件(Events)进行响应,Qt的信号和事件都可以触发转换。
  转换本身也可以包含动作,在转换的过程中,状态机能够执行一些动作(Action)。例如,当进入某个状态时,状态机执行onentry 动作;当退出某个状态时,执行onexit动作。假设当状态机通过一个名为T的转换从状态S1转换到S2时,它先执行S1的 onexit动作,然后执行T本身所包含的动作,最后执行S2 的onentry动作。
  addTransition()函数用来添加转换,添加的转换有3种情况。可以转换到另一个转换;可以是一个对象等待到一个信号后转换到目标状态;也可以无条件转到目标状态。removeTransition()函数用来移除一个转换。assignProperty()函数用来设置状态的属性。addDefaultAnimation()函数可以添加转换时的动态效果。postEvent() 函数可以为状态机设置优先级。
  在任何时刻,状态机总处于一个状态中,这个状态被称为主动态。当某一事件发生时,状态机将检查主动态包含的所有转换,如果发现有一个和该事件匹配,状态机将从当前的主动态转移到该转换指定的目标态,即完成状态的转换;若找不到和该事件匹配的转换,则状态不发生变化。
  3 状态转换机的多种状态
  3.1 历史状态(History States)
  历史状态是一个虚拟状态,同时也是一个子状态,它的父状态是最后一次退出时的那个父状态。历史状态用来记录当前状态,当状态机发现运行中有历史状态出现时,则当退出父状态时它会自动地记录当前子状态。一个转向历史状态的转换实际上是一个转向状态机之前存储的子状态的转换,状态机自动地转向该子状态。在中断机制中应用历史状态可以使状态转换过程变得非常简单。图2所示的状态机通过添加历史状态,使状态机在中断事件完成后仍然返回到发生中断之前所处的那个子状态。
  关键代码如下:
  QHistoryState *s1h = new QHistoryState(s1);
  QState *s3 = new QState();
  s1?>addTransition(interruptButton, SIGNAL(clicked()), s3);
  s3?>addTransition(s1h);
  首先添加一个以s1状态为父状态的QHistoryState类的子状态s1h和一个顶层状态s3,再添加一个中断按钮,然后添加由s1向s3的转换,点击中断按钮则发生转换,最后添加一个由s3向s1h的转换。在 s1状态的任何子状态均可发生中断,发生中断后则将当前子状态记录在s1h历史状态中,并进入到s3状态,然后从s3状态返回到s1h状态,即中断前所处的那个子状态。
  3.2 复合状态(Compound States)
  一个状态可以嵌套其他的状态,这样的状态称为复合状态。嵌套其他状态的状态称为父状态(parent state),而被嵌套的则称为子状态(child state)。子状态又可以嵌套其他的状态,直到任意深度。不包含任何子状态的状态,称为原子状态(atomic state)。
  当一个子状态处于主动态(acitve)时,它的父状态也必须处于主动态。这样一来,在任何一点我们都将拥有一个包含原子状态和它所有祖先的主动态的集合。
  由于复合状态的存在,转换(transition)不再是从一个原子状态转到另一个原子状态,而是从一个主动态集合转到另一个主动态集合的转换。如果状态转换的目标是一个原子状态,那么状态机将不仅进入到该原子状态,而且还将进入到它所有的处于活动状态的祖先状态中。如果转换的目标是一个复合状态,那么复合状态的子状态必须也被激活,由于转换并没有指定哪一个子状态,这时需要激活该复合状态的初始子状态。如果该状态依然是复合状态,则递归下去,直到原子状态为止。
  复合状态会影响到转换的选择。当事件发生时,状态机从最深层嵌套的原子状态开始查找,如果未找到匹配的转换,则查找其父状态的转换,依次递归。如果状态机中所有转换均不匹配,事件被丢弃。
  应用复合状态建立一个状态组,则可以通过为父状态应用转换使整个组的所有子状态都共享该转换,简化了为每个子状态都应用转换这一过程。
  最终状态(Final State)和历史状态(History State)可以作为一个复合状态的子状态。   3.3 平行状态(Parallel States)
  在状态机体系中,平行状态采用交叉存取方法,所有的平行操作以单一的、原子的事件处理方式执行,因此没有事件可以中断平行状态的操作。不过,事件仍然可以被顺序执行,因为状态机本身是单线程的。假设有两个转换,都是退出同一个平行状态组,使它们发生的条件同时为真。这时,最后被处理的事件不会生效,因为第一个事件已经使状态机从平行状态组退出了。
  如果在状态机中所有状态都是互斥的,那么这些状态将拥有多种组合和转换。特别的,使用平行状态时,当增加一个状态后,组合状态和转换的数量将会以线性方式增加,避免了指数级的剧增方式。而且增加或删除状态也不会影响到其他状态。
  建立平行状态的代码如下:
  QState *s1 = new QState (QState::ParallelStates);
  QState *s11 = new QState(s1);
  QState *s12 = new QState(s1);
  ParallelStates属性表明建立的s1是一个平行状态组,s1的子状态s11和s12是平行关系。 当进入到一个平行状态组时,也就同时进入了它所有的子状态。单个子状态转换正常,且可以应用一个退出父状态的转换。若退出父状态的转换发生,则同时退出父状态和它所有的子状态。
  平行状态与复合状态差别很大,当复合状态处于主动态时,有且只有一个子状态处于主动态;而当平行状态处于主动态时,所有的子状态都必须处于主动态。
  3.4 错误状态(Error States)
  如果遇到错误,状态机不会因为进入错误状态而停止运行,而会寻找错误状态,一旦找到,即进入这个状态,并可通过error()函数得到错误类型,Error枚举类型给出可能的错误类型。如果处在正确的状态申请错误状态类型,状态机则停止执行,并将错误信息输出至控制台[10]。
  4 状态转换机的启动与终止
  使用start()函数启动状态机。状态机启动前,必须设置初始状态,即启动状态机时状态机进入的那个状态。状态机进入到初始状态后,即发射started()信号。
  状态机是事件驱动的,它保持着自己的事件循环,事件通过postEvent()函数传给状态机,并异步执行,如果没有一个运行中的事件循环它将不再向前推进。不必像转换函数一样直接把事件传给状态机,比如QEventTransition类和它的子类会处理。但是对由事件触发的自定义的转换来说,postEvent()是非常有用的。
  状态机不断地处理事件和转换,直到进入到顶层的最终状态,此时状态机发射finished()信号。也可以使用stop()函数终止状态机,此时状态机发射stopped()信号。

你可能感兴趣的:(QT)