Russian Qt Forum

Qt => Уроки и статьи => Тема начата: ieroglif от Май 16, 2010, 16:37



Название: Урок: QPainter для начинающих
Отправлено: ieroglif от Май 16, 2010, 16:37
Всвязи с большим количеством однотипных вопросов от начинающих пользоваться Qpainter-ом, и людей, задающихся вопросом "Как нарисовать что-то своё на виджете", я распишу базовые понятия рисования.

Итак, начнём с главного правила - рисовать на виджете можно переопределив его метод paintEvent(QPaintEvent *).

Причина переопределения метода перерисовки в том, что код, выполняющийся в этой функции, будет запущен каждый раз, когда надо перерисовать внешний вид виджета. Перерисовка, на самом деле, требуется на так уж часто - когда изменяется геометрия виджета (например мы растянули окно, и вместе с ним растянулся наш виджет), когда виджет перекрывается/перестаёт_перекрываться/изменилось_перекрывание другими виджетами - вот основные причины для автоматического вызова перерисовки.

Кстати говоря, автоматического вызова перерисовки не всегда достаточно, но мы можем сами вызвать перерисовку, вызвав метод update() для виджета.

Подводя итог, можно сказать следующее: если мы хотим нарисовать что-то на виджете - нам надо создать свой собственный виджет, и переопределить его метод paintEvent(QPaintEvent *). После этого, мы создадим объект нашего класса.

1. Создаём наш виджет, который позволяет рисовать на себе:

qpaintwidget.h
Код
C++ (Qt)
#ifndef QPAINTWIDGET_H
#define QPAINTWIDGET_H
 
#include <QWidget>
 
class QPaintEvent;
 
class QPaintWidget : public QWidget
{
   Q_OBJECT
public:
   QPaintWidget(QWidget * parent = 0);
protected:
   void paintEvent(QPaintEvent *);
};
 
#endif // QPAINTWIDGET_H

qpaintwidget.cpp
Код
C++ (Qt)
#include "qpaintwidget.h"
 
#include <QPainter>
 
QPaintWidget::QPaintWidget(QWidget * parent) : QWidget(parent)
{
}
 
void QPaintWidget::paintEvent(QPaintEvent *) {
   QPainter p(this); // Создаём новый объект рисовальщика
   p.setPen(QPen(Qt::red,1,Qt::SolidLine)); // Настройки рисования
   p.drawLine(0,0,width(),height()); // Рисование линии
}
 

Пояснения:

QPainter p(this); - создаём новый объект рисовальщика. Рисовать мы можем, по идее, на любом QPaintDevice, указав указатель на объект унаследованный от QPaintDevice в качестве параметра для создающегося QPainter объекта. (Подробнее о классах, на которых можно рисовать - QtAssitant::QPaintDevice;) В нашем случае мы передаём указатель на наш виджет - this. После этого наш объект QPainter класса может совершенно спокойно рисовать.

p.setPen(QPen(Qt::red,1,Qt::SolidLine)); - настраиваем рисование. а точнее говорим, что рисовать надо красным цветом, сплошными линиями толщиной 1 пиксель.

p.drawLine(0,0,width(),height()); - рисуем линию из точки с координатами (0,0) (по умолчанию это верхний левый угол виджета, однако это можно изменить) в точку с координатами (width,height).

В результате, мы добъёмся того, что у нас на виджете всегда будет рисоваться диагональная красная линия.


2. Небольшое пояснение о системах координат:

По умолчанию виджет имеет систему координат с началом в верхнем левом углу (точка (0,0) ). Ось X направлена вправо, ось Y направлена вниз. Однако это всегда можно изменить функцией setWindow() - подробности в QtAssistant.


3. Осталось создать объект нашего класса.

Я создал стандартный проект с MainWindow  окном. Потом положил на него вертикальный лейаут (но, разумеется, можно любой, и вообще - это просто для удобства всего лишь один способ разместить виджет на другом виджете) что бы в него помещать наш виджет.
Модифицированные строки выделены комментариями.

mainwindow.h
Код
C++ (Qt)
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
 
//nash class
#include "qpaintwidget.h"
 
namespace Ui {
   class MainWindow;
}
 
class MainWindow : public QMainWindow {
   Q_OBJECT
public:
   MainWindow(QWidget *parent = 0);
   ~MainWindow();
 
protected:
   void changeEvent(QEvent *e);
 
private:
   Ui::MainWindow *ui;
 
   // Указатель на объект нашего класса
   QPaintWidget *wgt;
};
 
#endif // MAINWINDOW_H
 

mainwindow.cpp
Код
C++ (Qt)
#include "mainwindow.h"
#include "ui_mainwindow.h"
 
MainWindow::MainWindow(QWidget *parent) :
   QMainWindow(parent),
   ui(new Ui::MainWindow)
{
   ui->setupUi(this);
 
   // Создаем виджет
   wgt = new QPaintWidget(this);
 
   // И кладём его в лайаут
   ui->verticalLayout->addWidget(wgt);
}
 
MainWindow::~MainWindow()
{
   // Подчищаем
   wgt->deleteLater();
   delete ui;
}
 
void MainWindow::changeEvent(QEvent *e)
{
   QMainWindow::changeEvent(e);
   switch (e->type()) {
   case QEvent::LanguageChange:
       ui->retranslateUi(this);
       break;
   default:
       break;
   }
}
 


4. Вот и всё.

Что же происходит? Cоздаётся виджет, его размером управляет лейаут. Как только произошло изменение размеров, у виджета вызывается метод paintEvent(QPaintEvent *), в котором мы на этом виджете рисуем диагональную линию красным цветом.


5. Разумеется, этого редко бывает достаточно. Если мы действительно хотим что-то рисовать на виджете (например, графики), то в ход идут немного другие алгоритмы.

Например:

5.1. В нашем виджете создаём QImage объект и пишем функции синхронизации размеров этого объкта с размерами виджета. Это будет наш, так называемый, Back Buffer - фоновый буффер для рисования.

5.2. Когда нам надо что-то нарисовать - мы рисуем именно на этой "картинке". Рисование может занимать долгое время, может короткое - мы можем вынести этот код в отдельный поток - это не проблема. Просто результатом потока будет объект QImage на котором будет нарисовано всё что надо (а пока будет расчитываться и рисоваться - мы будем сигналить процент прогресса).

5.3. При необходимости рисования мы один раз рисуем фоновый буфер на наш виджет (QImage объект на котором УЖЕ нарисовано всё что нам надо) методом QPainter::drawImage(...) внутри функции paintEvent(QPaintEvent *). Тем самым мы добъёмся того, что бы отрисовка происходила плавно и не загружала основной поток.

Этот базовый функционал, по идее, должен работать в Qt >= Qt4, однако мной тестировалось в 4.6.1. Прикреплённый файл содержит проект, полностью демонстрирующий вышеописаный текст =). Надеюсь, этот маленький HOWTO резко сократит количество вопросов по рисованию с QPainter. ;)


Название: Re: HOWTO::QPainter для начинающих.
Отправлено: xintrea от Май 17, 2010, 11:55
Хорошее HOWTO, годное.

(но русская языка вынудила миня полчаса править техт. нельзя так издеваца нат родным языком)


Название: Re: HOWTO::QPainter для начинающих.
Отправлено: ieroglif от Май 17, 2010, 12:22
Хорошее HOWTO, годное.

(но русская языка вынудила миня полчаса править техт. нельзя так издеваца нат родным языком)
Прошу прощения  :-X Буду стараться =)


Название: Re: Урок: QPainter для начинающих
Отправлено: LpsoldierMike от Май 15, 2011, 10:38
урок по моему отличный только у меня не строится линия, и это пичально.

ui->verticalLayout->addWidget(wgt); - это не распознает, типу что нету такого члена на форме (/home/mike/untitled1-build-desktop/../untitled1/mainwindow.cpp:11: ошибка: ‘class Ui::MainWindow’ has no member named ‘verticalLayout’)

далее, без него компилится и выдает
"Похоже, что это не отладочная сборка.
Установка точек останова по имени файла и номеру строки может не работать."

OS : Ubuntu 11.04; Qt 4.7.2 (x64);


Название: Re: Урок: QPainter для начинающих
Отправлено: ieroglif от Май 15, 2011, 10:44
урок по моему отличный только у меня не строится линия, и это пичально.

ui->verticalLayout->addWidget(wgt); - это не распознает, типу что нету такого члена на форме (/home/mike/untitled1-build-desktop/../untitled1/mainwindow.cpp:11: ошибка: ‘class Ui::MainWindow’ has no member named ‘verticalLayout’)

далее, без него компилится и выдает
"Похоже, что это не отладочная сборка.
Установка точек останова по имени файла и номеру строки может не работать."

OS : Ubuntu 11.04; Qt 4.7.2 (x64);
а ты на свой основной виджет положил лейаут что бы туда запихивать рисовальный виджет?
ибо именно на это у тебя и ругается


Название: Re: Урок: QPainter для начинающих
Отправлено: LpsoldierMike от Май 15, 2011, 11:32
оооо заработало, откровенно говорю что стал заниматься Кьютшкой немного вчера и сегодня, сразу в груфику почему то, очень понравилась среда, или правельнее библиотека, все так коректно и не занимает 6 гиг, как кое что другое)

вопрос ко всем, я только получу ответ и мона будет удалять посты, потому что спам, книгу какую посоветуете, я так привык что без бумажного варианта тяжело, и реально просто тупо не воспринимаю с экрана, нужна книга с закладками... какая???

спасибо автору долгожданная линия нрисовалась, и сразу вопрос можно ли как то узнать где в каком месте какая координата на форме?


Название: Re: Урок: QPainter для начинающих
Отправлено: daimon от Май 16, 2011, 17:01
Да я занялся графиком и делаю как написано для графика типа такого

Код
C++ (Qt)
void CPlotter2d::paintEvent(QPaintEvent * /* event */)
{
   QStylePainter painter(this);
 
painter.drawPixmap(0, 0,pixmap);// mg);*/
//refreshPixmap(opt_graph);
 
}
 
void CPlotter2d::resizeEvent(QResizeEvent * /* event */)
{
if(opt_graph.width_widget !=  this->size().width() || opt_graph.height_widget != this->size().height() )
{
opt_graph.width_widget = this->size().width();
opt_graph.height_widget = this->size().height();
 
pixmap= refreshPixmap(opt_graph);
}
 
//   refreshPixmap(opt_graph);
}

вот теперь появилась задача нарисовать на виджете графика виджет легенды - наследник от лист виджета, при этом он должен быть активным и его граб вместе с графиком можна было сохранять в картинку.

Слышал о графическом представлении. Как его заюзать под это?
спс за помощь
было бы хорошо найти простую статью по этой теме
что-то нашел http://doc.crossplatform.ru/qt/4.7.x/graphicsview.html (http://doc.crossplatform.ru/qt/4.7.x/graphicsview.html)


Название: Re: Урок: QPainter для начинающих
Отправлено: Denjs от Май 16, 2011, 17:38
ещё вопрос. Как это дело ускорить?!  ;D

ну QPainter и вообще? а то как-то все оно медленно отрисовывается...

я конечно рисую не на форме - но суть та-же : надо через QPainter отрисвать виджет в делегате ячейки таблицы... в общем - оно у меня начинает весьма притормаживать при большом количестве ячеек...

т.е. как можно ускорить рисование через QPainter ?
Какие операции выполняются быстрее а какие медленнее? (отрисовка текста на виджете - подозреваю медленно делается... а если копировать отрисованный "пиксмап" с надписью в QPainter - оно будет быстрее?)

какие условия и настройки влияют на производительность?


Название: Re: Урок: QPainter для начинающих
Отправлено: ieroglif от Май 16, 2011, 17:46
а если копировать отрисованный "пиксмап" с надписью в QPainter - оно будет быстрее?
будет. так называемый BackBuffer


Название: Re: Урок: QPainter для начинающих
Отправлено: lega4 от Октябрь 14, 2011, 19:00
Очень бы хотелось увидеть "вторую часть", посвященную рисованию на QImage и последующей связью с QWidget. Было бы интересно почитать про плюсы и минусы такого подхода, отличия от описанного в этой теме.
Цитировать
5. Разумеется, этого редко бывает достаточно. Если мы действительно хотим что-то рисовать на виджете (например, графики), то в ход идут немного другие алгоритмы.

Например:

5.1. В нашем виджете создаём QImage объект и пишем функции синхронизации размеров этого объкта с размерами виджета. Это будет наш, так называемый, Back Buffer - фоновый буффер для рисования.

5.2. Когда нам надо что-то нарисовать - мы рисуем именно на этой "картинке". Рисование может занимать долгое время, может короткое - мы можем вынести этот код в отдельный поток - это не проблема. Просто результатом потока будет объект QImage на котором будет нарисовано всё что надо (а пока будет расчитываться и рисоваться - мы будем сигналить процент прогресса).

5.3. При необходимости рисования мы один раз рисуем фоновый буфер на наш виджет (QImage объект на котором УЖЕ нарисовано всё что нам надо) методом QPainter::drawImage(...) внутри функции paintEvent(QPaintEvent *). Тем самым мы добъёмся того, что бы отрисовка происходила плавно и не загружала основной поток.


Название: Re: Урок: QPainter для начинающих
Отправлено: inhesion от Март 27, 2013, 16:56
и мне очень бы хотелось! поднимаю тему!


Название: Re: Урок: QPainter для начинающих
Отправлено: ieroglif от Март 27, 2013, 20:34
Просто приложу пример.
Разобравшийся с "базовым рисованием" не найдёт в нём для себя ничего непонятного и сложного, кроме потоков, о которых существует уже огромное количество документации и примеров.
Код написан под Qt5 (не под Qt4!) и демонстрирует рисование фонового буффера в отдельном потоке, дабы не тормозить поток GUI. Что бы потоку было хоть чем заняться ;) - он рисует эту же красную диагональную линию 144000 раз. Запускается процесс обновления фонового буффера на событие виджета resizeEvent, а на paintEvent всегда рисуется последняя сгенерированная версия.


Название: Re: Урок: QPainter для начинающих
Отправлено: inhesion от Март 27, 2013, 23:06
спасибо огромное! щас буду расбираться!


Название: Re: Урок: QPainter для начинающих
Отправлено: Al_ex от Сентябрь 20, 2013, 14:34
ieroglif, спасибо за тему. Все-таки Ваш последний пример не очень показательный - прогресс идет, а вот линия рисуется только при достижении 99% почему-то причем вся сразу. Логичнее было бы рисовать ее одновременно с прогресс баром в разных потоках, разве нет? Пример получился бы интереснее.


Название: Re: Урок: QPainter для начинающих
Отправлено: ieroglif от Сентябрь 20, 2013, 14:37
ну так чего же ты ждёшь? :)
скорее пиши "более показательный", и даже не пример, а нормальный урок! ;


Название: Re: Урок: QPainter для начинающих
Отправлено: Геннадий Носов от Август 19, 2015, 18:29
Спасибо за урок.)

Подскажите пожалуйста, а как сделать, чтоб линия рисовалась не сразу, а при нажатии кнопки?


Название: Re: Урок: QPainter для начинающих
Отправлено: Albegx от Сентябрь 22, 2019, 18:13
Отлично.