Стек виджетов Qt

Помечено: , ,

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 11 мес., 4 нед. назад.

  • Автор
    Сообщения
  • #3176

    Во многих приложениях во время работы нужно переключать окна. Например, если вы пишите игрушку, то в ней могут быть экраны меню, помощи, игры, демо-игры, изменения настроек и т.п. При использовании Qt удобно верстать экраны с помощью QtDesigner. Помимо установки текущего экрана периодически нужно возвращаться к предыдущему окну – чтобы реализовать это можно использовать стек, в который сохраняются виджеты.

    В библиотеке Qt есть стандартные классы QStackedLayout и QStackedWidget. Они позволяют отображать несколько виджетов в виде стопки (один поверх другого). Вы можете изменить текущий виджет методами setCurrentWidget (принимает в качестве аргумента виджет) и setCurrentIndex (принимает номер виджета). Эти виджеты, к сожалению, не хранят историю переключения виджетов (вернуться к предыдущему виджету с их помощью нельзя), т.е. если вам нужна такая функциональность – писать ее придется в любом случае вручную. Кроме того, если у вас есть полупрозрачные виджеты (или с прозрачными частями), то через “дырки” будет видно другие виджеты. Эти классы не предназначены для использования с прозрачными окнами. Тем не менее, они использовались в моей первой игрушке под Android:

    ScreenController::ScreenController(QWidget *parent) :
      QWidget(parent), m_stackedLayout(this) {
    
      GameScreen *pGame(new GameScreen(this));
      HelpScreen *pHelp(new HelpScreen(this));
      MenuScreen *pMenu(new MenuScreen(this));
    
      m_stackedLayout.addWidget(pGame);
      m_stackedLayout.addWidget(pHelp);
      m_stackedLayout.addWidget(pMenu);
    
      connect(pHelp, SIGNAL(menuClicked()), SLOT(on_2menu()));
    
      connect(pGame, SIGNAL(menuClicked()), SLOT(on_2menu()));
      connect(pGame, SIGNAL(levelComplete(int,int)), SLOT(on_2menu()));
    
      connect(pMenu, SIGNAL(helpClicked()), SLOT(on_2help()));
      connect(pMenu, SIGNAL(levelSelected(int, int, int,int,QString)),
              this, SLOT(on_2game(int, int, int,int,QString)));
    
      connect(this, SIGNAL(helpScreenShowed()), pHelp, SLOT(load()));
      connect(this, SIGNAL(gameScreenShowed(int, int, int, int, QString)),
              pGame, SLOT(load(int, int, int, int, QString)));
    }
    
    void ScreenController::on_2game(int setNumber, int levelNumber, int n, int m, QString data) {
      m_stackedLayout.setCurrentIndex(0);
      emit gameScreenShowed(setNumber, levelNumber, n, m, data);
    }
    
    void ScreenController::on_2help() {
      m_stackedLayout.setCurrentIndex(1);
      emit helpScreenShowed();
    }
    
    void ScreenController::on_2menu() {
      m_stackedLayout.setCurrentIndex(2);
      emit menuScreenShowed();
    }

    Я думаю, этот код не нуждается в комментариях. Полную версию можно взять в репозитории.

    В моей новой игрушке стандартные варианты не подошли, поэтому я решил написать свой собственный класс, помогающий в переключении окон:

    #include <QWidget>
    #include <QGridLayout>
    #include <QStack>
    
    class ScreensStack : public QWidget {
        Q_OBJECT
    public:
        explicit ScreensStack(QWidget *parent = 0);
    public slots:
        void push(QWidget* widget);
        void pop();
        int lenght();
    private:
        QStack<QWidget*> m_widgets;
        QGridLayout m_layout;
    };

    Видно, что интерфейс позволяет добавить виджет в вершину стека, удалить виджет из вершины стека (при этом видимым становится следующий виджет в стеке) и получить количество виджетов (эта функция нужна, например, при обработки Back Button на Android).

    Конструктор класса создает QLayout и устанавливает его для виджета:

    ScreensStack::ScreensStack(QWidget *parent)
        : QWidget(parent), m_layout(this) {
        setLayout(&m_layout);
        m_layout.setMargin(0);
    }

    При добавлении в стек, вершина скрывается (методом hide()). За счет этого наш класс корректно работает с прозрачными виджетами:

    void ScreensStack::push(QWidget *widget) {
        if (false == m_widgets.empty()) {
            QWidget *top = m_widgets.front();
            top->hide();
        }
        m_layout.addWidget(widget);
        m_widgets.push_front(widget);
        widget->show();
    }

    При удалении элемента из стека, виджет вершины скрывается, а следующий за ним виджет – показывается:

    void ScreensStack::pop() {
        if (m_widgets.empty())
            return;
        m_widgets.front()->hide();
    
        m_widgets.pop_front();
        if (false == m_widgets.empty()) {
            m_widgets.front()->show();
        }
    }

    Пример использования этого класса я взял со своей новой казуальной игрушки:

    ScreenController::ScreenController(QWidget *parent)
        : ScreensStack(parent) {
    
        m_menu = new MenuScreen(this);
        connect(m_menu, SIGNAL(levelSelected(int)), SLOT(on_level_selected(int)));
        m_menu->hide();
    
        m_game = new GameScreen(this);
        connect(m_game, SIGNAL(back()), SLOT(pop()));
        connect(m_game, SIGNAL(tutorial_selected()), SLOT(on_tutorial_selected()));
        connect(m_game, SIGNAL(levelComplete(int)), m_menu, SLOT(on_level_complete(int)));
        m_game->hide();
    
        m_tutorial = new TutorialScreen(this);
        connect(m_tutorial, SIGNAL(back()), SLOT(pop()));
        m_tutorial->hide();
    
        push(m_menu);
    }
    
    void ScreenController::on_level_selected(int number) {
        Level level = LEVEL_DB.level(number);
        m_game->load(level, GameScreen::State::TipsOn);
    
        push(m_game);
    }
    
    void ScreenController::on_tutorial_selected() {
        push(m_tutorial);
    }
    

    Класс ScreenController является наследником ScreensStack. В конструкторе класса создаются экраны, соединяются с помощью сигналов и слотов и скрываются методом hide(). Текущим устанавливается экран меню с помощью вызова push(). если пользователь хочет вернуться в предыдущий экран – какое-либо окно выработало сигнал back() – вызывается слот pop().

    Пример целиком вы можете взять в репозитории.

Для ответа в этой теме необходимо авторизоваться.