Evgenii Legotckoi
24 вересня 2015 р. 20:56

Qt/C++ - Урок 021. Миша для малювання в Qt

На цьому уроці освоїмо малювання мишею в Qt з урахуванням примітивного аналога Paint з використанням QGraphicsScene . Жодних регулювань, ні розмірів кисті, ні палітри, ні спецефектів, а просто червона лінія, яку ми малюватимемо мишею.

Завдання поставлене – вперед виконувати!

Структура проекта

Структура проекту До структури проекту входять такі файли:

  • paint.h - заголовний файл віджету, в якому розташовуватиметься графічна сцена для малювання;
  • paint.cpp - відповідно файл вихідних кодів для цього віджету;
  • paintscene.h - заголовний файл кастомізованої графічної сцени, з якої ми працюватимемо;
  • paintscene.cpp – файл вихідних кодів кастомізованої графічної сцени.

paint.ui

Форма головного вікна програми складається з самого віджету та вміщеного в нього об'єкта QGraphicsView.

фарба.ч

У цьому файлі оголошується кастомізована графічна сцена, а також таймер зі слотом для цього таймера, який слугує для коректної обробки зміни розмірів вікна програми.

  1. #ifndef PAINT_H
  2. #define PAINT_H
  3.  
  4. #include <QWidget>
  5. #include <QTimer>
  6. #include <QResizeEvent>
  7.  
  8. #include <paintscene.h>
  9.  
  10. namespace Ui {
  11. class Paint;
  12. }
  13.  
  14. class Paint : public QWidget
  15. {
  16. Q_OBJECT
  17.  
  18. public:
  19. explicit Paint(QWidget *parent = 0);
  20. ~Paint();
  21.  
  22. private:
  23. Ui::Paint *ui;
  24. QTimer *timer; /* Определяем таймер для подготовки актуальных размеров
  25. * графической сцены
  26. * */
  27. paintScene *scene; // Объявляем кастомную графическую сцену
  28.  
  29. private:
  30. /* Переопределяем событие изменения размера окна
  31. * для пересчёта размеров графической сцены
  32. * */
  33. void resizeEvent(QResizeEvent * event);
  34.  
  35. private slots:
  36. void slotTimer();
  37. };
  38.  
  39. #endif // PAINT_H

paint.cpp

У даному класі відбувається додавання кастомізованої графічної сцени в об'єкт класу QGraphicsView , фактично з метою навчання програмний код з даної частини прикладу не має особливого відношення до самого процесу малювання, але відпрацювання зміни розмірів вікна включено в цей приклад для повноти картини. Саме малювання відбувається виключно у кастомізованій графічній сцені.

  1. #include "paint.h"
  2. #include "ui_paint.h"
  3.  
  4. Paint::Paint(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::Paint)
  7. {
  8. ui->setupUi(this);
  9.  
  10. scene = new paintScene(); // Инициализируем графическую сцену
  11. ui->graphicsView->setScene(scene); // Устанавливаем графическую сцену
  12.  
  13. timer = new QTimer(); // Инициализируем таймер
  14. connect(timer, &QTimer::timeout, this, &Paint::slotTimer);
  15. timer->start(100); // Запускаем таймер
  16. }
  17.  
  18. Paint::~Paint()
  19. {
  20. delete ui;
  21. }
  22.  
  23. void Paint::slotTimer()
  24. {
  25. /* Переопределяем размеры графической сцены в зависимости
  26. * от размеров окна
  27. * */
  28. timer->stop();
  29. scene->setSceneRect(0,0, ui->graphicsView->width() - 20, ui->graphicsView->height() - 20);
  30. }
  31.  
  32. void Paint::resizeEvent(QResizeEvent *event)
  33. {
  34. timer->start(100);
  35. QWidget::resizeEvent(event);
  36. }

paintscene.h

А ось і заголовний файл винуватця цього прикладу. Малювання відбувається за допомогою ліній, за допомогою обробки події mouseMoveEvent. Для цього перевизначається функція mouseMoveEvent у якій за двома координатами, перша від минулого події та друга від поточного, будуються червоні лінії, які у результаті утворюють загальну криву. Але щоб при відпусканні кнопки миші та повторному натисканні ми могли малювати нову лінію, а не продовжувати стару, перевизначаємо функцію mousePressEvent.

У mousePressEvent відмальовується еліпс, який є стартовою точкою малювання кривою мишею. Цей метод оновлює значення першої координати першої лінії, таким чином розриваючи і відокремлюючи стару криву від нової. У відеоуроці це продемонстровано. Точка першої координати лінії зберігається в об'єкт previousPoint.

  1. #ifndef PAINTSCENE_H
  2. #define PAINTSCENE_H
  3.  
  4. #include <QGraphicsScene>
  5. #include <QGraphicsSceneMouseEvent>
  6. #include <QTimer>
  7. #include <QDebug>
  8.  
  9. class paintScene : public QGraphicsScene
  10. {
  11.  
  12. Q_OBJECT
  13.  
  14. public:
  15. explicit paintScene(QObject *parent = 0);
  16. ~paintScene();
  17.  
  18. private:
  19. QPointF previousPoint; // Координаты предыдущей точки
  20.  
  21. private:
  22. // Для рисования используем события мыши
  23. void mousePressEvent(QGraphicsSceneMouseEvent * event);
  24. void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
  25.  
  26. };
  27.  
  28. #endif // PAINTSCENE_H

paintscene.cpp

У даному файлі вся робота з малюванням відбувається у методах mouseMoveEvent та mousePressEvent. Тоді як у конструкторі класу взагалі не відбувається будь-якої ініціалізації.

  1. #include "paintscene.h"
  2.  
  3. paintScene::paintScene(QObject *parent) : QGraphicsScene(parent)
  4. {
  5.  
  6. }
  7.  
  8. paintScene::~paintScene()
  9. {
  10.  
  11. }
  12.  
  13. void paintScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
  14. {
  15. // При нажатии кнопки мыши отрисовываем эллипс
  16. addEllipse(event->scenePos().x() - 5,
  17. event->scenePos().y() - 5,
  18. 10,
  19. 10,
  20. QPen(Qt::NoPen),
  21. QBrush(Qt::red));
  22. // Сохраняем координаты точки нажатия
  23. previousPoint = event->scenePos();
  24. }
  25.  
  26. void paintScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
  27. {
  28. // Отрисовываем линии с использованием предыдущей координаты
  29. addLine(previousPoint.x(),
  30. previousPoint.y(),
  31. event->scenePos().x(),
  32. event->scenePos().y(),
  33. QPen(Qt::red,10,Qt::SolidLine,Qt::RoundCap));
  34. // Обновляем данные о предыдущей координате
  35. previousPoint = event->scenePos();
  36. }

Підсумок

В результаті Ви зможете малювати на графічній сцені червоні лінії, а далі вже розвинути ці можливості - це вже залежить від Вас.

Також ви можете ознайомитися з коментарями та демонстрацією проекту у відеоуроці за цією статтею.

Малювання в Qt Архів з вихідним кодом проекту: Qt paint

Відеоурок про малювання в Qt

Вам це подобається? Поділіться в соціальних мережах!

Р
  • 25 серпня 2017 р. 14:00

Здравствуйте! Спасибо за подробное описание данного примера, а вы бы не могли показать, что все это время происходило в main.cpp?
Потому что при использовании данного примера я получила ошибку:

321: ошибка: 'addLine' was not declared in this scope
QPen(Qt::red,10,Qt::SolidLine));
^
И я просто не могу понять, что же не так?

Evgenii Legotckoi
  • 25 серпня 2017 р. 14:20

Добрый день!
main.cpp остаётся созданным по умолчанию, там ничего интересного для Вас нет.
А ошибка говорит о том, что у Вас отсутствует необходимый заголовочный файл, вернее его подключение.

Думаю, что Вы пропустили следующий include

#include <QGraphicsScene>
Р
  • 25 серпня 2017 р. 16:04

Нет, не пропустила, есть инклюд
Полагаю, ошибка в вызове в главном окне этой сцены.
Или в чем-то еще, ибо он не заходит в эту библиотеку и соотвественно не видит addline

Evgenii Legotckoi
  • 25 серпня 2017 р. 16:25

то есть вызываете этот метод так?

void mainWindow::someMethod()
{
    graphicsScene->addLine();
}
Вообще такая ошибка говорит о том, что метод или функция не объявлены, а это значит зачастую, что пропущен необходимый инклюд.
a
  • 11 травня 2018 р. 12:28

А почему mouseMoveEvent() выполняется лишь при на нажатой кнопки мыши?

По сигнатуре функции я предположил, что метод должен вызываться сценой при простом движением мышки по сцене.
Evgenii Legotckoi
  • 11 травня 2018 р. 13:10

Чтобы включить такое поведение, необходимо вызвать метод setMouseTracking(true) у QGraphicsView, в котором расположена графическая сцена, тогда события движения мыши должны передаваться при движении курсора как с нажатыми клавишами мыши, так и без.


Paint::Paint(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Paint)
{
    ui->setupUi(this);
 
    scene = new paintScene();       
    ui->graphicsView->setScene(scene);  
    ui->graphicsView->setMouseTracking(true);
 
}
a
  • 11 травня 2018 р. 13:56

Спасибо, попробовал. Работает )

Михаиллл
  • 10 червня 2019 р. 12:43
  • (відредаговано)

Я задал в конструкторе Paint картинку на лэйбел. Скажите пожалуйста, как рисовать на mapImage.

  1. QImage mapImage("C:\\Users\\New Owner\\Downloads\\mapMain.png");
  2. ui->label->setPixmap(QPixmap::fromImage(mapImage,Qt::AutoColor));

и я унаследовал клас рисовальщик так, но в нем нет таких сигналов, какие используется в .срр:

  1. class paintScene : public QLabel
Evgenii Legotckoi
  • 10 червня 2019 р. 13:52

Вообще не понимаю, зачем вы используете QLabel для этого. Используйте графическую сцену и рисуйте на ней. QLabel был написан для отображения текста и картинок, но никак не для рисования.

И естественно, что там не будет тех сигналов и методов, потому что PaintScene нужно наследовать от QGraphicsScene. Рисовать нужно на графической сцене.

ИВ
  • 29 квітня 2020 р. 03:12

Добрый день. Извините за нюбский вопрос.
Данный проект у меня работает, что вызвало удивление. Так как я не нашел слотов, принимающих события от void mousePressEvent(QGraphicsSceneMouseEvent * event); и void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
Я пока разбираюсь со слотами, обьясните пожалуйста - выходит если сигнал (в данном случае событие) описан внутри класса, то для обьектов класса слот не нужен ?

Evgenii Legotckoi
  • 29 квітня 2020 р. 04:31

Добрый день. Это переопределённые методы из базового класса, они не являются слотами и вызываются в очереди событий внутри ядра Qt фреймворка. В обычном программировании на Qt никто не ищет откуда они конкретно вызываются. Для этого нужно копаться в исходниках Qt. То есть сигнала нет внутри класса и сигналы нужны для слотов, но в данном конкретном случае к вызову этих методов обобщённо говоря сигналы отношения не имеют.

f
  • 10 жовтня 2022 р. 04:40
  • (відредаговано)

Здравствуйте! Не понимаю в чем проблема, ui(new Ui::Paint) обозначается как недопустимый неполный тип и программа не запускается. Уже скопировал тупо только ваш код без собственных фрагментов, но все та же проблема. Помогите пожалуйста, срочно! Программирую в среде Visual Studio 2019.

f
  • 10 жовтня 2022 р. 07:18

upd: все исправил

J
  • 30 березня 2023 р. 21:57

Евгений, здравствуйте! Только начал изучение Qt и возник вопрос по 21ому уроку. После написания кода, выдаёт следующие ошибки

В чём может быть проблема?

Evgenii Legotckoi
  • 16 квітня 2023 р. 14:03

В UI файле не был добавлен QGraphicsView объект с object name graphicsView

J
  • 19 квітня 2023 р. 15:18
  • (відредаговано)

Евгений, исправил: благодарю! Всё работает!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…