使用 QStateMachine 在菜单屏幕之间切换

Posted

技术标签:

【中文标题】使用 QStateMachine 在菜单屏幕之间切换【英文标题】:Transitioning between menu screens with QStateMachine 【发布时间】:2012-03-11 02:46:45 【问题描述】:

我正在考虑使用QStateMachine 在游戏中的菜单屏幕之间进行转换。但是,我不确定如何在状态之间发生转换时启动一些代码(例如show()QWidget)。我可以用普通的旧信号很容易地做到这一点(见注释掉的代码),但我认为我可以在使用过渡切换屏幕时做一些花哨的动画。

这是我的代码:

编辑:根据 Koying 的建议更新。

ApplicationWindow.h

#include <QtGui>
#include <QStateMachine>

#include "MainMenu.h"
#include "LoadGameMenu.h"

class ApplicationWindow : public QMainWindow

    Q_OBJECT
public:
    ApplicationWindow();
private slots:
    void mainMenuButtonClicked();
    void loadGameMenuButtonClicked();
private:
    MainMenu* mainMenu;
    LoadGameMenu* loadGameMenu;

    QStateMachine stateMachine;

    QStackedWidget* stack;
;

ApplicationWindow.cpp

ApplicationWindow::ApplicationWindow()

    resize(800, 600);

    stack = new QStackedWidget(this);

    mainMenu = new MainMenu();
    setCentralWidget(mainMenu);
    loadGameMenu = new LoadGameMenu();

    QState* mainMenuState = new QState();
    QState* loadGameMenuState = new QState();

    QAbstractTransition* loadTransition = mainMenuState->addTransition(
        mainMenu, SIGNAL(loadGameClicked()), loadGameMenuState);
    connect(loadTransition, SIGNAL(triggered()), this, SLOT(loadGameMenuButtonClicked()));

    QAbstractTransition* mainMenuTransition = loadGameMenuState->addTransition(
        loadGameMenu, SIGNAL(backToMainMenuClicked()), mainMenuState);
    connect(mainMenuTransition, SIGNAL(triggered()), this, SLOT(mainMenuButtonClicked()));

    stateMachine.addState(mainMenuState);
    stateMachine.addState(loadGameMenuState);

    stateMachine.setInitialState(mainMenuState);
    stateMachine.start();


void ApplicationWindow::mainMenuButtonClicked()

    setCentralWidget(mainMenu);


void ApplicationWindow::loadGameMenuButtonClicked()

    setCentralWidget(loadGameMenu);

LoadGameMenu.h:

#include <QtGui>

class LoadGameMenu : public QWidget

    Q_OBJECT
public:
    LoadGameMenu();
signals:
    void backToMainMenuClicked();
private:
    QPushButton* loadGameButton;
    QPushButton* backToMainMenuButton;
;

LoadGameMenu.cpp:

#include "LoadGameMenu.h"

LoadGameMenu::LoadGameMenu()

    loadGameButton = new QPushButton(tr("Load"));
    backToMainMenuButton = new QPushButton(tr("Main Menu"));

    QObject::connect(backToMainMenuButton, SIGNAL(clicked()),
        this, SIGNAL(backToMainMenuClicked()));

    QVBoxLayout* layout = new QVBoxLayout();
    layout->addWidget(loadGameButton);
    layout->addWidget(backToMainMenuButton);
    layout->setContentsMargins(300, 400, 300, 200);
    setLayout(layout);

MainMenu.h:

#include <QtGui>

class MainMenu : public QWidget

    Q_OBJECT
public:
    MainMenu();
signals:
    void newGameClicked();
    void loadGameClicked();
private slots:
    void exit();
private:
    QPushButton* newGameButton;
    QPushButton* loadGameButton;
    QPushButton* exitGameButton;

    QMenu* fileMenu;
;

MainMenu.cpp:

#include "MainMenu.h"

MainMenu::MainMenu()

    newGameButton = new QPushButton(tr("New Game"), this);
    loadGameButton = new QPushButton(tr("Load Game"));
    exitGameButton = new QPushButton(tr("Exit"));

    QObject::connect(newGameButton, SIGNAL(clicked()), this, SIGNAL(newGameClicked()));
    QObject::connect(loadGameButton, SIGNAL(clicked()), this, SIGNAL(loadGameClicked()));
    QObject::connect(exitGameButton, SIGNAL(clicked()), qApp, SLOT(quit()));

    QVBoxLayout* layout = new QVBoxLayout();
    layout->addWidget(newGameButton);
    layout->addWidget(loadGameButton);
    layout->addWidget(exitGameButton);
    layout->setContentsMargins(300, 200, 300, 200);
    setLayout(layout);


void MainMenu::exit()

    if( QMessageBox::question(
        this,
        tr("Exit?"),
        tr("Do you really want to exit the game?"),
        QMessageBox::Yes | QMessageBox::No,
        QMessageBox::No
        ) == QMessageBox::Yes
    )
    
        qApp->quit();
    

ma​​in.cpp:

#include <QtGui>

#include "ApplicationWindow.h"

int main(int argv, char **args)

    QApplication app(argv, args);

    ApplicationWindow window;
    window.show();

    return app.exec();

那么,当转换发生时,我如何触发一些行为或动作?

干杯。

【问题讨论】:

【参考方案1】:

要在状态转换中实际执行某些操作,您必须连接到转换的triggered() 信号,例如

QAbstractTransition* trMainLoad = mainMenuState->addTransition(mainMenu, SIGNAL(loadGameClicked()), loadGameMenuState);
connect(trMainLoad , SIGNAL(triggered()), SLOT(...));

【讨论】:

干杯。我已经这样做了,但现在我得到了:Unhandled exception at 0x779815de (ntdll.dll) in Hello Notepad.exe: 0xC0000005: Access violation reading location 0xabababaf. 异常发生在mainMenuButtonClicked() 内部——我不应该调用setCentralWidget 两次吗?我已经用我的新代码更新了我原来的帖子。 根据setCentralWidget() doc:QMainWindow takes ownership of the widget pointer and deletes it at the appropriate time.,所以当你执行setCentralWidget(loadGameMenu);时你的mainMenu可能被破坏了 当没有对指针的引用时,适当的时间不是吗?我还能如何在QMainWindow 中显示不同的Widgets(屏幕)?由于缺乏经验,我只是这样做 - 我愿意接受其他选择。 @Mitch 另一种选择是让它们成为 QStackedWidget 的页面;将它与 QSignalMapper (将按钮映射到小部件)结合起来,您可以非常轻松地制作一个非常漂亮的菜单系统。 感谢@tmpearce,效果很好。有兴趣的人的最终代码:pastebin.com/YhbHHJNe

以上是关于使用 QStateMachine 在菜单屏幕之间切换的主要内容,如果未能解决你的问题,请参考以下文章

QStateMachine 它是如何工作的

QStateMachine 如何在不同的 QState 中显示和隐藏 QGraphicsView 和 QObject

同步 QStateMachine

在两个堆栈之间传递参数 - React Native

侧边菜单未覆盖所有屏幕(DrawerNavigator - React Native)

vue element的meun菜单自适应屏幕宽度