Работа с графической сценой [Qt]

В библиотеку Qt включены специальные классы для удобной и эффективной работы с большим количеством двумерных графических объектов. Сегодня мы рассмотрим не все (остальные потом), но основные — QGraphicsScene, QGraphicsView, QGraphicsItem.

Использовать эти классы удобно если в Вашей программе происходит хоть какая-то работа с графическими объектами, и особенно, если таких объектов много, требуется обнаруживать их пересечения/столкновения, производить поиск объектов в заданной области (для этих случаев в QGraphicsScene реализованы эффективные алгоритмы).

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

В качестве примера, напишем программу, состоящую из панели инструментов (инструмента будет всего 2 — ромб и прямоугольник) и области рисования (на нее будут добавляться выбранные объекты).

рис. 1 снимок окна программы-примера

рис. 1 снимок окна программы-примера

Графическая сцена (QGraphicsScene) хранит внутри себя графические элементы (QGraphicsItem). Элементы упорядочиваются определенным образом (за счет этого, обеспечиваются перерисовка минимальной области сцены при изменении свойств элемента и эффективное обнаружение столкновений).

Грубо говоря, графическая сцена — это данные, для отображения которых используется вид (QGraphiscView). С одной сценой может быть связано несколько видов. QGraphiscView является наследником от QWidget, и даже, может быть размещена на форме при помощи Qt Designer.

Имеется ряд готовых элементов, которые могут быть добавлены на сцену, например овал (методом addEllipse()), текст (методом addText()), линия (методом addLine()) и т.п. Кроме того, можно разместить на сцене собственные элементы (методом addItem()), именно этим мы и займемся.

Метод addItem() принимает указатель на объект типа QGraphicsItem, однако, QGraphicsItem — абстрактный класс, с двумя чисто виртуальными методами:

  • boundingRect(), определяет прямоугольник (QRectF) внутри которого расположен наш элемент. Этот метод нужен для того, чтобы графическая сцена могла вовремя понять, что элемент необходимо перерисовать;
  • paint(), осуществляющий отрисовку нашего элемента.

Рисование элемента происходит в локальных координатах, внутри обрасти, ограниченной boundingRect().

рис. 2 UML диаграмма классов графических элементов

рис. 2 UML диаграмма классов графических элементов

Класс Figure (рис. 2) содержит поля для хранения высоты и ширины объекта, а также, определяет метод boundingRect(), однако, он также как и QGraphicsItem, является абстрактным (для него не определен метод paint()).

class Figure : public QGraphicsItem {
public:
  Figure();
protected:
  virtual QRectF boundingRect() const;

  float m_height, m_width;
};
// ...
Figure::Figure() : m_height(20), m_width(40) { }

QRectF Figure::boundingRect() const {
  return QRectF(-m_width / 2, -m_height / 2, m_width, m_height);
}
// ...
class RhombFigure : public Figure {
public:
  RhombFigure();
protected:
  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem*, QWidget*);
};
//...
RhombFigure::RhombFigure() { }

void RhombFigure::paint(QPainter *painter, const QStyleOptionGraphicsItem*,
                        QWidget*) {
  painter->setPen(Qt::black);
  painter->setBrush(Qt::white);

  QPoint
    p0(0, -m_height / 2), p1(m_width / 2, 0),
    p2(0, m_height / 2), p3(-m_width / 2, 0);
  painter->drawLine(p0, p1);
  painter->drawLine(p1, p2);
  painter->drawLine(p2, p3);
  painter->drawLine(p3, p0);
}

Из листинг 1 показано каким образом происходит рисование графических объектов. Каждый объект рисуется в своей собственной системе координат, центр которой будет использоваться при добавлении объекта на сцену (использовании QGraphicsScene::addItem()). Отмечу, что при очистке сцены (QGraphicsScene::clear()) или ее уничтожении будут уничтожены добавленные на нее объекты.

QGraphicsItem не является наследником класса QObject, поэтому он, в частности, не может использовать механизм сигналов и слотов. Если Вам нужно использовать сигналы и слоты — следует применять множественное наследование (в данном случае, оно безопасно — проблемы ромбовидного наследования возникнуть не может).

Графическая сцена может обрабатывать события мыши в методе mousePressEvent(QGraphicsSceneMouseEvent *event). Для получения координат сцены, в которых произошло событие используется метод QGraphicsSceneMouseEvent::scenePos().

void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
  if (None == m_figureType) return;

  QGraphicsItem *item;

  switch (m_figureType) {
  case Rect:
    item = new RectFigure();
    break;
  case Rhomb:
    item = new RhombFigure();
    break;
  }

  item->setPos(event->scenePos());
  addItem(item);
}

void Scene::on_tool_changed(FigureType type) { m_figureType = type; }

В нашей задаче, имеется тулбар (класс Tools), который генерирует сигнал tool_changed(FigureType type), который несет тип фигуры, добавляемой на графическую сцену при щелчке. Сигнал этот обрабатывается слотом on_tool_changed, приведенным на листинг 2.

Tools::Tools(QWidget *parent) : QWidget(parent) {
  QPushButton *but_1, *but_2;

  QGridLayout *grid = new QGridLayout(this);

  but_1 = new QPushButton("rect", this);
  but_2 = new QPushButton("rhomb", this);

  grid->addWidget(but_1, 0, 0, 1, 1);
  grid->addWidget(but_2, 1, 0, 1, 1);

  connect(but_1, SIGNAL(clicked()), SLOT(on_process_clicked()));
  connect(but_2, SIGNAL(clicked()), SLOT(on_switch_clicked()));
}

void Tools::on_process_clicked() { emit tool_changed(FigureType::Rect); }

void Tools::on_switch_clicked() { emit tool_changed(FigureType::Rhomb); }

Полный исходный код, как всегда, можно скачать: пример работы с графической сценой

Напомню, что пример статьи является заготовкой для будущих статей про шаблоны проектирования, поэтому я не стал усложнять код. Если Вам нужны более сложные примеры работы со сценой (с обнаружением столкновений и т.п.) — посмотрите на Colliding Mice, описанный в официальной документации.

10 thoughts on “Работа с графической сценой [Qt]

    1. admin Post author

      Пожалуйста. Вообще тут цикл статей планировался, но после второй статьи мне наскучила тема.

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

      Можно описать задачу, которую Вы сейчас решаете — может быть смогу помочь советом :).

  1. Zorik

    Здравствуйте! Скачал ваш проект, попытался скомпилировать, упала ошибка: tools.cpp:26: ошибка: ‘FigureType’ is not a class or namespace. Может подскажите, как исправить?

    1. Васильев Владимир Сергеевич Post author

      Здравствуйте. Возможно дело в том, что этот код писался давно, а вы используете более новый компилятор. Там используется что-то типа
      enum Name {Id1, Id2};
      Но дело в том, что енамы никогда не задавали пространства имен и использовать запись Name::Id1 было не корректно (Id1, Id2 объявлялись в глобальной области видимости). Однако, в тот момент нормального способа решить проблемы не было, можно было конечно поместить enum внутрь структуры (она задает пространство имен) — вы даже можете прочитать про это в статье «enum и константы«, но большинство знакомых мне программистов так не делали…

      Сейчас (в новых компиляторах) чтобы решить проблему достаточно написать enum class вместо enum:
      enum class Name {Id1, Id2};
      Такая конструкция задает не только константы, но и пространство имен для них. Прочитать про это подробнее можно в статье «Зачем нужен enum class«.

        1. Васильев Владимир Сергеевич Post author

          Достаточно странно. У меня без проблем скомпилировалось с Qt 5.8…
          Если что-то будет не получаться — пишите, я постараюсь помочь.
          Также, советую заглянуть сюда: Форум по программированию с Qt — там я публиковал несколько заметок по графической сцене, в том числе есть заготовка игры типа сокобана. Ну и код там получше и посвежее.

  2. bissckvik13

    Я первый раз на этом сайте, увидев все это понял, вот этого всего мне не хватало. Спасибо вам. И еще, я не очень понял как скачивать проект целиком. Можете объяснить по подробнее?

    1. Васильев Владимир Сергеевич Post author

      В конце статьи есть:
      >> Полный исходный код, как всегда, можно скачать: пример работы с графической сценой
      При этом часть строки после двоеточия — это ссылка на скачивание.

      По статье — когда-то я писал ее и очень старался. Сейчас я понимаю, что статья вышла сыроватой. Я очень рекомендую посмотреть другие (не только эту) статьи на этом сайте. По графической сцене, например: https://pro-prof.com/forums/topic-tag/qgraphicsscene. В качестве примера работы со сценой можно взять еще код этого проекта: https://pro-prof.com/archives/1520

Добавить комментарий