QStateMachine - QMouseEvent

时间:2016-06-06 11:19:06

标签: c++ qt qevent qmouseevent qstatemachine

在另一个问题中,您告诉我使用QStateMachine。

我是Qt的新手,这是我第一次使用这些对象,所以我犯了很多逻辑错误,所以使用QStateMachine这是一个很大的问题......

这是唯一能做到这一点的方法吗?我试着解释一下我的程序:

我想创建一个卡片的游戏,在之前的版本中,我使用了一个带有这一系列命令的旧图形库:

-> print cards on the scene 
-> wait for a mouse input (with a do-while)
-> if(isMouseClick(WM_LBUTTONDOWN)) 
-> if(mouse position is on the first card) 
-> select that card. So i wish to do the same thing with QGraphics. 

通过这种方式我告诉程序:

-> print cards 
-> wait for a mouse event 
-> print the card that I've selected with that event. 

现在我想改变程序图形,我已经介绍了QGraphics。 我创建了一个场景并在其上打印所有对象“卡”,所以现在我想告诉程序:

-> print the object and wait the mouse input
-> if a card is to selected with the left clik
-> print that card in scene, wait 1/2 second and go ahead with the program

问题是我使用for 1到20(我必须在匹配中运行20次)。 我试图通过随机的G1和COM播放程序,但应用程序冻结,直到最后一次执行for并且我在场景上仅打印最后一张卡片配置。 这就是原因,因为之前我说过我希望程序停止......

没有QStateMachine可以吗? 只是告诉他:“暂停”,打印这种情况,等待鼠标继续?

2 个答案:

答案 0 :(得分:1)

In qt you don't need to actively wait for an event (and usually shouldn't). Just subclass the event handling method of a widget which is part of the main interface.

For instance this is the code which use a subclass of a Script Error to change the game state. You could do the same with the scene itself, widgets, etc... but it should usually be like this.

QGraphicsItem

even if you are somehow using a state machine, void CardGameGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { if(event->button() == Qt::RightButton) { makeQuickChangesToGameState(); scene()->update(); //ask for a deffered ui update } QGraphicsItem::mousePressEvent(event); } should just trigger the machine state change, and go back asap.

答案 1 :(得分:1)

下面是一个完整的例子,长71行,在literate programming style中显示。它也可以在github上找到。该示例包含qmake .pro文件(未显示)和main.cpp,如下所示。该示例具有以下结构:

  1. 标题
  2. 卡片物品
  3. 状态机行为
  4. 主要
  5. 页脚
  6. screenshot of the example

    主要

    首先,让我们设置场景:

    int main(int argc, char ** argv) {
       QApplication app{argc, argv};
       QGraphicsScene scene;
       QGraphicsView view{&scene};
       scene.addItem(new CardItem(0, 0, "A"));
       scene.addItem(new CardItem(20, 0, "B"));
    

    状态机有三种状态:

       QStateMachine machine;
       QState s_idle{&machine};     // idle - no card selected
       QState s_selected{&machine}; // card selected, waiting 1/2 second
       QState s_ready{&machine};    // ready with card selected
       machine.setInitialState(&s_idle);
    

    我们将使用辅助函数以声明方式向计算机添加行为。这不是唯一可能的模式,但它可以工作并且相当容易应用。首先,当选择任何项目时,状态从s_idle更改为s_selected

       on_selected(&s_idle, &scene, true, &s_selected);
    

    然后,超时后,状态变为s_ready

       on_delay(&s_selected, 500, &s_ready);
    

    如果取消选中这些项目,我们会返回s_idle

       on_selected(&s_selected, &scene, false, &s_idle);
       on_selected(&s_ready, &scene, false, &s_idle);
    

    由于我们没有更好的办法,我们只需在输入s_ready州后取消选择所有项目即可。这表明国家已进入。当然,由于选择被清除,它会立即离开,我们在上面指出s_idle是没有选择任何项目时的状态。

       QObject::connect(&s_ready, &QState::entered, &scene, &QGraphicsScene::clearSelection);
    

    我们现在可以启动机器并运行我们的应用程序:

       machine.start();
    
       view.show();
       return app.exec();
    }
    

    请注意显式动态内存分配的最小使用,并且无需任何手动内存管理。

    卡片物品

    CardItem类是一个简单的卡片图形项目。该项目是可选择的。它也可以移动。图形视图框架自动处理交互:您不必手动解释鼠标按下/拖动/释放 - 至少现在还没有。

    class CardItem : public QGraphicsObject {
       Q_OBJECT
       const QRect cardRect { 0, 0, 80, 120 };
       QString m_text;
       QRectF boundingRect() const Q_DECL_OVERRIDE { return cardRect; }
       void paint(QPainter * p, const QStyleOptionGraphicsItem*, QWidget*) {
          p->setRenderHint(QPainter::Antialiasing);
          p->setPen(Qt::black);
          p->setBrush(isSelected() ? Qt::gray : Qt::white);
          p->drawRoundRect(cardRect.adjusted(0, 0, -1, -1), 10, 10);
          p->setFont(QFont("Helvetica", 20));
          p->drawText(cardRect.adjusted(3,3,-3,-3), m_text);
       }
    public:
       CardItem(qreal x, qreal y, const QString & text) : m_text(text) {
          moveBy(x, y);
          setFlags(QGraphicsItem::ItemIsSelectable);
       }
    };
    

    状态机行为

    将状态机行为分解为可用于在给定状态下声明行为的函数是有帮助的。

    首先,延迟 - 一旦输入src状态,并且经过了给定的毫秒数,机器就会转换到目标状态:

    void on_delay(QState * src, int ms, QAbstractState * dst) {
       auto timer = new QTimer(src);
       timer->setSingleShot(true);
       timer->setInterval(ms);
       QObject::connect(src, &QState::entered, timer, static_cast<void (QTimer::*)()>(&QTimer::start));
       QObject::connect(src, &QState::exited,  timer, &QTimer::stop);
       src->addTransition(timer, SIGNAL(timeout()), dst);
    }
    

    要拦截选择信号,我们需要一个发出通用信号的辅助类:

    class SignalSource : public QObject {
       Q_OBJECT
    public:
       Q_SIGNAL void sig();
       SignalSource(QObject * parent = Q_NULLPTR) : QObject(parent) {}
    };
    

    然后我们利用这样的通用信号源来描述当给定场景具有选择iff selected为真时转换到目标状态的行为,或者如果selected为假则没有选择:< / p>

    void on_selected(QState * src, QGraphicsScene * scene, bool selected, QAbstractState * dst) {
       auto signalSource = new SignalSource(src);
       QObject::connect(scene, &QGraphicsScene::selectionChanged, signalSource, [=] {
          if (scene->selectedItems().isEmpty() == !selected) emit signalSource->sig();
       });
       src->addTransition(signalSource, SIGNAL(sig()), dst);
    }
    

    页眉和页脚

    示例从以下标题开始:

    // https://github.com/KubaO/stackoverflown/tree/master/questions/sm-cards-37656060
    #include <QtWidgets>
    

    它以下面的页脚结束,由moc生成的信号实现和SignalSource类的对象元数据组成。

    #include "main.moc"