下面是一个完整的示例,长 71 行,以literate 编程风格呈现。它也可以在github上找到。该示例由一个 qmake.pro
文件(未显示)和main.cpp
完整显示在下面。该示例具有以下结构:
- 标题
- 卡片物品
- 状态机行为
- 主要的
- 页脚
主要的
首先,让我们设置我们的场景:
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_idle
to 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"