Сталкивались ли вы с ситуацией, когда десятки графических объектов начинают «тормозить», перекрывать друг друга и вести себя непредсказуемо? Каждый разработчик, работающий с интерактивной графикой, рано или поздно упирается в этот хаос — особенно когда требуется масштабирование, повороты, столкновения и обработка событий в реальном времени.
Эта глава постепенно раскрывает, как превратить хаотичную 2D-сцену в управляемую, масштабируемую систему. Здесь обнаружите, почему профессиональные разработчики делают ставку не на ручную перерисовку, а на архитектуру, способную без лишних усилий обрабатывать тысячи и даже миллионы элементов. Контраст «до/после» ощущается сразу: меньше кода — больше контроля, выше производительность и чище логика.
Рассматриваются QGraphicsScene, QGraphicsView и QGraphicsItem, работа с иерархиями элементов, базовые трансформации и несколько режимов определения столкновений — от быстрых до точных. В практических примерах показывается, как один и тот же объект может отображаться в нескольких представлениях и реагировать на события без ручного управления перерисовкой.
Пропуск этой главы — риск навсегда остаться в мире костылей и лишних оптимизаций.
В этой главе вы найдёте готовые к использованию примеры кода.
Самопроверка по главе
Какую роль выполняет каждый из трех основных классов графического представления в архитектуре Qt?Ответ
Правильный ответ: QGraphicsScene является моделью-контейнером для графических элементов, QGraphicsView — представлением для их визуализации с полосами прокрутки, QGraphicsItem — абстрактным базовым классом для создания элементов сцены.
Почему класс QGraphicsItem является абстрактным и какие методы необходимо реализовать при создании собственного элемента?Ответ
Правильный ответ: QGraphicsItem абстрактен, чтобы обеспечить гибкость для различных типов элементов. При наследовании обязательно нужно реализовать paint() для отрисовки и boundingRect() для определения границ элемента.
Зачем сцена разделяет область на подобласти перед отправкой сигнала changed()?Ответ
Правильный ответ: Разделение на подобласти позволяет анализировать, какие именно части были изменены, и перерисовывать только их. Это обеспечивает высокую производительность в режиме реального времени даже для миллионов элементов.
Почему отделение данных (QGraphicsScene) от представления (QGraphicsView) является преимуществом архитектуры?Ответ
Правильный ответ: Это позволяет отображать одну и ту же сцену в нескольких разных виджетах представления, обеспечивает автоматическую перерисовку при изменениях и более эффективно использует ресурсы процессора и памяти.
Как события мыши и клавиатуры передаются от представления к отдельным элементам сцены?Ответ
Правильный ответ: Представление получает события, преобразует их в события сцены с координатами сцены, затем сцена автоматически приводит координаты к локальным координатам конкретного элемента и передает событие ему.
Почему при перемещении элемента-предка его потомки перемещаются вместе с ним?Ответ
Правильный ответ: Потому что каждый элемент имеет локальную систему координат относительно предка, и любая трансформация предка автоматически применяется ко всем его потомкам.
Зачем необходимо переопределять метод boundingRect() при создании собственного элемента?Ответ
Правильный ответ: Метод boundingRect() нужен представлению для определения невидимых элементов, вычисления неперекрытых областей для перерисовки и для корректной работы механизма обнаружения столкновений.
В чем принципиальное различие между QGraphicsSimpleTextItem и QGraphicsTextItem?Ответ
Правильный ответ: QGraphicsSimpleTextItem предназначен для быстрого отображения простого текста с малым расходом памяти, а QGraphicsTextItem поддерживает форматированный текст с возможностью редактирования документа.
Как сделать элемент перемещаемым с помощью мыши и какие методы при этом вызываются?Ответ
Правильный ответ: Нужно вызвать setFlags(QGraphicsItem::ItemIsMovable). При перемещении автоматически вызываются mousePressEvent(), mouseMoveEvent() и mouseReleaseEvent(), которые можно переопределить для дополнительной обработки.
Для чего служит метод collidesWithItem() и какие режимы проверки столкновений существуют?Ответ
Правильный ответ: Метод определяет столкновение элементов. Режимы: IntersectsItemShape (пересечение форм), ContainsItemShape (полное содержание), IntersectsItemBoundingRect (быстрая проверка прямоугольников) и ContainsItemBoundingRect.
Зачем может понадобиться вызов setViewport(new QOpenGLWidget) для представления?Ответ
Правильный ответ: Это позволяет использовать OpenGL для аппаратного ускорения визуализации сцены, что значительно повышает производительность при работе со сложной графикой или большим количеством элементов.
Почему виджеты, помещенные в сцену через QGraphicsProxyWidget, сохраняют свою функциональность?Ответ
Правильный ответ: Благодаря механизму событий виджеты продолжают получать и обрабатывать пользовательские действия, при этом к ним можно применять геометрические преобразования как к обычным элементам сцены.
Когда использование графического представления предпочтительнее прямого размещения виджетов?Ответ
Правильный ответ: При работе с большим количеством графических элементов, требующих трансформаций, обнаружения столкновений, группировки и управления в режиме реального времени — графическое представление эффективнее использует ресурсы.
Какие три основных преобразования предоставляет класс QTransform и как они применяются к элементам?Ответ
Правильный ответ: QTransform предоставляет translate() (сдвиг), rotate() (поворот) и scale() (масштабирование). Преобразования применяются к элементу вызовом setTransform() с объектом QTransform.
Что произойдет, если попытаться создать несколько объектов QGraphicsScene и отобразить их в одном представлении?Ответ
Правильный ответ: В одном представлении можно отображать только одну сцену — последняя установленная через setScene() заменит предыдущую. Однако одну сцену можно отображать в нескольких представлениях одновременно.
Практические задания
Простой уровень
Интерактивная галерея фигур
Создайте приложение с графическим представлением, на котором размещены 5 разных геометрических фигур (прямоугольник, эллипс, линия, многоугольник, текст). Все элементы должны быть перемещаемыми мышью и иметь разные цвета заливки. Добавьте кнопки для увеличения и уменьшения масштаба представления.
Подсказки: Используйте методы scene.addRect(), addEllipse(), addLine(), addPolygon(), addText() для создания элементов. Для каждого элемента вызовите setFlags(QGraphicsItem::ItemIsMovable). Метод scale() представления позволяет изменять масштаб. Не забудьте задать цвета через QPen и QBrush.
Средний уровень
Детектор столкновений с визуализацией
Создайте сцену с двумя прямоугольниками разных цветов, которые можно перемещать мышью. Реализуйте обнаружение столкновений: когда прямоугольники пересекаются, они должны менять цвет на красный, а при отсутствии пересечения возвращаться к исходным цветам. Добавьте текстовую метку, показывающую статус столкновения («Столкновение» или «Нет столкновения»).
Подсказки: Создайте собственный класс элемента, унаследованный от QGraphicsRectItem. Переопределите методы mouseMoveEvent() для проверки столкновений. Используйте collidesWithItem() для определения пересечений. Храните указатели на оба элемента для взаимной проверки. Обновляйте цвета через setBrush() и текст метки через setText().
Сложный уровень
Иерархическая солнечная система с анимацией
Разработайте интерактивную модель солнечной системы: центральное солнце (круг), вокруг которого вращаются 3 планеты (меньшие круги). Каждая планета должна иметь 1-2 спутника, вращающихся вокруг неё. Используйте иерархию «предок-потомок» для связи элементов. Реализуйте анимацию вращения с помощью QTimer и методов rotate(). Добавьте виджеты управления: ползунок скорости анимации, кнопки паузы/запуска и кнопку сброса в начальное положение.
Подсказки: Создайте солнце как корневой элемент. Планеты добавьте через setParentItem(солнце), спутники — через setParentItem(планета). Для вращения используйте QTimer с интервалом ~16 мс и в слоте вызывайте setRotation() с инкрементом. Каждый элемент вращается относительно своей локальной системы координат. Виджеты добавьте через QGraphicsProxyWidget. Сохраняйте начальные позиции для функции сброса.
💬 Присоединяйтесь к обсуждению!
Освоили архитектуру графического представления? Есть вопросы о работе с QGraphicsScene или реализации обнаружения столкновений?
Поделитесь своими проектами с графическим представлением, расскажите о сложностях при работе с трансформациями или помогите другим разобраться с иерархией элементов!