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 行で、文芸的なプログラミング スタイルで示されています。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"));

ステート マシンには次の 3 つの状態があります。

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

次に、このようなユニバーサル信号ソースを利用して、指定されたシーンにselectedtrue の場合に選択がある場合、またはselectedfalseの場合に選択がない場合に、目的の状態に遷移する動作を記述します。

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 に答える