Evgenii Legotckoi
Evgenii Legotckoi12. September 2015 12:12

Qt/C++ - Lektion 017. QGraphicsScene – Arbeiten mit Grafiken in Qt

Beginnend mit diesem Artikel beginnen wir, die Qt-Grafikbibliotheken zu studieren, oder besser gesagt, QGraphicsScene Diese Klasse bietet Funktionen zum Verwalten einer großen Anzahl von 2D-Objekten. QGraphicsScene ist auf QGraphicsView eingestellt.

Beschreiben wir die Funktionalität, die in unserer Anwendung implementiert werden muss:

  • Hinzufügen einer Grafikszene zu QGraphicsView.
  • Zeichnen von zwei Objekten in der Grafikszene mit Linien, nämlich ein Rechteck und ein Quadrat.
  • Dynamische Größenänderung der Grafikszene in Abhängigkeit von der Größenänderung QGraphicsView.
  • Dynamisch wechselnde Objekte in der Grafikszene abhängig von der Größe der Grafikszene selbst.

Der Programmcode wurde in QtCreator 3.3.1 basierend auf Qt 5.4.1 geschrieben.

Projektstruktur für die Arbeit mit QGraphicsScene

Projektstruktur für die Arbeit mit QGraphicsScene Eine weitere Klasse MyGraphicView. wird der "Standard"-Projektstruktur hinzugefügt.

Tatsache ist, dass für die bequeme Arbeit mit QGraphicsScene beschlossen wurde, eine Klasse zu erstellen, die von QGraphicsView erbt und bereits darin mit einer grafischen Szene und ihren Objekten arbeitet.


mainwindow.ui

Das Erscheinungsbild der Anwendung ist das Hauptfenster und das GridLayout erstreckt sich über das gesamte Fenster. Am Ende der Lektionen sieht die Anwendung so aus:

Anwendungserscheinung mit QGraphicsScene ## mainwindow.h

Alles, was wir in dieser Datei tun, ist die Header-Datei 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

Auch diese Datei ist unauffällig. Wir initialisieren das Widget und fügen es dem GridLayout des Anwendungsfensters hinzu.

#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

Jetzt kommt der lustige Teil. Direktes Arbeiten mit der Grafikszene, in der wir zwei neue Objekte zeichnen, die neu gezeichnet werden müssen, wenn die Größe des Anwendungsfensters geändert wird, und dementsprechend das Widget selbst.

Beim dynamischen Neuzeichnen der Grafikszene sowie beim Erstellen des Hauptfensters werden die Parameter der Breite und Höhe des Fensters nicht sofort eingestellt, aber nach dem vollständigen Rendern dauert es daher, den Inhalt des Widgets zu rendern einige Zeitverzögerung, um die richtigen Werte für Breite und Höhe des Widgets zu erhalten, in dem QGraphicsScene enthalten sein wird. Dazu verwenden wir die Klasse QTimer , bei dessen Überlauf wir den SLOT nennen, in dem der Inhalt der Grafikszene gezeichnet und deren Größe angepasst wird.

#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

Um Objekte in QGraphicsScene neu zu zeichnen, müssen dieselben Objekte gelöscht werden, daher werden die Elemente dieser Objekte zur Vereinfachung der Arbeit gruppiert und es wird auch eine Methode geschrieben, um alle Elemente der Gruppe zu löschen . Dies ist praktisch, wenn Sie nur eines von mehreren Objekten neu zeichnen müssen, die aus mehreren Elementen bestehen.

#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;
       }
    }
}

Ergebnis

Das Ergebnis der Bewerbung wird im folgenden Video ab 05:27 Uhr demonstriert. Bis hierhin enthält das Video Erläuterungen zum Projekt.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

D
  • 28. März 2017 08:42

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

Evgenii Legotckoi
  • 28. März 2017 08: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. März 2017 09:09

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

Evgenii Legotckoi
  • 28. März 2017 09:17

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

D
  • 28. März 2017 10:44

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

D
  • 29. März 2017 00:52

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

Evgenii Legotckoi
  • 29. März 2017 01:11

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

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

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

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

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

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

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

D
  • 29. März 2017 01:27

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

МК
  • 27. September 2017 13:12

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

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

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

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

a
  • 10. Mai 2018 01:34

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

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

a
  • 10. Mai 2018 03:25

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

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

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

r
  • 25. September 2018 06:37

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

timer->setSingleShot(true);

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

Evgenii Legotckoi
  • 25. September 2018 06:43

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

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

G.
  • 31. Januar 2019 07:48

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

Evgenii Legotckoi
  • 31. Januar 2019 08:01

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

G.
  • 31. Januar 2019 10:38

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

МА
  • 26. Februar 2019 07:07
  • (bearbeitet)

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

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

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

Evgenii Legotckoi
  • 26. Februar 2019 07:27
  • (bearbeitet)

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

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
A
ALO1ZE19. Oktober 2024 08:19
Fb3-Dateileser auf Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15: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. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken