Visual C++/CLI: рисуем простой график функции в PictureBox
В этом примере мы хотим нарисовать простой график на компоненте PictureBox
"по точкам", а не с использованием готовой компоненты Chart.
Создав проект Windows Forms в любой версии Studio 2012 и выше, придадим окну формы удобный размер и перетащим туда компоненту TableLayoutPanel
, установим ей свойство Dock=Fill
, затем удалим последнюю строку так, чтобы осталось 2 столбца, первому столбцу зададим относительную ширину 100%, а второму - абсолютную ширину 100 пикселов (как выполнять такие действия, показано в этой заметке).
В правую ячейку TableLayoutPanel
перетащим кнопку Button и привяжем её ко всем краям формы, кроме нижнего (свойство Anchor = Top, Left, Right
, свойство Dock=Top
).
В левую ячейку перетащим PictureBox
и также установим ему Dock=Fill
. Внешний вид полученной формы показан на рисунке:
Вид формы приложения
Так как экранный шаг в пикселах в условии не задан, установим его в переменной unit
. Для порядка шаблон отрисовываемой функции double f(double x)
опишем как делегат соответствующего типа. Весь этот код можно поместить после директивы #pragma endregion
в файле MyForm.h
:
#pragma endregion public: int unit = 50; //шаг в пискелах public: delegate double DelegatePtr(double); //тип функции для рисования private: double f(double x) { //конкретная функция, которую рисуем double y=Math::Cos(Math::Sqrt(x)); return Double::IsNaN(y)?0:y; }
Нам придётся запрограммировать только щелчок по кнопке, после двойного щелчка по ней в конструкторе создастся обработчик события
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
}
в который мы и поместим весь показанный ниже код с комментариями.
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { int pW = pictureBox1->Width, pH = pictureBox1->Height; Bitmap ^img = gcnew Bitmap(pW, pH); //создаем поверхность для рисования (изображение) с размером элемента управления PictureBox Graphics ^g = Graphics::FromImage(img); //создаем устройство для рисования на поверхности //рисуем сетку: for (int i = 0; i < pW; i += unit) g->DrawLine(Pens::Blue, i, 0, i, pH); for (int i = 0; i < pH; i += unit) g->DrawLine(Pens::Blue, 0, i, pW, i); //находим середину и рисуем линии осей: int mX = int(pW / 2 - pW / 2 % unit); int mY = int(pH / 2 - pH / 2 % unit); g->DrawLine(Pens::Red, mX, 0, mX, pH); g->DrawLine(Pens::Red, 0, mY, pW, mY); g->ScaleTransform(1, -1); //переворачиваем ось Y для удобства восприятия g->TranslateTransform((float)mX, -(float)mY); //смещаем нулевую координату на пересечение осей //рисуем график: DelegatePtr^ f = gcnew DelegatePtr(this, &MyForm::f); double x1 = -1., x2 = 10., s = 0.25; //границы рисования double x = x1; double y; System::Collections::Generic::List<PointF> ^Points = gcnew System::Collections::Generic::List<PointF>(); //коллекция точек графика while (x < x2) { y = f(x); Points->Add(PointF(x*unit, y*unit)); //добавляем точку в коллекцию. Полученные координаты сразу переводим в экранные единицы x += s; } g->DrawLines(Pens::Green, Points->ToArray()); //рисование линий графика delete g; //освобождение ресурсов устройства рисования this->pictureBox1->Image = img; //присвоение и отображение изображения в PictureBox }
График перерисовывается каждый раз при нажатии кнопки. Пожалуй, это даже экономичнее по ресурсам, чем использование события Paint
.
Скачать этот проект Visual Studio 2015 в архиве .zip, развернуть, не создавая новой папки (7 Кб)
Можно подойти к задаче и по-другому, заранее задав интервал изменения переменной по оси X, а шаг по ней (как и по оси Y) рассчитывая так, чтобы он был равен одному пикселу. Тогда при той же самой форме пользовательская часть кода примет следующий вид (для простоты исключено рисование линий сетки).
#pragma endregion /* простой график на PictureBox, шаг по x = 1 пикселу */ private: delegate double Function (double); //указатель на функцию private: double f (double x) { return Math::Sin(x); } //сама функция private: double x1 =-2*Math::PI, x2= 2*Math::PI, xstep, y1, y2, ystep; //границы, в реальности границы по x введены откуда-то извне private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { int pw = pictureBox1->Width, ph = pictureBox1->Height; //размеры полотна Function ^f = gcnew Function(this, &MyForm::f); //рисуемая функция xstep = (x2 - x1) / pw; //шаг по x = 1 пикселу y1 = y2 = f(x1); for (double x = x1; x <= x2; x += xstep) { //ищем макс. и мин. по y double y = f(x); if (y>y2) y2 = y; if (y<y1) y1 = y; } ystep = (y2 - y1) / ph; //шаг по y также = 1 пикселу (искажение пропорций) double xcoeff = pw / (x2 - x1), ycoeff = ph / (y2 - y1); //коэффициенты пересчета физических координат в пиксельные Bitmap ^img = gcnew Bitmap(pw,ph); //картинка для рисования на ней Graphics ^g = Graphics::FromImage (img); //контекст картинки int mx = - x1 * xcoeff, my = - y1 * ycoeff; //начало координат g->DrawLine(Pens::Red,mx,0,mx,ph);g->DrawLine(Pens::Red,0,my,pw,my); //оси System::Collections::Generic::List <PointF> ^Points = gcnew System::Collections::Generic::List <PointF>(); //коллекция точек double x = x1, y; while (x <= x2) { y = f(x); Points->Add(PointF((x-x1)*xcoeff,(y2-y)*ycoeff)); x += xstep; } g->DrawLines(Pens::Green,Points->ToArray()); //отрисовка this->pictureBox1->Image = img; //назначили картинку полотну }
Вид формы приложения (2)
10.11.2017, 13:12 [20455 просмотров]