На данном ресурсе совсем недавно появилось несколько довольно интересных статей по введению в программирование графики на Qt (см. "Отрисовка графических примитивов на Qt/C++", "Создание простой анимации с использованием QML"). К сожалению, ни в одной из них не рассматривается прикладной характер использования программирования графики. Под "прикладным характером" я подразумеваю возможность реального использования и быстроты разработки. В Qt реализован отличный механизм UI, который позволяет создавать окна буквально в два клика, используя технологию drag&drop. По сути, сама форма является лишь xml файлом, в котором обозначены расположения всех объектов. Использование данного механизма позволяет разработчику тратить меньше времени на реализацию интерфейса, и сосредоточиться на разработке самой логики программы.
В данном уроке я объясню вам как использовать данную технологию для создания программы отрисовки графиков, что будет являться отличным примером прикладного программирования графики на Qt.
Начать следует с создания проекта. В меню создания вам предложат создать форму, и если в других уроках рекомендовалось её не создавать, то в этом её следует создать.
При окончании создания проекта у вас будет четыре файла:
1) main.cpp -- главный файл, который должен присутствовать во всех проектах (за редким исключением)
2) MainWindow.h -- заголовочный файл окна, в котором будут описаны необходимые нам методы.
3) MainWindow.cpp -- файл, в котором будут реализованы методы, объявленные в mainwindow.h
4) MainWindow.ui -- файл формы.
Хочется заметить, что последние три файла могут у вас называться по-другому, если вы решили назвать главный класс "не по дефолту".
Кликнув два раза по файлу mainwindow.ui в Qt Creator, вы перейдете в меню создания интерфейса. С помощью layout'ов (как и в других средах программирования) зададим приблизительную разметку интерфейса и поместим на форму нужные нам кнопки и поля.
Объект label будет использоваться для отображения картинки.
Вот что у меня получилось:
Если есть желание -- можете поменять названия объектов кнопок. Чтобы определить методы нажатия на кнопки, достаточно кликнуть по ней (кнопке) правой кнопкой мыши, перейти к пункту меню "перейти к слоту" и выбрать on_click().
Теперь перейдем к коду. Я сразу приведу код файлов mainwindow.h и mainwindow.cpp с комментариями.
// mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtGui> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); void drawGraph(bool notEmpty = 0); // функция, отвечающая за отрисовку графика void recountPixels(); // в зависимости от точности (об этом позже) считаем количество пикселей на один условный сантиметр void getData(); // получаем значения границ графика и точности double f(double x); // первая функция double f1(double x); // вторая функция double f2(double x); // третья функция double f3(double x); // четвертая функция private slots: void on_exit_clicked(); // что будет если кликнуть на кнопку выхода void on_clear_clicked(); // ... на кнопку очистки void on_draw_clicked(); // ... на кнопку отрисовки void on_save_clicked(); // ... на кнопку сохранения private: Ui::MainWindow *ui; // форма double leftX,rightX; // границы по х double leftY,rightY; // границы по у int pictWidth,pictHeight; // ширина и высота картинки double step; // шаг (точность) double onePixelX,onePixelY; // количество пикселей на шаг double Ox,Oy; // координаты центра }; #endif // MAINWINDOW_H
#include "mainwindow.h" #include "ui_mainwindow.h" #include <cmath> #include <QDebug> using namespace std; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); // означает, что будем использовать форму pictHeight = 370; // задаем высоту картинки pictWidth = 540; // и её ширину step = 0.1; // задаем начальный шаг leftX = -100; rightX = 100; // и начальные значения границ leftY = -100; rightY = 100; drawGraph(); // сразу же отрисовываем пустой график } MainWindow::~MainWindow() { delete ui; // стандартный деструктор } // следующие 4 метода -- это лишь задания функций, которые мы хотим отрисовывать double MainWindow::f(double x) { return log(x)*(-1); } double MainWindow::f1(double x) { return sin(x)*(-1); } double MainWindow::f2(double x) { return cos(x)*(-1); } double MainWindow::f3(double x) { return sin(1.0/x)*(-1); } // метод вычисляет середину экрана и пересчитывает количество пикселей на шаг void MainWindow::recountPixels() { onePixelX = 540.0/(rightX-leftX); onePixelY = 370.0/(rightY-leftY); Ox = fabs(leftX); Oy = rightY; } void MainWindow::getData() { // ui->name->method() означает, что мы обращаемся к объекту name, который помещен на форме ui leftX = ui->inputLeftX->text().toDouble(); // узнаем границы rightX = ui->inputRightX->text().toDouble(); leftY = ui->inputLeftY->text().toDouble(); rightY = ui->inputRightY->text().toDouble(); step = 1.0/ui->inputAccuracy->text().toDouble(); // и шаг } void MainWindow::drawGraph(bool notEmpty) { QPixmap graph(540,370); // создаем саму картинку QPainter paint; // и пэинтер paint.begin(&graph); // запускаем отрисовку paint.eraseRect(0,0,540,370); // очищаем рисунок paint.drawLine(Ox*onePixelX,0,Ox*onePixelX,pictHeight); // и рисуем координатные оси paint.drawLine(0,Oy*onePixelY,pictWidth,Oy*onePixelY); paint.setPen(QPen(Qt::black,3)); // устанавливаем цвет и толщину "пера" for(double i = leftX;i<=rightX;i+=10.0) // рисуем черточки на координатой оси paint.drawPoint((i+Ox)*onePixelX,Oy*onePixelY); for(double i = leftY;i<=rightY;i+=10.0) paint.drawPoint(Ox*onePixelX,(i+Oy)*onePixelY); // если мы не рисуем график, то отображаем координатную ось и выключаемся if(!notEmpty) { paint.end(); ui->outputGraph->setPixmap(graph); return; } paint.setPen(QPen(Qt::green,1,Qt::SolidLine)); // снова задаем цвет и тип линии paint.setRenderHint(QPainter::Antialiasing, true); // задаем параметры рендеринга QPainterPath path,p[3]; // QPainterPath означаем, что мы вначале занесем все необходимые точки, а затем соединим их bool first[4] = {1,1,1,1}; // узнаем первая ли точка, или надо сдвигаться // последовательно проходимся по всем точкам графика, проверяем, существует ли функция в данной точке, и если существует -- заносим точку в массив отрисовки for(double i = (double)leftX+step;i<=(double)rightX;i+=step) { if(!isnan(f(i))) { if(first[0]) { path.moveTo((i+Ox)*onePixelX,(f(i)+Oy)*onePixelY); first[0] = false; } else path.lineTo((i+Ox)*onePixelX,(f(i)+Oy)*onePixelY); } if(!isnan(f1(i))) { if(first[1]) { p[0].moveTo((i+Ox)*onePixelX,(f1(i)+Oy)*onePixelY); first[1] = false; } else p[0].lineTo((i+Ox)*onePixelX,(f1(i)+Oy)*onePixelY); } if(!isnan(f2(i))) { if(first[2]) { p[1].moveTo((i+Ox)*onePixelX,(f2(i)+Oy)*onePixelY); first[2] = false; } else p[1].lineTo((i+Ox)*onePixelX,(f2(i)+Oy)*onePixelY); } if(!isnan(f3(i))) { if(first[3]) { p[2].moveTo((i+Ox)*onePixelX,(f3(i)+Oy)*onePixelY); first[3] = false; } else p[2].lineTo((i+Ox)*onePixelX,(f3(i)+Oy)*onePixelY); } } // проверяем, если в CheckBox отмечено, что график надо отрисовывать -- задаем нужный цвет и отрисовываем с помощью функции drawPath() if(ui->main->isChecked()) { paint.setPen(QPen(Qt::blue,1,Qt::SolidLine)); paint.drawPath(path); } if(ui->sin->isChecked()) { paint.setPen(QPen(Qt::green,1,Qt::SolidLine)); paint.drawPath(p[0]); } if(ui->cos->isChecked()) { paint.setPen(QPen(Qt::red,1,Qt::SolidLine)); paint.drawPath(p[1]); } if(ui->tg->isChecked()) { paint.setPen(QPen(Qt::darkMagenta,1,Qt::SolidLine)); paint.drawPath(p[2]); } paint.end(); // заканчиваем рисование ui->outputGraph->setPixmap(graph); // и помещаем рисунок на форму return; } void MainWindow::on_exit_clicked() { this->close(); // при нажатии на кнопку выхода, закрываем окно } void MainWindow::on_clear_clicked() { // при нажатии на кнопку очистки, пересчитываем пиксели и рисуем координатную ось recountPixels(); drawGraph(); } void MainWindow::on_draw_clicked() { // при нажатии на кнопку отрисовки, получаем границы и точность, пересчитываем значение шага и отрисовываем график getData(); recountPixels(); drawGraph(1); } void MainWindow::on_save_clicked() { // данную функцию я не буду подробно рассматривать, так как она не имеет прямого отношения к уроку, но в двух словах -- мы задаем имя как текущую дату + время и сохраняем в папку с программой // в зависимости от результата сохранения выводим соответствующее окно QTime time = QTime::currentTime(); QDate date = QDate::currentDate(); QString name; if(date.day()<10) name += "0"; name += QString::number(date.day())+"."; if(date.month()<10) name += "0"; name += QString::number(date.month())+"."; name += QString::number(date.year())+"_"; if(time.hour()<10) name += "0"; name += QString::number(time.hour())+"-"; if(time.minute()<10) name += "0"; name += QString::number(time.minute())+"-"; if(time.second()<10) name += "0"; name += QString::number(time.second()); QFile file(name+".png"); qDebug() << name; file.open(QIODevice::WriteOnly); QMessageBox msgBox; msgBox.setStandardButtons(QMessageBox::Ok); if(ui->outputGraph->pixmap()->save(&file,"PNG")) { msgBox.setText("Saved to program folder with name: "+name+".png"); msgBox.setWindowTitle("Saved!"); } else { msgBox.setText("Error saving."); msgBox.setWindowTitle("Error!"); } msgBox.exec(); }
В итоге у нас получается что-то вроде такого:
Как вы видите, использование ui очень упрощает работу программисту, так как ему не надо так много времени тратить на программирование интерфейса.
Прикрепленный файл | Размер |
---|---|
Graphs.zip | 4.08 кб |
Комментарии
Вдруг кто то будет пользоваться:)
Использовал статейку, но заметил одну ошибку: графики рисуются отражёнными относительно оси Ox (т.е. перевёрнутые). Для правильного отображения нужно в строках присвоения значения точкам графика вместо (f(i)+Oy) писать (Oy-f(i)) и так же где на оси ставим отметочки не (i+Oy), а (Oy-i)