Понравилось? Поделись с друзьями:

16 января 2015 г.

Работа с Graphics View Framework в Qt

    В данный момент работаю с SVG-графикой в Qt и чтобы не забыть того, что узнал из работы с Graphics View Framework хочу написать данный цикл статей, пока эти знания у меня из головы не вылетели. Возможно кому-то кроме меня он окажется полезен, и я буду только рад этому. Сразу хочу сказать что, где-то могут быть ошибки и неточности. Я не профи в этом и только учусь.
    Не хочу расписывать подробно "что это такое", это уже давно сделали до меня. Приведу лишь одну схему, поясняющую модель взаимодействия объектов:

    И по отдельности хочу рассказать про каждый класс, и те нюансы с которыми мне пришлось столкнуться.

Краткое описание каждого из них. 

    QGraphicsView - класс представления, он отвечает за отрисовку всех элементов, и именно его нужно добавлять внутрь ваших виджетов. В QtDesigner имеется. Наследуется от QAbstractScrollArea, а это означает, что, если отображение не помещается внутрь заданной области, то будут добавлены полосы прокрутки.
    QGraphicsScene - класс сцены, хранит в себе элементы, и это самое главное и понятное. Может быть привязан к нескольким сценам.
    QGraphicsItem - класс элемента, все элементы сцены должны быть потомками от этого класса. Сам класса QGraphicsItem является абстрактным, от него нужно создать собственный класс и переопределить в нем хотя бы методы paint() и boundingRect(). Но обычно этого не требуется ведь есть много уже готовых классов, например, прямоугольники (QGraphicsRectItem), эллипсы (QGraphicsEllipseItem) и текстовые элементы (QGraphicsTextItem). Проще отнаследоваться от них переопределив нужные методы. Да, кстати, данный класс не наследуется от QObject, а это значит, что нем не реализован сигнально-слотовый механизм. Но есть прекрасный класс QGraphicsObject, в котором это есть, но использовав его вы жертвуете производительностью.
   

Теперь об основах работы с ними.

    Допустим, что объект QGraphicsView уже добавлен в виджет, и мы хотим его связать со сценой:
QGraphicsScene *scene = new QGraphicsScene;
graphicsView->setScene(scene);
    или можно так
QGraphicsScene scene(QRectF(-100, -100, 300, 300));
graphicsView->setScene(&scene);
Чтобы добавить элемент на сцену, например линию:
QGraphicsLineItem *pLineItem =
scene.addLine(
   QLineF(-10, -10, -80, -80),
   QPen(Qt::red, 2)
);
    Дальше обращаясь к объекту pLineItem мы можем его менять, прямо на сцене.
    Поподробней про элементы сцены. У них есть собственные локальные координаты и координаты сцены (как минимум, есть правда еще, но пока что забудем о них).
    Под локальными понимается координаты внутри данного объекта. Т.е. его начало координат будет в левом верхнем углу. Для преобразования из одной системы в другую - используются методы элемента mapTo...() и mapFrom...()
    Например, нужно узнать середину квадрата в координатах сцены внутри класса предка QGraphicsRectItem. Объект этого класса назовем myRect.
void showMiddlePoint()
{
    qDebug() <<
mapToScene(QPointF(boundingRect().width()/2,             boundingRect().height()/2));
}
    Удобно вызывать внутри этого метода сигнал, который связать со слотом отрисовки этой точки. Дополним.
void showMiddlePoint()
{
    qDebug() << mapToScene(QPointF(width()/2, height()/2));
    emit needDrawPoint(
             mapToScene(QPointF(width()/2, height()/2)                        );
}
connect( myRect, SIGNAL(needDrawPoint(QPointF())),
       this, SLOT(drawPoint(QPointF())));
void drawPoint(QPointF p)
{
    scene->addEllipse(
       QRectF(p.x(),p.y(),1,1),
       QPen(Qt::NoPen),
       QBrush(Qt::black)
    );
}
    Так же удобно отображать клик мыши на сцене создав предка от  QGraphicsScene и переопределив метод mousePressEvent().
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    emit clicked(event->scenePos());    event->accept();
}
connect(scene, SIGNAL(clicked(QPointF())),
        this, SLOT(drawPoint(QPointF())));
    Теперь про трансформацию элементов.
    Внутри каждого класса наследуемого от QGraphicsItem имеются методы transform() и setTransform(). Метод transform() возвращает объект класса QTransform, который позволяет выполнять различные преобразования над элементом (масштабирование, перемещение, поворот). Факт в том, что преобразованию подвергается локальная система координат. Т.е. все его характеристики внутри нее, будь то длина, ширина или конечные точки - остаются без изменений. Еще одна особенность в том, поворот (метод QTransform::rotate()) осуществляется вокруг локальной точки точки 0;0, причем вне зависимости от установки transformOriginPoint() у элемента. Если хочется вращать вокруг определенной точки нужно задать эту точку методом QGraphicsItem::setTransformOriginPoint() и затем вызвать QGraphicsItem::setRotation(). Пример:
void rotateAroundMiddle(qreal angle)
{
    QPointF p(boundingRect().width()/2,
              boundingRect().height()/2);
    setTransformOriginPoint(p);
    setRotation(angle);
}
    Немного про "родитей" у элементов.
    Для каждого элемента можно задать элемент, который будет его "родителем", и тогда все действия трансформации (поворот, масштабирование и пр.), которые будут производиться над основным элементом произойдут и над "дочерним" элементом, но только в системе координат "родителя". Весьма запутанная система получается. Задать "родителя" можно методом setParentItem().

    Теперь немного про флаги у элементов.
    Устанавливаются они через метод setFlag() или setFlags(). Первый - устанвливает только один флаг, второй метод - одновременно несколько, разделенные "|". Например:
item1->setFlag(QGraphicsItem::ItemStacksBehindParent);
item2->setFlags(QGraphicsItem::ItemStacksBehindParent
                | QGraphicsItem::ItemIsMovable);
item3->setFlags(QGraphicsItem::ItemStacksBehindParent
                | QGraphicsItem::ItemSendsGeometryChanges
                | QGraphicsItem::ItemIsMovable);
    Подробнее о представленных флагах. Флаг ItemStacksBehindParent - делает так чтобы родитель был позади (на заднем плане) потомка, флаг ItemIsMovable - позволяет перемещать элементы мышью, флаг ItemSendsGeometryChanges - позволяет отхватывать события методом QGraphicsItem::itemChange().
    Теперь о работе с SVG в Qt.
    Для простой работы достаточно класса QSvgWidget
QSvgWidget svg(":/motion.svg");
svg.show();
    Но мне этого было мало и я создал свой класс-потомок QGraphicsSvgItem. Объекты этого класса можно добавлять на сцену, и работать с ними как QGraphicsItem. Прежде всего их нужно правильно создать и связать с нужным объектом SVG. Я делаю это так:
QSvgRenderer *renderer = new
    QSvgRenderer(QString(":/motion.svg"));
QGraphicsSvgItem *item = new QGraphicsSvgItem();
item->setSharedRenderer(renderer);
item->setElementId("rect1");
item->setPos(renderer->boundsOnElement("rect1").topLeft());
scene->addItem(item);

    У каждого объекта в SVG свой ID по которому можно к нему обратиться. Рендерер же создается один раз для SVG-картинки. А с помощью метода setPos() изменяется положение элемента на сцене в соответствии с картинкой, иначе элемент будет располагаться в точке 0;0 на сцене.
    Пока что это всё, будет что-то новое - добавлю.

Используемый материал:
Qt 5. Graphics View Framework
Qt 4. Graphics View Framework
Каркас Графического представления
Красота и мощь Qt Graphics View Framework на примере
Программирование графического интерфейса с помощью Qt 4, Часть 4

При копировании статьи просьба указывать источник и автора.
С уважением, GRomR1.

2 комментария:

  1. какое же убогое форматирование текста

    ОтветитьУдалить
  2. пунктуация и орфография тоже не на высшем уровне...

    ОтветитьУдалить