0

在另一个问题中,您告诉我使用 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

问题是我使用for1 到 20(我必须在一场比赛中跑 20 次)。我尝试使用随机的 G1 和 COM 播放来启动程序,但应用程序冻结到最后一次执行,for并且我在场景上仅打印卡的最后配置。这就是原因,因为之前我说过我希望程序停止......

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

4

2 回答 2

2

下面是一个完整的示例,长 71 行,以literate 编程风格呈现。它也可以在github上找到。该示例由一个 qmake.pro文件(未显示)和main.cpp完整显示在下面。该示例具有以下结构:

  1. 标题
  2. 卡片物品
  3. 状态机行为
  4. 主要的
  5. 页脚

示例截图

主要的

首先,让我们设置我们的场景:

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);

我们将使用辅助函数以声明方式向机器添加行为。这不是唯一可能的模式,但它有效并且相当容易应用。First, when any items are selected, the state changes from s_idleto 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) {}
};

selected然后,我们利用这种通用信号源来描述当给定场景有一个选择当且仅当当真或没有选择当当当当当时转换到目标状态的行为selected

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"
于 2016-06-06T22:44:52.937 回答
1

在 qt 中,您不需要主动等待事件(通常也不应该)。只需子类化作为主界面一部分的小部件的事件处理方法。

例如,这是使用 a 的子类QGraphicsItem来更改游戏状态的代码。你可以对场景本身、小部件等做同样的事情……但通常应该是这样的。

void CardGameGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
   if(event->button() == Qt::RightButton)
   {
      makeQuickChangesToGameState();
      scene()->update(); //ask for a deffered ui update
   }
   QGraphicsItem::mousePressEvent(event);
}

即使您以某种方式使用状态机,makeQuickChangesToGameState()也应该触发机器状态更改,然后尽快返回。

于 2016-06-06T14:16:02.043 回答