ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 09.11.2023
Просмотров: 58
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
56
Создание собственных виджетов for(int angle=0; angle<=270; angle+=45)
{
p.save();
p.rotate(angle+135);
p.drawLine(extent*0.4, 0, extent*0.48, 0);
p.restore();
}
// Отрисовка стрелки p.rotate(m_value+135);
QPolygon polygon;
polygon << QPoint(-extent*0.05, extent*0.05)
<< QPoint(-extent*0.05, -extent*0.05)
<< QPoint(extent*0.46, 0);
p.setPen(Qt::NoPen);
p.setBrush(QColor(255,0,0,120));
p.drawPolygon(polygon);
}
Для определения реакции на нажатие клавиши на клавиатуре пе- реопределим keyPressEvent():
Пример 6.8
void CircularGauge::keyPressEvent(QKeyEvent *ev)
{
switch(ev->key())
{
case Qt::Key_Up:
case Qt::Key_Right:
setValue(value() + 1);
break;
case Qt::Key_Down:
case Qt::Key_Left:
setValue(value() - 1);
break;
case Qt::Key_PageUp:
setValue(value() + 10);
Создание собственных виджетов
57
break;
case Qt::Key_PageDown:
setValue(value() - 10);
break;
default:
QWidget::keyPressEvent(ev);
}
}
Аналогичным образом отслеживаются события мыши.
Пример 6.9
// Обрабатываем нажатие кнопки мыши void CircularGauge::mousePressEvent(QMouseEvent *ev)
{
setValueFromPos(ev->pos());
}
// Обрабатываем освобождение кнопки мыши void CircularGauge::mouseReleaseEvent(QMouseEvent *ev)
{
setValueFromPos(ev->pos());
}
// Обрабатываем перемещение указателя мыши void CircularGauge::mouseMoveEvent(QMouseEvent *ev)
{
setValueFromPos(ev->pos());
}
В некоторых случаях, необходимо предусмотреть реакцию на со- бытие, не связанное с пользовательских вводом, а происходящее по истечении определенного интервала времени. Для этого воспользуем- ся классом QTimer.
Пример 6.10
MyClass(QObject *parent) : QObject(parent)
{
58
Создание собственных виджетов
// Создаем экземпляр таймера
QTimer *timer = new QTimer(this);
// Взводим таймер на 5 секунд timer->setInterval(5000);
// Связываем сигнал таймера с обработчиком connect(timer, SIGNAL(timeout()),
this, SLOT(doSomething());
// Запускаем обратный отсчет timer->start();
}
В приведенном выше коде сигнал таймера будет приходить каж- дые пять секунд. В некоторых случаях необходимо запланировать однократное выполнение некоторого кода в определенный момент в будущем.
// Функция doSomething() будет выполнена через 1.5 с
QTimer::singleShot(1500, dest, SLOT(doSomething());
Следует также упомянуть два класса Qt для работы с изобра- жениями: QImage, оптимизированный для выполнения манипуляций с картинкой, и QPixmap, оптимизированный для быстрой отрисовки на экране.
Работа с 2D-графикой
59
§7.
Работа с 2D-графикой
7.1
Graphics View Framework
В §6 был предложен способ отрисовки двухмерных изображений на поверхности виджета переопределением метода paintEvent().
Пример 7.1
void Widget::paintEvent(QPaintEvent *)
{
// Получаем доступ к холсту
QPainter painter(this);
// Выполняем отрисовку painter.setPen(QPen(Qt::black, 2));
painter.setBrush(Qt::green);
painter.drawRect(20, 20, 60, 60);
}
Этот подход обладает рядом существенных ограничений. В част- ности, поверхность виджета всегда имеет прямоугольную форму, спо- зиционированные с помощью компоновщиков виджеты не перекры- ваются, отрисовка ведется с применением целочисленных координат пикселей.
С другой стороны, программист может быть заинтересован в со- здании композиции из нескольких, возможно перекрывающих друг друга элементов произвольной формы, произвольно позиционируе- мых с использованием вещественнозначных (субпиксельных) коорди- нат. При этом может требоваться независимое перемещение элементов друг относительно друга, а также применение графических эффектов и анимаций.
Для реализации подобных сценариев (например, при разработ- ке компьютерных игр) в Qt предусмотрен набор классов под общим названием Graphics Vew Framework, обеспечивающий работу с 2D- графикой на основе понятия сцены.
60
Работа с 2D-графикой
Graphics Vew Framework стоит на трех китах: экземпляры классов
QGraphicsItem и QGraphicsObject выступают «актерами» — составля- ющими графической композиции, объект QGraphicsScene объединяет и структурирует их, а объект QGraphicsView обеспечивает отрисовку сцены.
Пример 7.2
void Widget::setupScene()
{
// Создаем представление для отображения сцены
QGraphicsView *view = new QGraphicsView();
// Создаем сцену
QGraphicsScene *scene = new QGraphicsScene(0, 0 300, 200, this);
// Выполняем отрисовку примитивов scene->addRect(20, 20, 60, 60, QPen(Qt::red, 3),
QBrush(Qt::green));
scene->addEllipse(120, 20, 60, 60, QPen(Qt::red, 3),
QBrush(Qt::yellow));
scene->addPolygon(QPolygonF() << QPointF(220, 80)
<< QPointF(280, 80) << QPointF(250, 20),
QPen(Qt::blue, 3), QBrush(Qt::magenta));
// Связываем сцену с представлением view->setScene(scene);
view->show();
}
На рис. 15 приведен результат выполнения кода примера 7.2.
Класс QGraphicsScene представляет сцену: содержит все элементы и работает как интерфейс между ними и представлением, распределя- ет события, а также содержит методы для обхода и поиска элементов.
Каждая сцена имеет ограничивающий прямоугольник (sceneRect),
определяющий ее границы. Если ограничивающий прямоугольник не указан, в качестве него выбирается самый большой прямоугольник,
Работа с 2D-графикой
61
Рис. 15: Результат отрисовки примера 7.2
содержащий все элементы. Ограничивающий прямоугольник не обя- зательно должен начинаться в точке с координатами (0, 0).
Пример 7.3
QGraphicsScene *scene = new QGraphicsScene(this);
// Явно определяем ограничивающий прямоугольник scene->setSceneRect(-100, -100, 201, 201);
Класс QGraphicsScene имеет методы, позволяющие добавить про- стые примитивы. Поддерживаются отрезки прямых линий, эллипсы,
1 2 3
прямоугольники, многоугольники, текст, изображения. Тип каждого элемента сцены представлен отдельным классом, унаследованным от
QGraphicsItem.
Пример 7.4
QGraphicsItem *item = scene->addRect(20, 20, 60, 60,
QPen(Qt::red, 3), QBrush(Qt::green));
Виджет QGraphicsView отображает прямоугольную часть сцены
(не обязательно сцену целиком). Непосредственная привязка сцены обеспечивается методом setupScene().
Пример 7.5
QGraphicsView *view = new QGraphicsView();
QGraphicsScene *scene = setupScene();
view->setScene(scene);
QGraphicsItem.
Пример 7.4
QGraphicsItem *item = scene->addRect(20, 20, 60, 60,
QPen(Qt::red, 3), QBrush(Qt::green));
Виджет QGraphicsView отображает прямоугольную часть сцены
(не обязательно сцену целиком). Непосредственная привязка сцены обеспечивается методом setupScene().
Пример 7.5
QGraphicsView *view = new QGraphicsView();
QGraphicsScene *scene = setupScene();
view->setScene(scene);
62
Работа с 2D-графикой
По умолчанию сцена центрирована. Следует отметить, что класс
QGraphicsView наследует от класса QAbstractScrollArea, в том числе свойства horizontalScrollBarPolicy и verticalScrollBarPolicy. Таким об- разом, можно организовать прокрутку сцены внутри QGraphicsView.
И представление, и сцена, и каждый отдельный элемент имеют локальную систему координат, при этом для представления и эле- ментов определены методы для перехода от одной системы к другой
(mapFromScene / mapToScene, mapFromParent / mapToParent, а так- же mapFromItem / mapToItem). Сцена всегда использует свою систе- му координат.
7.2
Управление элементами сцены
Элементы сцены могут подчиняться другим элементам, образуя иерархии.
Пример 7.6
QGraphicsRectItem *rootItem =
new QGraphicsRectItem(-50, -50, 101, 101);
rootItem->setBrush(Qt::green);
QGraphicsEllipseItem *item;
// Следующие четыре эллипса подчинены прямоугольнику item = new QGraphicsEllipseItem(-40, -40, 30, 30, rootItem);
item->setBrush(Qt::yellow);
item = new QGraphicsEllipseItem(10, -40, 30, 30, rootItem);
item->setBrush(Qt::blue);
item = new QGraphicsEllipseItem(-40, 10, 30, 30, rootItem);
item->setBrush(Qt::red);
item = new QGraphicsEllipseItem(10, 10, 30, 30, rootItem);
item->setBrush(Qt::magenta);
scene->addItem(rootItem);
Результат выполнения кода примера 7.6 приведен на рис. 16.
Работа с 2D-графикой
63
Рис. 16: Пример иерархии элементов сцены
Элементы сцены могут быть спозиционированы относительно друг друга с помощью компоновщиков (аналогично компоновке виджетов).
Базовый класс для компоновщиков сцены — QGraphicsLayout. Про- граммисту при этом доступны:
• QGraphicsLinearLayout организует элементы в строку или стол- бец;
• QGraphicsGridLayout организует элементы в виде таблицы;
• QGraphicsAnchorLayout привязывает элементы к соседям.
При создании линейного компоновщика необходимо выбрать ори- ентацию (в строку или столбец), элементы можно добавить в компо- новщик с помощью методов addItem() и insertItem().
Пример 7.7
QGraphicsLinearLayout(Qt::Orientation orientation,
QGraphicsLayoutItem * parent = 0);
void addItem (QGraphicsLayoutItem *item);
void insertItem (int index, QGraphicsLayoutItem *item);
Пример 7.8 демонстрирует привязку левого края элемента b к пра- вому краю элемента a.
64
Работа с 2D-графикой
Пример 7.8
QGraphicsAnchor *addAnchor(
QGraphicsLayoutItem *firstItem,
Qt::AnchorPoint firstEdge
QGraphicsLayoutItem *secondItem,
Qt::AnchorPoint secondEdge);
layout->addAnchor(b, Qt::AnchorLeft, a, Qt::AnchorRight);
Элементы сцены (QGraphicsItem) может подвергаться преобразо- ваниям масштабирования, переноса, поворота, линейным искажения- ми. При этом преобразование родительского элемента применяется и к дочерним. В следующем примере строится набор подчиненных друг другу прямоугольников и выполняется преобразование поворота для каждого из них.
Пример 7.9
QGraphicsRectItem *rootItem =
new QGraphicsRectItem(...);
rootItem->setBrush(Qt::darkGray);
rootItem->setRotation(17);
QGraphicsRectItem *midItem =
new QGraphicsRectItem(..., rootItem);
midItem->setBrush(Qt::gray);
midItem->setRotation(17);
QGraphicsRectItem *topItem =
new QGraphicsRectItem(..., midItem);
topItem->setBrush(Qt::lightGray);
topItem->setRotation(17);
Результат выполнения кода представлен на рис. 17
Анимация объектов QGraphicsItem обеспечивается возможностя- ми QGraphicsItemAnimation. При этом изменения матрицы преобра- зований объекта планируются в определенные моменты времени, а
Работа с 2D-графикой
65
Рис. 17: Преобразование подчиненных элементов изменения свойств от момента к моменту вычисляются с помощью линейной интерполяции. Временные отсчеты задаются на интервале
[0.0 . . . 1.0]
, где нулевое значение соответствует состоянию объекта пе- ред стартом анимации, а единица — по завершению. Отсчет времени обеспечивается классом QTimeLine, сигнал valueChanged() которого связывается со слотом setStep() класса QGraphicsItemAnimation (см.
пример 7.10).
Пример 7.10
// Создаем элемент и таймер
QGraphicsItem *ball =
new QGraphicsEllipseItem(0, 0, 20, 20);
// Создаем таймер
QTimeLine *timer = new QTimeLine(5000);
timer->setFrameRange(0, 100);
// Создаем анимацию
QGraphicsItemAnimation *animation =
new QGraphicsItemAnimation;
animation->setItem(ball);
66
Работа с 2D-графикой animation->setTimeLine(timer);
// Выставляет временные отсчеты for (int i = 0; i < 200; ++i)
animation->setPosAt(i / 200.0, QPointF(i, i));
// Создаем и визуализируем сцену
QGraphicsScene *scene = new QGraphicsScene();
scene->setSceneRect(0, 0, 250, 250);
scene->addItem(ball);
QGraphicsView *view = new QGraphicsView(scene);
view->show();
timer->start();
В интерактивных приложениях, таких как компьютерные игры,
достаточно часто приходится выполнять проверку касания или на- ложения элементов сцены — отслеживать так называемые коллизии.
Например, может быть необходимо контролировать невозможность перемещения управляемого игроком персонажа сквозь стену или об- рабатывать столкновение с виртуальным противником.
Далее перечислим набор методов QGraphicsItem, предоставляю- щих удобный интерфейс для контроля коллизий.
Пример 7.11
// Задает контур объекта для контроля пересечения
// если не задан, ограничивающий прямоугольник virtual QPainterPath shape() const
// Проверяет, попадает ли указанная точка в контур virtual bool contains(const QPointF & point) const
// Проверяет, имеет ли место коллизия с заданным элементом virtual bool collidesWithItem(const QGraphicsItem * other,
Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const
Работа с 2D-графикой
67
// Проверяет, имеет ли место коллизия с заданным контуром virtual bool collidesWithPath(const QPainterPath & path,
Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const
// Возвращает список элементов, с которыми имеется коллизия
QList
Qt::ItemSelectionMode mode = Qt::IntersectsItemShape) const
7.3
Создание собственных элементов сцены
Программист может создавать собственные производные от клас- са QGraphicsItem классы. При этом, как минимум, необходимо пе- реопределить метод boundingRect(), определяющий ограничивающий прямоугольник и метод paint(), отвечающий за отрисовку элемента.
Пример 7.12
class BasicItem : public QGraphicsItem
{
public:
BasicItem(QGraphicsItem *parent=0);
QRectF boundingRect() const;
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget);
};
QRectF BasicItem::boundingRect() const
{
return QRectF(0, 0, 100, 100);
}
void BasicItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
painter->setPen(Qt::NoPen);
QRadialGradient gradient = radialGradient();
68
Работа с 2D-графикой painter->setBrush(gradient);
painter->drawEllipse(boundingRect());
}
Метод paint() вызывается автоматически, однако при необходимо- сти можно вызвать метод update() для перерисовки. Следует помнить,
что ограничивающий прямоугольник должен содержать все элементы
(размеры которых могут динамически изменяться).
Интерактивные возможности элементов, то есть способы, которы- ми с ними можно взаимодействовать, определяются флагами:
• ItemIsMovable —- элемент можно перемещать мышью;
• ItemIsSelectable —- элемент может быть выделен посредством методов setSelected() и QGraphicsScene::setSelectionArea();
• ItemIsFocusable —- элемент может получить фокус клавиатуры.
Пример 7.13 демонстрирует применение флага ItemIsMovable. При перемещении элемент получает события об изменении положения.
Пример 7.13
QGraphicsItem *item;
item = scene->addRect(...);
item->setFlag(QGraphicsItem::ItemIsMovable, true);
В следующем примере продемонстрировано, как можно использо- вать отрисовку для подсвечивания выделенных элементов. При выде- лении и снятии выделения элемент получает автоматический запрос на перерисовку.
Пример 7.14
item->setFlag(QGraphicsItem::ItemIsSelectable, true);
void BasicItem::paint(...)
{
if(isSelected())
Работа с 2D-графикой
69
painter->setPen(Qt::black);
else painter->setPen(Qt::NoPen);
}
Возможность перемещения и выделения элементов реализована по умолчанию. Чтобы получить полный контроль, можно напрямую переопределить обработку событий:
• setAcceptHoverEvents — наведение курсора мыши;
• setAcceptTouchEvents — касание;
• setAcceptedMouseButtons — допустимые кнопки мыши.
В следующем примере элемент отслеживает события наведения и нажатия кнопки мыши: при наведении элемент расширяется, при нажатии меняется изображение.
Пример 7.15
class InteractiveItem : public QGraphicsItem
{
public:
InteractiveItem(QGraphicsItem *parent=0);
QRectF boundingRect() const;
void paint(...);
protected:
// Переопределяемые обработчики событий void hoverEnterEvent(QGraphicsSceneHoverEvent*);
void hoverLeaveEvent(QGraphicsSceneHoverEvent*);
void mousePressEvent(QGraphicsSceneMouseEvent*);
void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
protected:
// Переменные для хранения состояния элемента bool m_pressed;
bool m_hovered;
};
InteractiveItem::InteractiveItem(QGraphicsItem *parent) :
70
Работа с 2D-графикой
QGraphicsItem(parent),
m_pressed(false), m_hovered(false)
{
// В конструкторе разрешим обработку событий наведения setAcceptHoverEvents(true);
}
QRectF InteractiveItem::boundingRect() const
{
// Ограничивающий прямоугольник меняется
// при наведении курсора if(m_hovered)
return QRectF(-50, -50, 101, 101);
else return QRectF(-30, -30, 61, 61);
}
void InteractiveItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QRadialGradient gradient;
if(m_hovered)
... // Отрисовываем элемент в состоянии наведения if(m_pressed)
... // Отрисовываем элемент в состоянии нажатия
}
// Реализация обработчиков событий void InteractiveItem::mousePressEvent(
QGraphicsSceneMouseEvent*)
{
m_pressed = true;
update();
}
void InteractiveItem::mouseReleaseEvent(
QGraphicsSceneMouseEvent*)