Qt Graphics Framework — темная сторона. Часть 1

в 13:43, , рубрики: c++, qt, Qt Software, Программирование, темные стороны документации, метки: ,

В первой статье я рассказывал как мог о достоинствах фреймворка. Сегодня я попытаюсь рассказать о его меной стороне, плохо освещенной в документации.

Дело №1

Мы хотим изменять размер сцены и объектов в ней согласно размеру отображаемого окна. В доке сказано:«QGraphicsView takes ownership of the viewport widget». Ну что-ж, создадим простейшим проект и напишем следующее:

MainWindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QGraphicsView>


class MainWindow : public QWidget
{
    Q_OBJECT
    
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
private:
    bool eventFilter(QObject *, QEvent *);
private:
    QGraphicsScene *m_scene;
    QGraphicsEllipseItem *m_elipse;
    QGraphicsView * graphicsView;
};

#endif // MAINWINDOW_H

MainWindow.cpp:

#include "MainWindow.h"
#include <QGridLayout>
#include <QEvent>
#include <QResizeEvent>

MainWindow::MainWindow(QWidget *parent) :
    QWidget(parent)
{
    setLayout(new QGridLayout());
    graphicsView = new QGraphicsView();
        layout()->addWidget(graphicsView);
        graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_scene = new QGraphicsScene();
    m_elipse = new QGraphicsEllipseItem();
    m_scene->addItem(m_elipse);
    m_scene->setSceneRect(m_elipse->boundingRect());
    graphicsView->setScene(m_scene);
    graphicsView->installEventFilter(this);
}

MainWindow::~MainWindow()
{
}

bool MainWindow::eventFilter(QObject *, QEvent *event)
{
    if(event->type() == QEvent::Resize )
    {
        QResizeEvent *res = reinterpret_cast<QResizeEvent*>(event);
        m_elipse->setRect(0, 0, res->size().width(), res->size().height());
        return true;
    }
    return false;
}

Ожидается, что меняя размера виджета, мы будем менять размер эллипса, но он по-прежнему будет виден целиком, т.к. изменится размер и отображаемой области. Компилируем, запускаем и убеждаемся, что как надо не работает: можно уменьшить первоначальный размер, но вот при превышении эллипс обрезается.

Тогда меняем

    graphicsView->installEventFilter(this);

на

    graphicsView->viewport()->installEventFilter(this);

Компилируем, запускаем — все работает. Теперь попробуем установить фильтр в сцену. Компилируем, запускаем и ничего не видим, вообще.

Дело №2

Хотим отслеживать положение мыши, при ее перемещении по сцене. Для это модифицируем наш тестовый и класс, следующим образом:

bool MainWindow::eventFilter(QObject *, QEvent *event)
{
    if(event->type() ==QEvent::MouseMove)
    {
        qDebug()<<event;
        return true;
    }
    return false;
}

В конструктор дописываем:

graphicsView->setMouseTracking(true);

Ну и фильтр инсталлируем в graphicsView.
Компилируем, запускаем, убеждаемся что не работает. Инсталлируем фильтр в viewport и все снова работает.
Теперь немного модифицируем:

  if(event->type() ==QEvent::MouseMove)

заменим на

  if(event->type() ==QEvent::GraphicsSceneMouseMove)

И инсталлируем фильтр в сцену. И в отличии от прошлого раза мы обнаружим, что перемещение мыши отслеживается.

Выводы следствия

Как это было бы не странно с точки зрения новичка, но Graphics Framework действительно следует логике: отображение отдельно, а композиция отдельно. И потому событие об изменении размера не передается в сцену. И graphicsView действительно берет шефство над viewport. В этом можно убедится если в деле номер №1 заменить фильтр на:

  if(event->type() == QEvent::Resize )
    {
        QResizeEvent *res = reinterpret_cast<QResizeEvent*>(event);
        m_elipse->setRect(0, 0, res->size().width(), res->size().height());
    }
    return false;

Т.е. убрать фильтрацию события, то мы увидим ожидаемое поведение. Т.е. событие отфильтровалось и дальше не было передано в viewport. А вот адекватное поведение при уменьшении вызвано внутренней логикой Qt или спонтанными событиями, как их именуют в коде.

Вернемся к передачи событий в сцену. Этот процесс вообще не описан, а здесь есть важный момент: перед тем как отправится в сцену graphicsView преобразуют событие в соответствующий QGRaphisSceneEvent, но происходит это уже в специализированных методах, собственно поэтому в доке и рекомендует переопределять их, а не точку входа (event или viewportEvent).

Еще раз обращу внимания на последний момент: логика фреймворка такова, что все внешние события подготавливаются для работы со сценой. Т.е. если у вас есть ваш QEvent, который должен быть передан сцене, то лучший способ, не нарушающий логику Qt — это создать аналог кастомного события, отнаследованный от QGRaphicsSceneEvent, написать метод преобразование и посылки преобразованного в сцену, который будет вызываться в классе, отнаследованном от QGraphicsView, ну и посылать QEvent в этот-самый модифицированный QGraphicsView.

Осталось рассмотреть последний этап: доставка события к Item-ам. Здесь картина аналогичная с доставкой до сцены, а именно: после того как событие пришло определяется какую специализированную функцию надо задействовать; в этой специализированной функции определяются координаты события, с помощью методов itemAt() определяется каким Item-а доставлять событие, затем событие подготавливается к доставке в Item и только после этого отправляется в него.

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

Автор: DancingOnWater

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js