Evgenii Legotckoi
Evgenii LegotckoiSept. 12, 2015, 12:12 p.m.

Qt/C++ - Lesson 017. QGraphicsScene – How to work with graphics in Qt

From this article we begin to explore the Qt graphic libraries, to be exact, QGraphicsScene. This class provides the functionality to manage a large number of 2D objects. QGraphicsScene set to QGraphicsView.

We describe the features that need to be implemented in our application:

  • Adding a graphic scene in QGraphicsView.
  • Rendering the graphics objects on the two stage via lines, namely a rectangle and a square.
  • Dynamic resizing of graphic scenes, depending on changes QGraphicsView sizes.
  • Dynamically changing objects on the graphic scene, depending on the size of the graphic scene.

Project Structure for QGraphicsScene

Project Structure for QGraphicsScene By the project structure "Default" added another class MyGraphicView .

The fact is that for the convenience of working with QGraphicsScene it was decided to create a class that inherits from QGraphicsView already inside to operate graphical scene and its objects.


mainwindow.ui

The appearance of the application consists of a main window and GridLayout, spans across all of this window. As a result of the lessons of the application will look as follows:

The appearance of the application with QGraphicsScene ## mainwindow.h

Everything that we do in this file is MyGraphicsView connect the header file.

#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

This file is also unremarkable. Initialize the widget and add it GridLayout window.

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    /* Initialize a widget with graphics */
    myPicture   = new MyGraphicView();
    /* and add it to a layer */
    ui->graphicLayout->addWidget(myPicture);
}

MainWindow::~MainWindow()
{
    delete ui;
}

mygraphicview.h

And now the most interesting. Work directly with a graphic scene in which we otrisuem two new objects, which will have to be redrawn when resizing the application window, and respectively of the widget.

For dynamic redrawing the graphic scenes, as well as the creation of the main window, the parameters width and height of the window shall be established immediately, but after a full draw, so to draw the widget's contents will require some delay in time to get the correct width and widget height, which will contained QGraphicsScene. For this we use a QTimer class, overflow which will cause the slot, which will already be content rendering graphics scenes and adjusting its size.

#ifndef MYGRAPHICVIEW_H
#define MYGRAPHICVIEW_H

#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItemGroup>
#include <QTimer>


// Extend the class QGraphicsView
class MyGraphicView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyGraphicView(QWidget *parent = 0);
    ~MyGraphicView();

signals:

private slots:
    void slotAlarmTimer();  /* slot timer overflow handler there will be repainting the widget
                             * */

private:
    QGraphicsScene      *scene;     
    QGraphicsItemGroup  *group_1;   
    QGraphicsItemGroup  *group_2;   

    /* Timer for delayed rendering. 
     * The fact is that when you create a window and the widget 
     * needs some time to parent layer turned to take 
     * from him adequate width and height settings
     * */
    QTimer              *timer;

private:
    void resizeEvent(QResizeEvent *event);
    void deleteItemsFromGroup(QGraphicsItemGroup *group_1);
};

#endif // MYGRAPHICVIEW_H

mygraphicview.cpp

To redraw the objects in QGraphicsScene these same objects will need to be removed, so for the convenience of the elements of these objects will be grouped, and will write a method to remove all elements of the group. This is useful if you need to redraw the object only one of several, which consists of several elements.

#include "mygraphicview.h"

MyGraphicView::MyGraphicView(QWidget *parent)
    : QGraphicsView(parent)
{

    this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Disable scroll horizontally
    this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);   // Disable scroll vertically
    this->setAlignment(Qt::AlignCenter);                        // Make the contents of binding to the center
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);    // Stretch the widget content

    this->setMinimumHeight(100);
    this->setMinimumWidth(100);

    scene = new QGraphicsScene();   // Initialize the scene to render
    this->setScene(scene);          // Set the scene in a widget

    group_1 = new QGraphicsItemGroup(); // Initialize the first group of elements
    group_2 = new QGraphicsItemGroup(); // Initialize the elements of the second group

    scene->addItem(group_1);            // Add the first group into the scene
    scene->addItem(group_2);            // Add the second group into the scene

    timer = new QTimer();               // Initialize Timer
    timer->setSingleShot(true);

    connect(timer, SIGNAL(timeout()), this, SLOT(slotAlarmTimer()));
    timer->start(50);                   
}

MyGraphicView::~MyGraphicView()
{

}

void MyGraphicView::slotAlarmTimer()
{
    /* We remove all items from the stage if they are facing a new rendering
     * */
    this->deleteItemsFromGroup(group_1);
    this->deleteItemsFromGroup(group_2);

    int width = this->width();      
    int height = this->height();    

    /* Set the stage size to size the widget. 
     * The first coordinate - it is the top left corner, 
     * and the second - is the lower right corner
     * */
    scene->setSceneRect(0,0,width,height);

    /* Getting started drawing random pictures
     * */
    QPen penBlack(Qt::black); 
    QPen penRed(Qt::red);   

    /* Draw a black rectangle
     * */
    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));

    /* Draw a red rectangle
     * */
    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));
}

/* This method catches widget resize event
 * */
void MyGraphicView::resizeEvent(QResizeEvent *event)
{
    timer->start(50);   // As soon as we start the timer event has occurred to render
    QGraphicsView::resizeEvent(event);  //Run event the parent class
}


/* Method for removing all the elements from the group
 * */
void MyGraphicView::deleteItemsFromGroup(QGraphicsItemGroup *group)
{
    /* Loop through all the elements of the scene, 
     * and if they belong to the group, passed into the method, then delete them
     * */
    foreach( QGraphicsItem *item, scene->items(group->boundingRect())) {
       if(item->group() == group ) {
          delete item;
       }
    }
}

Result

The result of the application shown in the following video since 5:27. Up to this point in the video present explanation of the project.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

D
  • March 28, 2017, 8:42 a.m.

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

Evgenii Legotckoi
  • March 28, 2017, 8:55 a.m.

Добрый день, 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
  • March 28, 2017, 9:09 a.m.

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

Evgenii Legotckoi
  • March 28, 2017, 9:17 a.m.

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

D
  • March 28, 2017, 10:44 a.m.

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

D
  • March 29, 2017, 12:52 a.m.

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

Evgenii Legotckoi
  • March 29, 2017, 1:11 a.m.

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

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

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

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

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

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

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

D
  • March 29, 2017, 1:27 a.m.

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

МК
  • Sept. 27, 2017, 1:12 p.m.

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

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

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

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

a
  • May 10, 2018, 1:34 a.m.

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

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

a
  • May 10, 2018, 3:25 a.m.

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

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

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

r
  • Sept. 25, 2018, 6:37 a.m.

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

timer->setSingleShot(true);

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

Evgenii Legotckoi
  • Sept. 25, 2018, 6:43 a.m.

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

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

G.
  • Jan. 31, 2019, 7:48 a.m.

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

Evgenii Legotckoi
  • Jan. 31, 2019, 8:01 a.m.

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

G.
  • Jan. 31, 2019, 10:38 a.m.

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

МА
  • Feb. 26, 2019, 7:07 a.m.
  • (edited)

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

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

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

Evgenii Legotckoi
  • Feb. 26, 2019, 7:27 a.m.
  • (edited)

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
B

C++ - Test 002. Constants

  • Result:16points,
  • Rating points-10
B

C++ - Test 001. The first program and data types

  • Result:46points,
  • Rating points-6
FL

C++ - Test 006. Enumerations

  • Result:80points,
  • Rating points4
Last comments
k
kmssrFeb. 9, 2024, 5:43 a.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 9:30 p.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 7:38 p.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 19, 2023, 8:01 a.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
AC
Alexandru CodreanuJan. 19, 2024, 10:57 p.m.
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCTDec. 27, 2023, 7:57 p.m.
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
ДмитрийJan. 10, 2024, 3:18 p.m.
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii LegotckoiDec. 12, 2023, 5:48 p.m.
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Follow us in social networks