Организация сложной логики переключения виджетов (Qt)

Программирование Программирование на С++ Использование библиотеки Qt/QML Организация сложной логики переключения виджетов (Qt)

  • В этой теме 0 ответов, 1 участник, последнее обновление 4 недели, 1 день назад сделано Васильев Владимир Сергеевич.
Просмотр 0 веток ответов
  • Автор
    Сообщения
    • #6210
      @admin

      1 Разделение на экраны

      В Qt-программах базовым элементом интерфейса пользователя является виджет, представляющим не только окна, но и все вложенные элементы (кнопки, поля ввода и так далее). В ряде случаев необходимо «переключать» их, например в мобильных приложениях — при клике по кнопке «О программе» должен открыться экран со справкой, а при закрытии справки — произойти возврат на предыдущий экран.

      Сам по себе виджет, является классом и нередко реализует сложную логику. Кроме того, логика переключения экранов тоже бывает достаточно сложной. Например, если вы реализуете иерархическое меню, в каждом уровне которого пользователь вводит некоторые данные, а результат работы пользователя должен зависеть от всех введенных данных. В таких случаях, нам хотелось бы изолировать логику переключения экранов от основной логики виджета.

      Основным способом такой изоляции является разбиение класса на несколько (см. книги о рефакторинге). Мы создаем класс «Игровой Экран», который реализует логику переключения на соседение экраны, и содержит виджет «Игра», областью ответственности которого является только игровой процесс.

      2 Выделение ScreenController-а

      Это очень просто и удобно, каждый Screen знает о своих «соседях», т.е. при клике не кнопку «Играть» в меню — MenuScreen скрывает себя и показывает GameScreen. В простейшем случае экраны связываются через указатели. Однако, часть этой логики у всех экранов может оказаться общая — в частности это возможно возврата на предыдущий экран. В статье «Стек виджетов Qt» предложено перенести логику переключения в отдельный класс ScreenController, который будет знать обо всех экранах (хранить указатели) и выполнять их «переключение». Все что остается экранам — генерировать правильные сигналы.

      Такой прием очень сильно ослабляет зависимости между экранами, теперь Экран Помощи не знает о том, откуда он был вызван (и куда надо вернуться после закрытия). Код стал значительно чище. Также, «общая» логика может выноситься в базовые классы, тут за счет класса ScreenStack осущестляется возврат назад: «Qt — обработка Android back button«.

      3 Иерархия ScreenController-ов

      Все прекрасно — чистый код повсюду, но если логика взаимодействия классов очень сложная — то ScreenController станет узким местом. Например, вы пишите игру, где игроку после прохождения уровня отображается обучающая справочная информация (открывается InfoScreen). При этом, в классе GameScreen такая информация не должна храниться — ведь не он занимается отображением. Ссылка на InfoScreen есть только у ScreenController, поэтому в него придется поместить и эту «справочную информацию».

      Это надуманный пример, но такие ситуации часто встречаются. Для решения проблемы (разгрузки класса ScreenController) я предлагаю разделять его на классы в соответствии с множествами окон, которые он обрабатывает. Нечто похожее, на то, что я реализовывал приведено на рисунке:

      ScreenController-ы включаются друг в друга, каждый из них имеет очень простую реализацию, ведь он обрабатывает сигналы от небольшого числа окон (а не от всех экранов как было раньше). Для реализации такого поведения, в ScreenController стоит добавить сигнал back, который будет вырабатываться когда в его стеке нет виджетов (необходимо перейти на родительский ScreenController). Так как это функционал, общий для всех контроллеров и относится к стеку виджетов — то его необходимо поместить в ScreensStack:

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

      Ссылка на репозиторий с работающим примером скоро будет…

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