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

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

    Дело №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, в противном случае наткнемся на проблему с отрисовкой. Но не только, проблемы возникнут и с доставкой событий.
    Метки:
    • +39
    • 23,8k
    • 5
    Поделиться публикацией
    Похожие публикации
    Комментарии 5
    • +1
      То, что клавиатурные и мышиные события идут через viewport() — это в Qt применяется повсеместно, я, поработав с ListView и прочими TreeView это как-то быстро понял) так что меня сей факт в статье не показался «темной стороной» такой уж.
      А вот про обмен событиями — интересно, спасибо, такие нюансы действительно могут сократить несколько часов времени.
      Пишите дальше.
      • +1
        Не все, если я верно помню, приграничная область viewport-а принадлежит QAbstractArea и ее наследникам и события мыши в этой области не попадают в viewport.
        • +1
          Любопытный факт. Вы работали в Qt4 и Qt5 с eventFilter? я заметил что после компиляции под пятеркой у меня в фильтр стали сыпаться события мыши со скроллбара, которых не было в 4ке.
          • +2
            Если честно, то сие я заметил уже будучи на пятерке, но код QAbstractScrollArea что в пятерке, что в четверке структурно похож (жаль, сейчас нет под рукой исходников четверки, чтобы проверить досконально)
            • 0
              Не, Вы меня наверное не так поняли, то что Вы сказали — в целом верно. Возможно, то что я наблюдал вообще был какой-то баг- хз. Я не настаиваю -просто делюсь наблюдениями (вдруг кто прочтет в комментах, ему полезно будет.)

              Мне вообще Graphics Framework концептуально нравится, где-то читал что он сам по себе хорошо оптимизирован.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.