Evgenii Legotckoi
Evgenii Legotckoi12 сентября 2015 г. 12:12

Qt/C++ - Урок 017. QGraphicsScene или как работать с графикой в Qt

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

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

D
  • 28 марта 2017 г. 8:42

Добрый день, Евгений! Спасибо за ваши уроки и за ваш исходный код к ним. Только небольшой вопрос - а где исходный код главной функции main()? без нее ничего не запускается. Напишите, пожалуйста, ответ, ну или, исходный код функции main(). Заранее спасибо.

Evgenii Legotckoi
  • 28 марта 2017 г. 8:55

Добрый день, Doug.
Я обычно прикладываю код функции main тогда, когда она отличается от файла в проекте созданном по умолчанию.

Например здесь, используется класс mainwindow для окна приложения. Он будет выглядеть так, если создадите в Qt Creator приложение на Qt, у которого базовым классом будет класс QMainWindow. 

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MainWindow w;
    w.show();

    return a.exec();
}
D
  • 28 марта 2017 г. 9:09

Спасибо, Евгений!! А файл main.cpp во всех других уроках останется таким же? ( я имею ввиду уроки, где вы рассказываете о работе с 2d и QGraphicsScene ? ... и, если есть какие-то изменения, не могли бы вы тоже добавить в другие уроки файл main.cpp c главной функцией main()? Еще раз спасибо.

Evgenii Legotckoi
  • 28 марта 2017 г. 9:17

Там может быть разница в том, что класс окна может быть Widget вместо MainWindow . Но это должно быть очевидно из класса главного окна приложения. Поэтому просто вместо MainWindow пишите Widget . Код этой функции приводится в том случае, если он отличается от кода по умолчанию при создании нового проекта.

D
  • 28 марта 2017 г. 10:44

Понял. Хорошо.Спасибо. Удачи Вам!

D
  • 29 марта 2017 г. 0:52

Добрый день! Можно такой вопрос - как организовать перемещение / поворот фигур ПРАВОЙ кнопкой мыши? И как сделать, так чтобы фигуру в фигуре (например, квадрат в треугольнике), можно было перемещать / вращать как единое целое (монолит)? Спасибо.

Evgenii Legotckoi
  • 29 марта 2017 г. 1:11

Что касается перемещения, то можно организовать либо как сделано в этой статье

Либо можно воспользоваться флагами:

  1. ItemIsMovable - включает возможность перемещения
  2. ItemIsSelectable - включает возможность выделения объектов

Флаги применяются непосредственно к QGraphicsItem . Можно сразу в конструкторе настраивать.

setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsSelectable);

Что касается поворота объектов, то встроенными средствами Qt это не реализуется, в том смысле, что там нет волшебного флага наподобие ItemIsMovable , который просто возьмёт и включит данную возможность. Я как-то реализовывал подобное в одном тестовом задании, но нужно искать исходники. Там много строчек написано. Поэтому ответить сходу затрудняюсь.

P/S/ Этот ваш последний вопрос имеет косвенное отношение к статье, поэтому не могли бы Вы задавать такие вопросы сразу на форуме ? Чтобы была уже отдельная ветка обсуждения.

D
  • 29 марта 2017 г. 1:27

Спасибо, Евгений за Ваш такой оперативный ответ. Не знал, что есть форум. Спасибо за ссылку.

МК
  • 27 сентября 2017 г. 13:12

Благодарю за уроки!
Сделал вроде бы всё как написано, но при изменении размеров окна не происходит изменения размеров виджета, и соответственно геометрических фигур. В чём причина, подскажите.
Кстати, можно сделать подключение сигнала к слоту в новом синтаксисе:

// Подключаем СЛОТ для отрисовки к таймеру
//connect(timer, SIGNAL(timeout()), this, SLOT(slotAlarmTimer()));
connect(timer, &QTimer::timeout, this, &MyGraphicView::slotAlarmTimer);

Нашёл ответ на просторах интернета:
В Дизайнере ПКМ по MainWindow: Компоновка - Скомпоновать по сетке.
Нужно писать о таких простых, но совсем не очевидных для новичка вещах.
ЗЫ: увидел у вас 24-ый урок про сигналы и слоты, так что вторая часть предыдущего комментария также снимается ))

Evgenii Legotckoi
  • 27 сентября 2017 г. 13:39

Добрый день!
О всех очевидных вещах не напишешь. Молодца, что нашли решение самостоятельно.

a
  • 10 мая 2018 г. 1:34

Евгений, добрый день!

А где можно исходники найти?

a
  • 10 мая 2018 г. 3:25

Взял код отсюда. Приделал graphicLayout. Всё, заработало, спасибо.

Правда, так и не понятно зачем этот таймер, зачем эта задержка при отрисовки. Почему бы сразу мгновенно не отрисовывать?
Evgenii Legotckoi
  • 10 мая 2018 г. 3:47

Я уже не помню какая там проблема была.

r
  • 25 сентября 2018 г. 6:37

В статье написано, что таймер сработает один раз. Но это не так. Было бы хорошо добавить

timer->setSingleShot(true);

После инициализации таймера.

Evgenii Legotckoi
  • 25 сентября 2018 г. 6:43

Прямо так не написано.

Хотя соглашусь, что в качестве улучшения вызов данного метода здесь к месту.

G.
  • 31 января 2019 г. 7:48

Где вызывается наш метод resizeEvent() ?

Evgenii Legotckoi
  • 31 января 2019 г. 8:01

Он вызывается в очереди событий в рамках обработки событий взаимодействия с окном. Это всё крутится под капотом Qt. У виджетов есть методы Event, они вызываются в зависимости от срабатывания событий. Если хотите знать, где конкретно, то лезьте в исходники Qt.

G.
  • 31 января 2019 г. 10:38

Спасибо большое! Разобрался.

МА
  • 26 февраля 2019 г. 7:07
  • (ред.)

В файле "mainwindow.cpp" есть строчка:
ui->graphicLayout->addWidget(myPicture);

Возникает вопрос: что такое "graphicLayout"? Где/Как его найти/создать?

upd
Уже разобрался. Т.к. работаю с C++ всего пару часов, не сразу понял, что это просто объект QGridLayout.

Evgenii Legotckoi
  • 26 февраля 2019 г. 7:27
  • (ред.)

Объект был создан через графический дизайнер. ui создаётся в графическом дизайнере.

Комментарии

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

Qt - Тест 001. Сигналы и слоты

  • Результат:68баллов,
  • Очки рейтинга-1
ЛС

C++ - Тест 001. Первая программа и типы данных

  • Результат:53баллов,
  • Очки рейтинга-4
АА

C++ - Тест 001. Первая программа и типы данных

  • Результат:60баллов,
  • Очки рейтинга-1
Последние комментарии
ИМ
Игорь Максимов5 октября 2024 г. 17:51
Django - Урок 064. Как написать расширение для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55 июля 2024 г. 21:02
QML - Урок 016. База данных SQLite и работа с ней в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr9 февраля 2024 г. 5:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 12:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 21:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Сейчас обсуждают на форуме
K
Keithfap13 октября 2024 г. 19:24
добавить qlineseries в функции North Symbol by Bubnov Ltd https://seven-elephants.com/en/categories/penthouse/ Искеле – жемчужина острова! Все факторы говорят про большой инвестиционный потенциал данного района как для …
ИМ
Игорь Максимов3 октября 2024 г. 14:05
Реализация навигации по разделам Спасибо Евгений!
JW
Jhon Wick2 октября 2024 г. 1:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27 сентября 2024 г. 19:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22 июля 2024 г. 14:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Следите за нами в социальных сетях