Починаючи з цієї статті Ми приступаємо до вивчення графічних бібліотек Qt, а якщо точніше, QGraphicsScene. Цей клас надає функціонал управління великою кількістю 2D об'єктів. QGraphicsScene встановлюється в QGraphicsView.
Опишемо функціонал, який необхідно буде реалізувати у нашому додатку:
- Додавання графічної сцени в QGraphicsView.
- Відображення двох об'єктів на графічній сцені за допомогою ліній, а саме прямокутника та квадрата.
- Динамічна зміна розмірів графічної сцени залежно від зміни розмірів QGraphicsView.
- Динамічне зміна об'єктів на графічній сцені залежно від розмірів графічної сцени.
Програмний код був написаний QtCreator 3.3.1 на основі Qt 5.4.1.
Структура проекту для роботи з QGraphicsScene
Структура проекту для роботи з QGraphicsScene До структури проекту за промовчанням додається ще один клас MyGraphicView.
Справа в тому, що для зручності роботи з QGraphicsScene було прийнято рішення створити клас, який успадковується від QGraphicsView і вже всередині нього працювати з графічною сценою та її об'єктами.
mainwindow.ui
Зовнішній вигляд програми є головним вікном і GridLayout, розтягнутий на все це вікно. За підсумками уроків додаток буде виглядати так:
Зовнішній вигляд програми з QGraphicsScene ## mainwindow.h
Все, що робимо в цьому файлі, це підключаємо заголовний файл MyGraphicsView.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <mygraphicview.h> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; MyGraphicView *myPicture; // Наш кастомный виджет }; #endif // MAINWINDOW_H
mainwindow.cpp
Цей файл також нічим не примітний. Ініціалізуємо віджет та додаємо його GridLayout вікна програми.
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); /* Инициализируем виджет с графикой */ myPicture = new MyGraphicView(); /* и добавляем его на слой */ ui->graphicLayout->addWidget(myPicture); } MainWindow::~MainWindow() { delete ui; }
mygraphicview.h
А тепер найцікавіше. Робота безпосередньо з графічною сценою, в якій ми малюємо два нових об'єкти, які повинні будуть перемальовуватися при зміні розміру вікна програми, і відповідно віджету.
Для динамічного перемальовування графічної сцени, а також при створенні головного вікна, параметри ширини і висоти вікна встановлюються не відразу, а після повного відмальовування, тому для відображення вмісту віджету потрібна деяка затримка часу, щоб отримати правильні значення ширини та висоти віджета, в якому буде утримуватися QGraphicsScene. Для цього скористаємося класом QTimer , по переповненню якого викликатимемо СЛОТ, у якому вже відбуватиметься малювання вмісту графічної сцени та коригування її розмірів.
#ifndef MYGRAPHICVIEW_H #define MYGRAPHICVIEW_H #include <QWidget> #include <QGraphicsView> #include <QGraphicsScene> #include <QGraphicsItemGroup> #include <QTimer> // Расширяем класс QGraphicsView class MyGraphicView : public QGraphicsView { Q_OBJECT public: explicit MyGraphicView(QWidget *parent = 0); ~MyGraphicView(); signals: private slots: void slotAlarmTimer(); /* слот для обработчика переполнения таймера * в нём будет производиться перерисовка * виджета * */ private: QGraphicsScene *scene; // Объявляем сцену для отрисовки QGraphicsItemGroup *group_1; // Объявляем первую группу элементов QGraphicsItemGroup *group_2; // Объявляем вторую группу элементов /* Таймер для задержки отрисовки. * Дело в том, что при создании окна и виджета * необходимо некоторое время, чтобы родительский слой * развернулся, чтобы принимать от него адекватные параметры * ширины и высоты * */ QTimer *timer; private: /* Перегружаем событие изменения размера окна, * чтобы перехватывать его * */ void resizeEvent(QResizeEvent *event); /* Метод для удаления всех элементов * из группы элементов * */ void deleteItemsFromGroup(QGraphicsItemGroup *group_1); }; #endif // MYGRAPHICVIEW_H
mygraphicview.cpp
Для перемальовування об'єктів у QGraphicsScene ці об'єкти необхідно буде видаляти, тому для зручності роботи елементи цих об'єктів буду згруповані, а також буде написаний метод для видалення всіх елементів групи. Це зручно в тому випадку, якщо Вам необхідно перемалювати лише один об'єкт із кількох, який складається з низки елементів.
#include "mygraphicview.h" MyGraphicView::MyGraphicView(QWidget *parent) : QGraphicsView(parent) { /* Немного поднастроим отображение виджета и его содержимого */ this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Отключим скроллбар по горизонтали this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Отключим скроллбар по вертикали this->setAlignment(Qt::AlignCenter); // Делаем привязку содержимого к центру this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Растягиваем содержимое по виджету /* Также зададим минимальные размеры виджета * */ this->setMinimumHeight(100); this->setMinimumWidth(100); scene = new QGraphicsScene(); // Инициализируем сцену для отрисовки this->setScene(scene); // Устанавливаем сцену в виджет group_1 = new QGraphicsItemGroup(); // Инициализируем первую группу элементов group_2 = new QGraphicsItemGroup(); // Инициализируем вторую группу элементов scene->addItem(group_1); // Добавляем первую группу в сцену scene->addItem(group_2); // Добавляем вторую группу в сцену timer = new QTimer(); // Инициализируем Таймер timer->setSingleShot(true); // Подключаем СЛОТ для отрисовки к таймеру connect(timer, SIGNAL(timeout()), this, SLOT(slotAlarmTimer())); timer->start(50); // Стартуем таймер на 50 миллисекунд } MyGraphicView::~MyGraphicView() { } void MyGraphicView::slotAlarmTimer() { /* Удаляем все элементы со сцены, * если они есть перед новой отрисовкой * */ this->deleteItemsFromGroup(group_1); this->deleteItemsFromGroup(group_2); int width = this->width(); // определяем ширину нашего виджета int height = this->height(); // определяем высоту нашего виджета /* Устанавливаем размер сцены по размеру виджета * Первая координата - это левый верхний угол, * а Вторая - это правый нижний угол * */ scene->setSceneRect(0,0,width,height); /* Приступаем к отрисовке произвольной картинки * */ QPen penBlack(Qt::black); // Задаём чёрную кисть QPen penRed(Qt::red); // Задаём красную кисть /* Нарисуем черный прямоугольник * */ group_1->addToGroup(scene->addLine(20,20, width - 20, 20, penBlack)); group_1->addToGroup(scene->addLine(width - 20, 20, width - 20, height -20, penBlack)); group_1->addToGroup(scene->addLine(width - 20, height -20, 20, height -20, penBlack)); group_1->addToGroup(scene->addLine(20, height -20, 20, 20, penBlack)); /* Нарисуем красный квадрат * */ int sideOfSquare = (height > width) ? (width - 60) : (height - 60); int centerOfWidget_X = width/2; int centerOfWidget_Y = height/2; group_2->addToGroup(scene->addLine(centerOfWidget_X - (sideOfSquare/2), centerOfWidget_Y - (sideOfSquare/2), centerOfWidget_X + (sideOfSquare/2), centerOfWidget_Y - (sideOfSquare/2), penRed)); group_2->addToGroup(scene->addLine(centerOfWidget_X + (sideOfSquare/2), centerOfWidget_Y - (sideOfSquare/2), centerOfWidget_X + (sideOfSquare/2), centerOfWidget_Y + (sideOfSquare/2), penRed)); group_2->addToGroup(scene->addLine(centerOfWidget_X + (sideOfSquare/2), centerOfWidget_Y + (sideOfSquare/2), centerOfWidget_X - (sideOfSquare/2), centerOfWidget_Y + (sideOfSquare/2), penRed)); group_2->addToGroup(scene->addLine(centerOfWidget_X - (sideOfSquare/2), centerOfWidget_Y + (sideOfSquare/2), centerOfWidget_X - (sideOfSquare/2), centerOfWidget_Y - (sideOfSquare/2), penRed)); } /* Этим методом перехватываем событие изменения размера виджет * */ void MyGraphicView::resizeEvent(QResizeEvent *event) { timer->start(50); // Как только событие произошло стартуем таймер для отрисовки QGraphicsView::resizeEvent(event); // Запускаем событие родителького класса } /* Метод для удаления всех элементов из группы * */ void MyGraphicView::deleteItemsFromGroup(QGraphicsItemGroup *group) { /* Перебираем все элементы сцены, и если они принадлежат группе, * переданной в метод, то удаляем их * */ foreach( QGraphicsItem *item, scene->items(group->boundingRect())) { if(item->group() == group ) { delete item; } } }
Підсумок
Результат роботи програми продемонстровано на наступному відео з 5:27. До цього моменту у відео присутні пояснення проекту.
Добрый день, Евгений! Спасибо за ваши уроки и за ваш исходный код к ним. Только небольшой вопрос - а где исходный код главной функции main()? без нее ничего не запускается. Напишите, пожалуйста, ответ, ну или, исходный код функции main(). Заранее спасибо.
Добрый день, Doug.
Я обычно прикладываю код функции main тогда, когда она отличается от файла в проекте созданном по умолчанию.
Например здесь, используется класс mainwindow для окна приложения. Он будет выглядеть так, если создадите в Qt Creator приложение на Qt, у которого базовым классом будет класс QMainWindow.
Спасибо, Евгений!! А файл main.cpp во всех других уроках останется таким же? ( я имею ввиду уроки, где вы рассказываете о работе с 2d и QGraphicsScene ? ... и, если есть какие-то изменения, не могли бы вы тоже добавить в другие уроки файл main.cpp c главной функцией main()? Еще раз спасибо.
Там может быть разница в том, что класс окна может быть Widget вместо MainWindow . Но это должно быть очевидно из класса главного окна приложения. Поэтому просто вместо MainWindow пишите Widget . Код этой функции приводится в том случае, если он отличается от кода по умолчанию при создании нового проекта.
Понял. Хорошо.Спасибо. Удачи Вам!
Добрый день! Можно такой вопрос - как организовать перемещение / поворот фигур ПРАВОЙ кнопкой мыши? И как сделать, так чтобы фигуру в фигуре (например, квадрат в треугольнике), можно было перемещать / вращать как единое целое (монолит)? Спасибо.
Что касается перемещения, то можно организовать либо как сделано в этой статье
Либо можно воспользоваться флагами:
Флаги применяются непосредственно к QGraphicsItem . Можно сразу в конструкторе настраивать.
Что касается поворота объектов, то встроенными средствами Qt это не реализуется, в том смысле, что там нет волшебного флага наподобие ItemIsMovable , который просто возьмёт и включит данную возможность. Я как-то реализовывал подобное в одном тестовом задании, но нужно искать исходники. Там много строчек написано. Поэтому ответить сходу затрудняюсь.
P/S/ Этот ваш последний вопрос имеет косвенное отношение к статье, поэтому не могли бы Вы задавать такие вопросы сразу на форуме ? Чтобы была уже отдельная ветка обсуждения.
Спасибо, Евгений за Ваш такой оперативный ответ. Не знал, что есть форум. Спасибо за ссылку.
Благодарю за уроки!
Сделал вроде бы всё как написано, но при изменении размеров окна не происходит изменения размеров виджета, и соответственно геометрических фигур. В чём причина, подскажите.
Кстати, можно сделать подключение сигнала к слоту в новом синтаксисе:
Нашёл ответ на просторах интернета:
В Дизайнере ПКМ по MainWindow: Компоновка - Скомпоновать по сетке.
Нужно писать о таких простых, но совсем не очевидных для новичка вещах.
ЗЫ: увидел у вас 24-ый урок про сигналы и слоты, так что вторая часть предыдущего комментария также снимается ))
Добрый день!
О всех очевидных вещах не напишешь. Молодца, что нашли решение самостоятельно.
Евгений, добрый день!
Взял код отсюда. Приделал graphicLayout. Всё, заработало, спасибо.
Я уже не помню какая там проблема была.
В статье написано, что таймер сработает один раз. Но это не так. Было бы хорошо добавить
После инициализации таймера.
Прямо так не написано.
Хотя соглашусь, что в качестве улучшения вызов данного метода здесь к месту.
Где вызывается наш метод resizeEvent() ?
Он вызывается в очереди событий в рамках обработки событий взаимодействия с окном. Это всё крутится под капотом Qt. У виджетов есть методы Event, они вызываются в зависимости от срабатывания событий. Если хотите знать, где конкретно, то лезьте в исходники Qt.
Спасибо большое! Разобрался.
В файле "mainwindow.cpp" есть строчка:
ui->graphicLayout->addWidget(myPicture);
Возникает вопрос: что такое "graphicLayout"? Где/Как его найти/создать?
upd
Уже разобрался. Т.к. работаю с C++ всего пару часов, не сразу понял, что это просто объект QGridLayout.
Объект был создан через графический дизайнер. ui создаётся в графическом дизайнере.