Russian Qt Forum
Апрель 23, 2024, 11:51 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Урок: QPainter для начинающих  (Прочитано 87086 раз)
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. Подмигивающий
« Последнее редактирование: Май 19, 2010, 08:38 от xintrea » Записан
xintrea
Moderator
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #1 : Май 17, 2010, 11:55 »

Хорошее HOWTO, годное.

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

Собираю информацию по крупицам
http://webhamster.ru
ieroglif
Гость
« Ответ #2 : Май 17, 2010, 12:22 »

Хорошее HOWTO, годное.

(но русская языка вынудила миня полчаса править техт. нельзя так издеваца нат родным языком)
Прошу прощения  Рот на замке Буду стараться =)
Записан
LpsoldierMike
Гость
« Ответ #3 : Май 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);
Записан
ieroglif
Гость
« Ответ #4 : Май 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);
а ты на свой основной виджет положил лейаут что бы туда запихивать рисовальный виджет?
ибо именно на это у тебя и ругается
Записан
LpsoldierMike
Гость
« Ответ #5 : Май 15, 2011, 11:32 »

оооо заработало, откровенно говорю что стал заниматься Кьютшкой немного вчера и сегодня, сразу в груфику почему то, очень понравилась среда, или правельнее библиотека, все так коректно и не занимает 6 гиг, как кое что другое)

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

спасибо автору долгожданная линия нрисовалась, и сразу вопрос можно ли как то узнать где в каком месте какая координата на форме?
Записан
daimon
Гость
« Ответ #6 : Май 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
« Последнее редактирование: Май 16, 2011, 17:06 от daimon » Записан
Denjs
Гость
« Ответ #7 : Май 16, 2011, 17:38 »

ещё вопрос. Как это дело ускорить?!  Смеющийся

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

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

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

какие условия и настройки влияют на производительность?
Записан
ieroglif
Гость
« Ответ #8 : Май 16, 2011, 17:46 »

а если копировать отрисованный "пиксмап" с надписью в QPainter - оно будет быстрее?
будет. так называемый BackBuffer
Записан
lega4
Гость
« Ответ #9 : Октябрь 14, 2011, 19:00 »

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

Например:

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

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

5.3. При необходимости рисования мы один раз рисуем фоновый буфер на наш виджет (QImage объект на котором УЖЕ нарисовано всё что нам надо) методом QPainter::drawImage(...) внутри функции paintEvent(QPaintEvent *). Тем самым мы добъёмся того, что бы отрисовка происходила плавно и не загружала основной поток.
Записан
inhesion
Гость
« Ответ #10 : Март 27, 2013, 16:56 »

и мне очень бы хотелось! поднимаю тему!
Записан
ieroglif
Гость
« Ответ #11 : Март 27, 2013, 20:34 »

Просто приложу пример.
Разобравшийся с "базовым рисованием" не найдёт в нём для себя ничего непонятного и сложного, кроме потоков, о которых существует уже огромное количество документации и примеров.
Код написан под Qt5 (не под Qt4!) и демонстрирует рисование фонового буффера в отдельном потоке, дабы не тормозить поток GUI. Что бы потоку было хоть чем заняться Подмигивающий - он рисует эту же красную диагональную линию 144000 раз. Запускается процесс обновления фонового буффера на событие виджета resizeEvent, а на paintEvent всегда рисуется последняя сгенерированная версия.
Записан
inhesion
Гость
« Ответ #12 : Март 27, 2013, 23:06 »

спасибо огромное! щас буду расбираться!
Записан
Al_ex
Гость
« Ответ #13 : Сентябрь 20, 2013, 14:34 »

ieroglif, спасибо за тему. Все-таки Ваш последний пример не очень показательный - прогресс идет, а вот линия рисуется только при достижении 99% почему-то причем вся сразу. Логичнее было бы рисовать ее одновременно с прогресс баром в разных потоках, разве нет? Пример получился бы интереснее.
Записан
ieroglif
Гость
« Ответ #14 : Сентябрь 20, 2013, 14:37 »

ну так чего же ты ждёшь? Улыбающийся
скорее пиши "более показательный", и даже не пример, а нормальный урок! ;
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.14 секунд. Запросов: 23.