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, в противном случае наткнемся на проблему с отрисовкой. Но не только, проблемы возникнут и с доставкой событий.

ссылка на оригинал статьи http://habrahabr.ru/post/182614/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *