qt状态机 - CSDN
精华内容
参与话题
  • Qt状态机框架

    千次阅读 2017-03-17 08:42:49
     Qt中的状态机框架为我们提供了很多的API和类,使我们能更容易的在自己的应用程序中集成状态动画。这个框架是和Qt的元对象系统机密结合在一起的。比如,各个状态之间的转换是通过信号触发的,状态可被配置为用来...

           状态机框架       

           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成为其孩子来完成。下面是这种方法所对应的状态转换图:


           上面的三个状态被重命名为s11,s12,s13以此来表明它们是s1的孩子。子状态会隐式的继承父状态的过渡。这意味着我们目前可以只添加一个s1到final状态s2的过渡即可,s11,s12,s13会继承这个过渡,从而无论在什么状态均可退出应用程序。并且,将来新添加到s1的新的子状态也会自动继承这个过渡。

            而所谓的分组,就是只需在创建状态时为其指定一个合适的父状态即可。当然,还需要为这组状态指定一个初始状态,即当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状态是,退出按钮的点击被忽略。还有,一个过渡可以选择任何状态作为其目标状态,也就是说,一个过渡的目标状态不需要和他的源状态在状态图上处于同一个层次。

            使用历史 历史状态保存和恢复当前状态

            如果我们想给上面的例子添加一个中断机制,即用户能通过点击一个按钮让状态机停下来去做一些其他的工作,之后再返回到它之前停下的地方。这种行为我们就可以通过 历史状态 实现。历史状态  是一个假想的状态,它表示了父状态上次退出时的子状态。

            历史状态通常创建为想要保存的那个状态的子状态。这样,程序运行时,当状态机检测到这种状态的存在时,就会在父状态退出时自动记录当前的子状态。连接到历史状态的过渡实际上就是连接到状态机上次保存的子状态,状态机会自动的将过渡前移到正在的子状态。下面的状态图显示了添加打断机制后的执行流程:



    下面的代码展示了具体怎么实现这种功能。在这个例子里,当进入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);
           使用并行状态来避免过多的状态组合

           一般情况下,对象的一个属性对应着两种状态,比如汽车的干净和不干净,移动和停止。这是4中独立的状态,会构成8中不同的状态转换。如下:


    如果我们继续添加属性,比如颜色 红色和蓝色,那么就会变成8中状态。这是一个指数式的增长,很难想上面一样把这些状态放在一起考虑。这时,由于这些属性都是独立的,所以我们就可以将这个属性所构成的状态转换看成独立的,分开实现。可以使用并行状态来解决这个问题。如下图所示:


    创建并行状态也非常的简单,只需在创建状态时将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);
    当状态机进入一个并行状态组时,所有的子状态都会同时开始运行,每一个子状态的过渡都会正常执行。但是,每一个子状态都有可能退出父状态,如果这样,父状态和它所有的子状态都会结束。
            在Qt状态机框架的并行机制里有一个交错语义。所有的并行操作都是在一个事件处理中独立的、原子的被执行,所以没有事件能打断并行操作。但是,事件仍然是被顺序的处理的,因为状态机本身是单线程的。举个栗子,如果有两个过渡退出同一个并行状态组,并且它们的触发条件同时被满足。在这种情况下,第二个被处理的退出事件将没有任何实际的反应,因为第一个事件已经导致了状态机从并行状态中结束。

           检测组合状态的结束

           其实子状态可以是一个final状态;当进入一个final子状态时,父状态会发出finished() 信号。下图显示了一个组合状态s1在做了一系列的处理后进入了一个final状态:


    当s1进入一个final子状态时,s1会自动发出finished() 信号。我们使用一个 信号过渡 来触发一个状态转换:

    s1->addTransition(s1, SIGNAL(finished()), s2);
    在组合状态中使用final状态对应想隐藏组合状态的内部细节来说是非常有用的。也就是说,对应外部世界来说,只需要进入这个状态,然后等待这个状态的完成信号即可。这对于构建复杂的状态机来说是一种强有力的的封装和抽象机制。但是,对应并行状态组来说,finishe()信号只有在所以的子状态都进入final状态时才会发出。

           无目标状态的过渡

           一个Transition并不是一定要有一个目标状态,并且,没有目标状态的过渡也可以像其他过渡一样被触发。但区别是当一个没有目标状态的过渡被触发时,不会导致任何状态的改变。这运行你在状态机进入某个状态时响应一个信号或事件而不必离开那个状态。例如:

      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状态会结束,然后重新进入该状态。
                事件和过渡

            状态机运行在自己的事件循环中。对于信号转换(QSignalTransition 对象)来说,状态机会自动给它自己投递一个QStateMachine::SignalEvent 当它拦截到相应的信号后;同样,对于QObject事件转换(QEventTransition 对象)来说,QStateMachine::WrappedEvent会被投递。当然,你可以使用QStateMachine::postEvent()投递自己定义的事件给状态机。

           当向状态机投递一个自定义的事件时,你通常还会定义一或多个能被自定义的事件类型触发的过渡。为了创建这种过渡,可以继承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 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。当状态机在s1状态时,改属性会被设置为1.0,因为这个状态显式的为其设置了值。当状态机进入s2状态时,该状态没有为fooBar属性显式的设置值,所以它会被隐式的重置为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,所以,s3会继承该值,将fooBar设置为1.0。

           为状态过渡引入动画

           假设我们有下面的代码:

          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状态时,button变的更大。如果我们点击按钮触发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"));
    为属性引入动画以为着当进入该状态时,属性的赋值不会立刻起作用。相反,当进入该状态时会开发执行该动画并慢慢的改变属性的值。以为我们没有设置动画的开始值和结束值,动画会隐式的设置它们。开始值会被设置为动画开始时的属性值,结束值会被设置为终止状态指定的值。

           检测一个状态中所有的属性均被设置完成

           当使用动画为属性赋值时,一个状态不再为属性定义确切的值,当动画运行时,属性可能具有任何值。而在有些情况下,检测某个属性是否已经被某个状态设置完成对我们来说是很重要的。例如下面的代码:

          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);
    当按钮被点击时,状态机会进入s2状态,该状态会改变按钮的尺寸,然后弹出一个消息框提示用户按钮的尺寸已经被改变了。

    正常情况下,也就是没有使用动画的情况下,这个动作会如我们期望的所运行。但是,如果我们为s1到s2的转换添加了动画,那么当进入s2状态时会执行该动画,但是在动画执行结束之前,按钮的尺寸不会达到预定义的值。在这种情况下,消息框会在按钮尺寸实际设置完成之前弹出。

    为了确保消息框直到按钮尺寸变化到指定值时才弹出,我们可以使用状态的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);
    在这个例子中,当按钮被点击,状态机会进入s2。但会保持在s2按钮的尺寸达到预设的QRect(0, 0, 50, 50)。接着会进入s3状态。当进入s3状态时,消息框会弹出。如果到s2的过渡被添加了动画,那么状态机会停留在s2直到动画播放完成。如果没有添加动画,就会简单的设置属性值然后立即进入s3状态。无论哪种方式,当状态机进入s3时,可以确保按钮的尺寸已经达到了预设值。

            状态在动画完成之前退出

            如果一个状态有属性赋值,并且到这个状态的过渡为这个属性应用了动画,那么该状态有可能在属性被赋予预设值之前退出。这在从不依赖于propertiesAssigned()信号的状态发出的过渡中更有可能发生。当发生这种情况时,状态机保证属性值要么是一个显式设置的值,要么是状态结束时动画运行到的值。

            当一个状态在动画结束之前退出,状态机的行为依赖与过渡的目标状态。如果目标状态显式的设置了该属性值,那么就不需要进行额外的操作。该属性会被设置为目标状态所定义的值。如果目标状态没有设置该属性的值,那么会有两种可能:默认情况下,该属性会被设置为正在离开的那个状态所定义的值。但是,如果设置了全局重置策略,则重置策略优先,该属性会像往常一样被重置。

           默认动画

           正如上文所说,你可以为一个过渡添加动画从而确保在目标状态里的属性赋值时动态的。如果你想为一个属性应用一个特定的动画,不论发生的是哪一个过渡,那么你可以把该动画添加为状态机的默认动画。这在创建状态机之前不知道某个属性会由哪个状态所赋值来说至关重要。例如以下代码:

      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() 并不是父状态机的configuration的一部分。

           子状态机中的状态不能被指定为父状态机中的过渡的目标状态;反过来也是这样。不过,子状态机的finished()信号可以在父状态机中被用来触发一个过渡。

            以上就是Qt状态机框架的基本知识。至于QML中使用的Declarative State Machine Framework,知识点与此类似,大家可以自行研习Qt 帮助文档The Declarative State Machine Framework 一节。








           









    展开全文
  • 原创文章,可以转载,转载时请以超链接形式保留本文地址、...Qt状态机框架中,有一个特殊的类QFinalState,这个类定义了状态机的终止状态,我们仅需要简单的将QFinalState的对象A加入到状态机中,然后定义一个状态迁

    原创文章,可以转载,转载时请以超链接形式保留本文地址、作者信息和本声明。否则将追究法律责任。

    前几节讲述的都是状态机运行的过程,没有结束状态。任何一个状态机都应该有一个结束状态。那如何停止状态机呢,或者说如何进入终止态呢?

    Qt状态机框架中,有一个特殊的类QFinalState,这个类定义了状态机的终止状态,我们仅需要简单的将QFinalState的对象A加入到状态机中,然后定义一个状态迁移到该A就可以终止状态机了。状态机在终止时会触发QStateMachine::finished()信号,我们可以根据该信号做一些“文章”。我们声明一个OnHalt()槽函数,关联QStateMachine::finished()信号,看代码:


    声明:
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QStateMachine>
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    public slots:
        void OnExitStateS1(void);
        void OnEnterStateS2(void);
        void OnHalt(void);
    
    private:
        Ui::MainWindow *ui;
        QStateMachine machine;
    };
    实现:
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QState>
    #include <QDebug>
    #include <QFinalState>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        QState *s1 = new QState();
        QState *s2 = new QState();
        QState *s3 = new QState();
        QFinalState *final = new QFinalState();
        machine.addState(s1);
        machine.addState(s2);
        machine.addState(s3);
        machine.addState(final);
    
        s1->addTransition(ui->button, SIGNAL(clicked()), s2);
        s2->addTransition(ui->button, SIGNAL(clicked()), s3);
        s3->addTransition(ui->button, SIGNAL(clicked()), final);
    
        // 分配属性
        s1->assignProperty(ui->button, "text", "S1");
        s2->assignProperty(ui->button, "text", "S2");
        s3->assignProperty(ui->button, "text", "S3");
    
        connect(s1, SIGNAL(exited()), this, SLOT(OnExitStateS1()));
        connect(s2, SIGNAL(entered()), this, SLOT(OnEnterStateS2()));
        connect(&machine, SIGNAL(finished()), this, SLOT(OnHalt()));
    
        machine.setInitialState(s1);
        machine.start();
    
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::OnExitStateS1(void)
    {
        qDebug() << "Exit s1";
    }
    
    void MainWindow::OnEnterStateS2(void)
    {
        qDebug() << "Enter s2";
    }
    
    void MainWindow::OnHalt(void)
    {
        qDebug() << "State Machine Halted";
    }

    运行上述代码,当点击到S3时,再此点击,状态机终止,输出"State Machine Halted"字符串。


    展开全文
  • Qt之浅析状态机QState

    千次阅读 2017-10-09 21:43:05
    Qt之浅析状态机QState1、状态机,百度百科上分为四部分:现态、条件(事件)、动作和次态。状态机是做界面逻辑常用的方法。 2、Qt上的状态机有关的常用类:QState、QStateMachine、QFinalState等。 3、设备安装...

    Qt之浅析状态机QState

    一、状态机,百度百科上分为四部分:现态、条件(事件)、动作和次态。状态机是做界面逻辑常用的方法。

    二、Qt上的状态机有关的常用类:QState、QStateMachine、QFinalState等。

    三、设备安装程序中界面的逻辑复杂的时候,用状态机会简化程序。例如看下面这个情景。有三个按钮,第一个是“全选”,按下去变为“取消全选”;第二个是“选择已安装”,按下去变为“取消选择已安装”;第三个是“选择未安装”,按下去变为“取消选择未安装”。这三个按钮的作用效果跟按钮下方的TreeView中的设备相关。当按下第一个按钮时,选中所有的设备,按钮为按下状态,图标改变,按钮上的字也响应变成“取消全选”,同时第二个、第三个按钮也会按下,图标、字也相应改变。第二个、第三个按钮同时按下,第一个按钮无论之前是什么状态都会变为按下状态,第二个、第三个按钮只要有一个没有按下,第一个按钮就会弹起。在TreeView中,手动选择设备也会有类似的效果,当选中所有设备,第一个按钮即使没有鼠标点击也会变成按下状态,这也适用于第二个、第三个按钮。这个逻辑关系复杂,如果用标志位需要写很多判断。可以用状态机实现。
    (1)每个按钮单独连接槽,对应各自的功能;
    (2)TreeView每次有设备选择状态改变时,判断是否全选、是否选中了全部已安装设备、是否选择全部未安装、是否有一些设备在全选或选择已安装或者选择未安装状态下取消了选择,并根据具体情况发送响应的信号。总共定义了6种信号。
    (3)三个按钮定义状态机,s1、s2、s3分别代表三个按钮状态。s11、s12代表一号按钮弹起和按下两种状态,并作为s1的子状态,子状态模式为默认模式(可能是ExclusiveStates)。S21、s22是s2的子状态,s31、s32是s3的子状态。s1、s2、s3是s的子状态,模式为平行模式(QState::ParallelStates)。S加入QStateMachine。以下是状态关系图。

    四、以下是代码示意:

    void OpenDeviceDlg::setupStateMachine()
    {
        //**********状态机--ltx--17-7-11
        //**********1--up;2--down;3--invisible
        //**********s1--全选按钮;s2--选择已安装按钮;s3--选择未安装按钮
    
        //**********定义所有的状态State
        QState *s = new QState(QState::ParallelStates);
        QState *s1 = new QState(s);
        QState *s2 = new QState(s);
        QState *s3 = new QState(s);
    
        QState *s11 = new QState(s1);
        QState *s12 = new QState(s1);
        QState *s13 = new QState(s1);
        s1->setInitialState(s11);
        QState *s21 = new QState(s2);
        QState *s22 = new QState(s2);
        QState *s23 = new QState(s2);
        s2->setInitialState(s21);
        QState *s31 = new QState(s3);
        QState *s32 = new QState(s3);
        QState *s33 = new QState(s3);
        s3->setInitialState(s31);
    
        //**********定义所有的Transition
        s11->addTransition(pModel, SIGNAL(selectedAll()), s12);
        s13->addTransition(pModel, SIGNAL(selectedAll()), s12);
        s12->addTransition(pModel, SIGNAL(unselectedAll()), s11);
        s13->addTransition(pModel, SIGNAL(unselectedAll()), s11);
        s11->addTransition(pModel, SIGNAL(emptiedAll()), s13);
        s12->addTransition(pModel, SIGNAL(emptiedAll()), s13);
    
        s21->addTransition(pModel, SIGNAL(selectedInstalled()), s22);
        s23->addTransition(pModel, SIGNAL(selectedInstalled()), s22);
        s22->addTransition(pModel, SIGNAL(unselectedInstalled()), s21);
        s23->addTransition(pModel, SIGNAL(unselectedInstalled()), s21);
        s21->addTransition(pModel, SIGNAL(emptiedInstalled()), s23);
        s22->addTransition(pModel, SIGNAL(emptiedInstalled()), s23);
    
        s31->addTransition(pModel, SIGNAL(selectedNotinstalled()), s32);
        s33->addTransition(pModel, SIGNAL(selectedNotinstalled()), s32);
        s32->addTransition(pModel, SIGNAL(unselectedNotinstalled()), s31);
        s33->addTransition(pModel, SIGNAL(unselectedNotinstalled()), s31);
        s31->addTransition(pModel, SIGNAL(emptiedNotinstalled()), s33);
        s32->addTransition(pModel, SIGNAL(emptiedNotinstalled()), s33);
    
        QIcon checkblank = QIcon(":/images/images/checkblank.png");
        QIcon checkright = QIcon(":/images/images/checkright.png");
    
        //**********配置属性
        //**********按钮1
        s11->assignProperty(ui.pushButtonSelectAll, "visible", true);
        s11->assignProperty(ui.pushButtonSelectAll, "text", tr("All"));
        s11->assignProperty(ui.pushButtonSelectAll, "checked", false);
        s11->assignProperty(ui.pushButtonSelectAll, "icon", checkblank);
    
        s12->assignProperty(ui.pushButtonSelectAll, "visible", true);
        s12->assignProperty(ui.pushButtonSelectAll, "text", tr("cAll"));
        s12->assignProperty(ui.pushButtonSelectAll, "checked", true);
        s12->assignProperty(ui.pushButtonSelectAll, "icon", checkright);
    
        s13->assignProperty(ui.pushButtonSelectAll, "visible", false);
        //**********按钮2
        s21->assignProperty(ui.pushButtonHasInstalled, "visible", true);
        s21->assignProperty(ui.pushButtonHasInstalled, "text", tr("Inst"));
        s21->assignProperty(ui.pushButtonHasInstalled, "checked", false);
        s21->assignProperty(ui.pushButtonHasInstalled, "icon", checkblank);
    
        s22->assignProperty(ui.pushButtonHasInstalled, "visible", true);
        s22->assignProperty(ui.pushButtonHasInstalled, "text", tr("cInst"));
        s22->assignProperty(ui.pushButtonHasInstalled, "checked", true);
        s22->assignProperty(ui.pushButtonHasInstalled, "icon", checkright);
    
        s23->assignProperty(ui.pushButtonHasInstalled, "visible", false);
        //**********按钮3
        s31->assignProperty(ui.pushButtonNotInstalled, "visible", true);
        s31->assignProperty(ui.pushButtonNotInstalled, "text", tr("NInst"));
        s31->assignProperty(ui.pushButtonNotInstalled, "checked", false);
        s31->assignProperty(ui.pushButtonNotInstalled, "icon", checkblank);
    
        s32->assignProperty(ui.pushButtonNotInstalled, "visible", true);
        s32->assignProperty(ui.pushButtonNotInstalled, "text", tr("cNInst"));
        s32->assignProperty(ui.pushButtonNotInstalled, "checked", true);
        s32->assignProperty(ui.pushButtonNotInstalled, "icon", checkright);
    
        s33->assignProperty(ui.pushButtonNotInstalled, "visible", false);
    
        machine.addState(s);
        machine.setInitialState(s);
        machine.start();
    
    }
    
    展开全文
  • Qt状态机框架

    千次阅读 2018-04-01 09:20:44
    英文原文链接:http://doc.qt.io/archives/qt-4.8/statemachine-api.html状态机框架提供用于创建和执行状态图的类。状态图的概念和符号基于Harel的Statecharts: A visual formalism for complex systems,这也是UML...

    英文原文链接:http://doc.qt.io/archives/qt-4.8/statemachine-api.html

    状态机框架提供用于创建和执行状态图的类。状态图的概念和符号基于Harel的Statecharts: A visual formalism for complex systems,这也是UML状态图起源。状态机执行的语义是基于State Chart XML (SCXML)

    状态图提供了一种用图的形式建模的方法,描述一个系统如何对外界刺激进行反应。它通过定义系统可能的状态以及系统如何进行状态转换实现。事件驱动系统(比如Qt应用)的关键特征是,系统的行为不仅取决于最近一次或当前事件,还取决于更早的事件。使用状态图可以容易的表示这种信息。

    状态机框架提供API和执行模型,它们可以有效地将状态图的元素和语义嵌入到Qt应用中。这个框架紧密集成了Qt元对象系统,比如状态转换可以通过信号触发,状态可以利用QObject配置和设置属性调用方法。Qt事件系统也能驱动状态机。

    状态图在状态机框架中是分层的。状态可以嵌套在其他状态中,而且状态机的当前配置由一系列当前活动的状态组成。在状态机框架配置中所有有效的状态都有一个共同的原型。

    状态机模型中的类

    qt提供了以下类,用于创建事件驱动状态机。

    QAbstractState

    The base class of states of a QStateMachine

    QStateMachine 的状态的基类

    QAbstractTransition

    The base class of transitions between QAbstractState objects

    QAbstractState 对象之间转换关系的基类

    QEventTransition

    QObject-specific transition for Qt events

    Qt事件驱动的转换关系

    QFinalState

    Final state

    终态

    QHistoryState

    Means of returning to a previously active substate

    返回到先前活动的子状态的方法

    QKeyEventTransition

    Transition for key events

    按键事件驱动的转换关系

    QMouseEventTransition

    Transition for mouse events

    鼠标事件驱动的转换关系

    QSignalTransition

    Transition based on a Qt signal

    Qt信号驱动的转换关系

    QState

    General-purpose state for QStateMachine

    QStateMachine 的一般用途状态

    QStateMachine

    Hierarchical finite state machine

    分级有限状态机

    QStateMachine::SignalEvent

    Represents a Qt signal event

    表示Qt信号事件

    QStateMachine::WrappedEvent

    Inherits QEvent and holds a clone of an event associated with a QObject

    继承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, 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");

    当进入任意状态时,label的文本将会由此改变。

    进入某状态时,会发射 QState::entered() 信号,离开某状态时,会发射 QState::exited()  信号。下面的代码中,当进入状态S3时,按钮的 showMaximized() 槽将被调用,离开时,将调用 showMinimized() 。

        QObject::connect(s3, SIGNAL(entered()), button, SLOT(showMaximized()));
        QObject::connect(s3, SIGNAL(exited()), button, SLOT(showMinimized()));
    重载 QAbstractState::onEntry() 和 QAbstractState::onExit() 可以定义状态。

    状态机的终结

    前面定义的状态机不会终结。为了状态机能有终结,它需要有一个顶层的终态 (QFinalState object)。当状态机机进入一个顶层终态时,状态机将会发射信号 QStateMachine::finished() 并停止。

    引入一个终态只需要创建一个  QFinalState 对象,并用其作为状态转换关系的目标。

    通过分组状态共享转换关系

    假设我们可以在任何时候点击Quit按钮退出应用。为了实现这个目的,需要创建一个终态,并通过Quit按钮的 clicked() 信号触发转换关系的目标。可以为s1, s2, s3 都增加转换关系,但这种方法十分冗长,而且还要记得以后为每种新增的状态都需要增加这种转换。

    我们可以用过给状态s1, s2, s3 分组的方法实现相同的行为(即无论状态机处于哪种状态,点击Quit按钮均能退出状态机)。这是通过建立一个新的顶层状态实现的,之前的三个状态则作为这个顶层状态子状态。下图显示了新的状态机。


    之前的三个状态被重命名为了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);
    
        QObject::connect(&machine, SIGNAL(finished()), QApplication::instance(), SLOT(quit()));

    这个例子中,我们希望当状态机结束时,退出程序,因此状态机的 finished() 信号与应用的 quit() 槽相连。

    子状态可以覆盖继承的转换。例如,下面的代码增加了这样的转换关系,将使得 Quit 按钮会在s12状态下无视。

        s12->addTransition(quitButton, SIGNAL(clicked()), s12);
    一个转换关系可以以任何状态作为其目标,例如,目标状态不必与源状态的层级相同。

    用历史状态保存和还原当前状态

    试想我们需要对前面的状态机增加一个“中断”机制;用户应可以点击一个按钮,让状态机执行一些不相关的任务,在这之后,状态机应该重新开始之前的工作(例如,在这个例子中,回到s1, s2, s3中的之前的状态)。

    这种行为可以很容易的使用历史状态(history states)建模。一个历史状态(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);

    使用并行状态避免状态的组合爆炸

    假设您想在一台状态机中模拟一组互斥的汽车属性。假设我们感兴趣的属性是干净的和脏的,移动的和不移动的。它需要四个互斥的状态和八个转换才能在所有可能的组合之间自由移动。



    如果我们添加第三个属性(比如红色和蓝色),那么状态的总数将翻倍,达到8个;如果我们增加第四个属性(比如,封闭的 vs 可转换的),那么状态的总数将会翻倍,达到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);
    当进入一个并行状态时,它所有的子状态将同时被进入。单个子状态的转移通常是正常的。然而,任何子状态都可能会经历一个退出父状态的转换。当这种情况发生时,父状态及其子状态都会退出。

    状态机中的并行状态遵循一种交叉语义。所有的并行操作将会在事件处理的单个原子步骤中执行,所有没有事件可以中断并行操作。但是,事件仍将按顺序处理,因为状态机本身时单线程的。举个例子:考虑这样的情况,由两个转换关系都退出相同的并行状态组,而且它们的条件同时变为真。在这种情况下,它们中的后发生的事件将不会产生任何影响,因为第一个事件已经导致状态机退出了并行状态。

    检测组合状态已完成

    子状态可以是终态;进入是终态的子状态时,其父状态会发射 QState::finished() 信号。下图显示了组合状态s1做了某些操作后进入了终态:


    当进入s1的终态时,s1会自动发射 finished() 信号。用一个信号触发的状态转换关系使其触发一个状态转换:

      s1->addTransition(s1, SIGNAL(finished()), s2);

    这是隐藏组合状态内在细节的很实用的方法;比如,外部世界唯一工作时进入状态,然后当状态完成是收到通知。当构建复杂(深度网络)状态机时,这是一种非常有力的抽象和封装机制。(在前面的例子中,当然可以直接创建从s1的完成状态的转换,而 不使用s1的 finish() 信号,但这会导致暴露和依赖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本身,则每次会退出并重入状态( 比如,会发射 QAbstractState::entered() 和 QAbstractState::exited() 信号)。

    事件、转换关系和守卫

    状态机(QStateMachine)运行它自己的事件循环(event loop)。对于信号转换(QSignalTransition objects),状态机截取到对应的信号时,会自动地布置一个状态机信号事件(QStateMachine::SignalEvent )给自己;类似地,对于 QObject 事件转换(QEventTransition objects),会布置 一个状态机包装事件( 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
    {
    public:
        StringTransition(const QString &value)
            : m_value(value) {}
    
    protected:
        virtual bool eventTest(QEvent *e) const
        {
            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"));

    一个不做任何处理的事件可以被状态机静默地消费。这对分组状态和提供事件的默认处理时很有用的;例如,下面的状态图:


    对于深度嵌套的状态图,您可以在最合适的粒度级别上添加此类“撤退”转换。

    使用恢复策略来自动恢复属性




    未完待续。。。




    展开全文
  • qt状态机的实现

    2011-11-04 16:45:37
    创建状态,设置状态中的属性,设置初始状态,设置状态装换条件、动画,启动状态机 int nMargin = 9; int nInitWidth = m_pSelMoldForm-&gt;width(); int nInitHeight = m_pSelMoldForm-&gt;height...
  • Qt 状态机框架学习

    万次阅读 2011-05-09 23:35:00
    Qt状态机框架是基于状态图XML(SCXML) 实现的。从Qt4.6开始,它已经是QtCore模块的一部分。尽管它本身是蛮复杂的一套东西,但经过和Qt的事件系统(event system)、信号槽(signals and slots)及属性系统(property ...
  • 详解Qt中的状态机机制(一)

    千次阅读 2016-10-27 15:56:44
    状态机,简写为FSM(Finite State Machine),状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。  在GUI开发的时候,...
  • Qt状态机框架介绍(一)

    千次阅读 2019-10-16 21:56:55
    概述 状态机,简写为FSM(Finite State Machine),...Qt状态机的使用场景主要针对比较复杂的界面,或者需要切换不同状态的控件,比如三态按钮,每个状态对应不同的样式,如果自己做状态管理,那就比较麻烦了。 而...
  • Qt状态机框架的一个典型模型
  • QT学习之状态机框架

    千次阅读 2019-01-24 10:31:37
    状态机框架 创建状态机
  • http://blog.sina.com.cn/s/blog_6cf525b10100o4ew.html #include  #include "widget.h" #include  #include  #include  #include  #include  int main(int argc, char *...QApplication a(arg
  • QT状态机框架

    千次阅读 2010-03-10 10:31:00
    状态机(The State Machine Framework) 作者:刘旭晖 Raymond 转载请注明出处Email:colorant@163.comBLOG:http://blog.csdn.net/colorant/ QT的State Machine Framework是在Qt4.6中引入的,其理论基础是Harel的...
  • 对此,辉为科技以TI 工业级的A8主板作为核心,配以真彩液晶屏和触摸屏作为人交互设备,采用目前Linux操作系统及QT/Embedded 图形库,实现了大量数据的实时采集,数据的曲线动态显示,数据保存以及人工控制等必备...
  • 上次发布demo虽然使用了大量的动画框架、有限状态机框架,但是仍有瑕疵。比如说在用户一直按下按键的时候角色会被“冻”住,但是角色的位置是移动的,这可能是使用QKeyEventTransition的一个副作用吧。在觉察到使用...
  • Qt 之自定义控件(开关按钮)

    万次阅读 多人点赞 2018-05-30 10:05:40
    简述接触过IOS系统的童鞋们应该对开关按钮很熟悉了,它的切换以及滑动比较帅气。通常说的开关按钮,有两个状态:on、off。下面,我们利用自定义控件来实现一个开关按钮。简述 原理 源码 示例 ...使用QT
  • QTQt Qml状态机框架

    千次阅读 2018-05-24 09:34:31
    Qt5.4引入了QML状态机框架,与C++状态机框架类似,可以在应用程序中创建并执行状态图。为此,QtQml.StateMachine模块提供了一些相关的QML类型,用于创建事件驱动的状态机,这些QML类型列举如下: StateMachine—— ...
  • Qt 之动画框架

    万次阅读 多人点赞 2018-05-30 10:15:25
    简述Qt动画框架旨在为创建动画和平滑的GUI提供了一种简单的方法。通过Qt动画属性,该框架为部件和其它QObject对象的动画操作提供了非常大的自由性,框架也可以被用于图形视图框架中,动画框架中许多可用的概念也可以...
  • 开源QT写的串口调试上位机(带波形显示)

    万次阅读 热门讨论 2015-03-17 00:26:05
    为了方便PC同单片机(下位)进行通信,需要开发一款基于串口协议的上位机辅助调试软件。软件不仅能够向下位发送数据,发送指令进而控制下位,而且还应该能准确接收下位发来的数据,便于对下位的一些...
  • Qt实现菜单栏,工具栏,状态

    万次阅读 2016-03-30 14:29:35
    1.菜单栏1.... 同样子菜单也是同样操作。 如果我们想给菜单设置ICON,可以如下操作: 在File下新建一个Open子菜单,现在想给Open子菜单添加Icon图标。选中子菜单Open,在Open子菜单对应的QAction属性中可以设置icon ...
  • Qt 查看IP是否在线的两种方法

    千次阅读 2017-03-02 09:09:13
    1、使用QProcess和Ping QProcess *cmd = new QProcess; #ifdef _TTY_ARMV4_ QString strArg = "ping -s 1 -c 1 " + b_tmpIpStr; //linux平台下的格式 #els
1 2 3 4 5 ... 20
收藏数 11,690
精华内容 4,676
热门标签
关键字:

qt状态机